STL与泛型编程(第一周)

part 1 C++模版简介

一,模版概观

1.模板 (Templates)是C++的一种特性,允许函数或类(对象)通过泛型(generic types)的形式表现或运行。

 模板可以使得函数或类在对应不同的型别(types) 的时候正常工作,而无需为 每一个型别都写一份代码。

2.C++主要有两种类型的模板:

  (1)类模板(Class template): 使用泛型参数的类(classes with generic parameters)

  (2)函数模板(Function template): 使用泛型参数的函数(functions with generic parameters)

3.模版实例化  

  模板的声明(declaration)其实并未给出一个函数或类的完全定义(definition),只是提供了一个函数或类的语法框架(syntacticalskeleton)。

  实例化是指从模板构建出一个真正的函数或类的过程,比如:

template <typename T> struct Object { . . . };

可以用来构建诸如Object<int>, Object<char>, Object<int*>, Object<MyClass*>等等不同型别的具体实例。

  实例化有两种类型:

  (1)显式实例化–在代码中明确指定要针对哪种型别进行实例化。
  (2)隐式实例化–在首次使用时根据具体情况使用一种合适的型别进行实例化。

 

二,模版函数

4.函数模版

 定义:函数模板是参数化的一族函数(a family of functions)。

 通过函数模板, 可以定义一系列函数,这些函数都基于同一套代码,但是可以作用在不同型别的参数上。

template <typename T>
T max(const T& a, const T& b)
{
    return (a>b) ? a : b;
}

   也可以使用 class 来代替 typename , 但是不推荐使用。

5.模版的使用

int i = 1, j = 2;
max(i, j);    // 正确

int f = 3.1, g = 4.2
max(f, g);    //正确

max(i, f);    //错误
              //compile error:  template parameter 'T' is  ambiguous

6. 模版实例化

 用具体型别替代模板参数T的过程叫做实例化(instantiation);从而产生了一个模板实例。 

 一旦使用函数模板,这种实例化过程便由编译器自动触发的,不需要额外去请求模板实例化。

 如果实例化一种型别,而该型别内部并不支持函数所使用的操作,那么就会导致一个编译错误。

 例如:std::complex并没有重载“>”,也就是说该型别并不支持使用“>”比较大小,而Max函数使用“>”来判断c1、c2的大小,所以无法通过Max(complex1, complex2)得到预期的结果。

 结论: 模板被编译了两次
  1)没有实例化之前,检查模板代码本身是否有语法错误。
  2)实例化期间, 检查对模板代码的调用是否合法。

7.参数推导

  模板参数是有传递给模板函数的实参决定的不允许自动型别转换:每个T必须严格匹配!

Max(1, 2) // OK:两个实参的型别都是 int
Max(1, 2.0) // ERROR:第一个参数型别是 int,第二个参数型别是 double

  一般有两种处理这种错误的方法:

    1) 用 static_cast或强制转换参数型别以使两者匹配。
    2) 显式指定T的型别。

Max(static_cast<double>(1), 2.0)
Max<double>(1, 2.0)

8.函数模版重载

  (1)重载:函数模板也可以像普通函数一样被重载
  (2)同名:非模板函数可以和同名的模板函数共存
  (3)参数推导:编译器通过函数模板参数推导来决定使用调用哪个重载

// 普通函数
inline int const& Max(const int const& a, const int const& b) // 第一个

template <typename T>
inline T const& Max(const T const& a, const T const& b) // 第二个

template <typename T>
inline T const& Max(const T const& a, const T const& b, const T const& c) // 第三个

  Max(7, 42, 68): 调用接受三个参数的模板 // 调用 第三个
  Max(7.0, 42.0): 调用 Max<double>(参数推导) // 调用 第二个
  Max('a', 'b'): 调用 Max<char>(参数推导) // 调用 第二个
  Max(7, 42): 调用非模板函数,参数型别为 int // 调用 第一个 其他因素都相同的情况下,重载裁决过程调用非模板函数,而不是从模板产生实例
  Max<>(7, 42): 调用 Max< int>(参数推导) // 调用 第二个 允许空模板参数列表
  Max<double>(7, 42): 调用 Max<double>(无需参数推导) // 调用 第二个
  Max('a', 42.7): 调用非模板函数,参数型别为 int // 调用 第一个 对于型别不同的参数只能调用非模板函数( char 型别 'a' 和 double 型别 42.7 都将转化为 int 型别)

  总结
    对于不同的实参型别,模板函数定义了一族函数
    当传递模板实参的时候,函数模板依据实参的型别进行实例化
    可以显式指定模板的实参型别
    函数模板可以重载
    当重载函数模板时, 将改变限制在:显式指定模板参数
    所有的重载版本的声明必须位于它们被调用的位置之前

 

三,类模版

  1.1与函数模版类似,类也可以通过函数参数泛化,从而可以构建出一族不同型别的类实例(对象)。

    1.2 类模版实参可以是某一型别或常量(仅限int或enum)。

  2. 类模版实例

const std::size_t DefaultStackSize = 1024;
template<typename T, std::size_t n = DefaultStackSize>
class Stack
{
public:
    void Push(const T const& element);
    int Pop(T& element);
    int Top(T& element) const;
private:
    std::vector<T> m_Members;
    std::size_t m_nMaxSize = n;
};

  n是编译时定义的常量,n可以有默认值。

  3.类模板的声明

  除了 Copy constructor 之外,如果在类模版中需要使用到这个类本身,比如 operator= ,那么应该使用其完整的定义(Stack<T>),而不是省略型别 T 。如下面的例子所示: 

template <typename T, std::size_t n>
class Stack
{
public:
    Stack(Stack<T, n> const&); // copy constructor
    Stack<T>& operator= (Stack<T, n> const&); //assignment operator
};

 

 4. 类模板的实现

  要定义一个类模板的成员函数,则要指明其是一个模版函数。

 4.1 例如,Push 函数的定义应当如下:

template <typename T, std::size_t nMaxSize>
void Stack<T, nMaxSize>::Push(const T const& element)
{
    if (m_Members.size() >= m_nMaxSize)
    {
        //error handing ...
        return;
    }

    m_Members.push_back(element);
}

  4.2 Pop 函数:从 Stack 中弹出顶部元素,但是没有 pop 出该元素:

template <typename T, std::size_t nMaxSize>
int Stack<T, nMaxSize>::Top(T& element) const
{
    if (m_Members.empty())
        return 0;

    element = m_Members.back();
    return 1;
}

 

posted @ 2015-09-22 10:56  健康平安快乐  阅读(292)  评论(0编辑  收藏  举报