C++模板详解

参考:C++ 模板详解(一)

 

模板类型进行参数化的工具;通常有两种形式: 

  • 函数模板:仅参数类型不同;
  • 类模板:   仅数据成员成员函数类型不同。

目的:让程序员编写与类型无关的代码。

注意:模板的声明或定义只能在全局、命名空间、类范围内进行。即不能在局部范围、函数内进行,比如不能在main函数中声明或定义一个模板。

 

一 函数模板

1 函数模板的格式: 

template <class 类型名1,class 类型名2,......> 
返回类型 函数名(参数列表)
{
    函数体
}
  • template、class是关键字,class可以用关键字typename代替,在这里typename和class没区别
  • <>中的参数叫模板形参不能为空。模板形参用传入的实参类型来初始化。
  • 模板形参可以在 返回类型、参数列表、函数体内使用。一旦编译器确定了模板实参类型,就称他实例化了函数模板的一个实例。

 

例1:swap函数的模板:

template <class T> 
void swap(T& a, T& b)   //参数列表使用模板形参
{
T tmp = a; //函数体内使用模板形参
a = b;
b = tmp; }
  • 当调用这个函数模板时,类型T就会被被调用时的类型所代替
  • 比如swap(a,b),其中abint 型,模板函数就变为swap(int &a, int &b)
  • 而当swap(c,d),其中cddouble型时,模板函数会被替换为swap(double &a, double &b)。

 

例2:max函数的模板 

 1 #include<iostream>
 2 
 3 template<typename T>
 4 const T& myMax(const T &a, const T &b)
 5 {
 6   return a > b ? a : b;
 7 }
 8  
 9 int main()
10 {
11   cout << myMax(2.1, 2.2) << endl;         //输出2.2  模板实参被隐式推演成double
12   cout << myMax<double>(2.1, 2.2) << endl; //输出2.2  显示指定模板参数。
13   cout << myMax<int>(2.1, 2.2) << endl;    //输出2    显示指定的模板参数,会将参数转换为int。
14 
15   return 0;
16 }

  

2、注意:

  • 不存在swap(int, int)这样的调用! 即不能用类型初始化,只能用实参推演来进行。即根据2来推出int型。
  • 即只能进行swap(2, 3);  或者  int a, b; swap(a,b);   这样的调用。

 

二 类模板

1 类模板的格式为:

template<class 形参名1, class 形参名2, ...>
class 类名
{
    ... 
};
  • 与函数模板一样,以template开始,后接模板形参列表模板形参不能为空
  • 类的数据成员和函数成员可以使用模板形参。

 

:一个类模板的例子:

template<class T> 
class A
{
public: 
    T a;               //类成员使用模板形参
    T b; 
    T func(T c, T &d);
};

 

2 类模板对象的创建

  • 方法:A<int> m;  类A中用到模板形参的地方都会被int 所代替。
  • 两个模板形参:A<int, double> m;  类型之间用逗号隔开。

3 类模板形参必须指定类型而不是实参:

  • 必须这样指定 A<int> m;  明确指定类型。
  • 不能这样使用A<2> m;  类模板形参不存在实参推演的问题。

4 在类模板外部定义成员函数的方法为

template<模板形参列表> 
函数返回类型 类名<模板形参名>::函数名(参数列表) 
{
    函数体
}

 

:比如模板类A,有两个模板形参T1,T2,有一个成员函数 void func(),则外部定义该函数的语法为:

template<class T1, class T2>   //与类一致
void A<T1, T2>::func()      //类名也要加上模板参数列表
{
}

注意:当在类外面定义类的成员时,template后面的模板形参应与所定义的类的模板形参一致

 

三 模板的形参

包括 类型形参、非类型形参、默认形参。

1 类型形参 

类型形参由关键字class或typename后接说明符构成,如

template<class T> 
void func(T a)
{
};
  • 其中 就是一个类型形参,名字由用户确定。

 

函数模板,同一个类型形参,必须用相同类型的实参来初始化,比如

template<class T>
void func(T a, T b)
{
}
  • 调用 func(2, 3.2); 将编译出错,因为模板形参T同时被指定为int 和 double,不一致,会出错。

 

类模板,其内部成员函数,则没有上述的限制,比如 

template<class T>
class A
{
public:
    T func(T a, T b);   //或者T func(const T &a, const T &b);  普通引用会编译报错
};
  • 声明 A<int> a;  调用 a.func(2, 3.2);  在编译时不会出错
  • 第二个实参3.2类型为double,在运行时,会强制类型转换3

 

:模板类的对象调用成员函数: 

 1 #include <iostream>
 2 using namespace std;
 3 
 4 template<class T>
 5 class A
 6 {
 7 public:
 8     A();
 9     T func(T a, T b);
10 };
11 
12 template<class T>     //类外定义构造函数
13 A<T>::A()
14 {
15 }
16 
17 template<class T>     //类外定义成员函数
18 T A<T>::func(T a, T b)
19 {
20     return a + b;
21 }
22 
23 int main(int argc, char *argv[])
24 {
25     A<int> ia;                        //模板实参为int类型的对象
26     cout << ia.func(3, 2.1) << endl;  //输出5
27 
28     A<double> da;
29     cout << da.func(3, 2.1) << endl;  //输出5.1
30 
31     return 0;
32 }

  

2 非类型形参

也就是内置类型形参,如

template<class T, int a>   //int a 就是非类型形参
class B
{
};

非类型形参有几点要注意的:

  • 在模板定义的内部是常量值,也就是说,上面的a在类B内部是一个常量。
  • 形参只能是整型、指针、引用,像double、string、string **是不允许的,但是double &、double *、对象的引用或指针是正确的。
  • 实参必须是一个常量表达式,即必须能在编译时计算出结果。注意局部对象/变量和其地址,全局指针/变量/对象,都不是常量表达式;全局变量/对象地址或引用const类型变量sizeof的结果,都是常量表达式。
  • 形参是整型时,实参也必须是整型的,且在编译期间是常量,比如
template <class T, int a> 
class A
{
};

如果有int b;  这时 A<int, b> m; 出错,因为b不是常量;如果有 const int b;  这时 A<int, b> m;  正确,因为这时b是常量。

  • 非类型形参一般不应用于函数模板中,比如有函数模板
template<class T, int a> 
void func(T b)
{
}

若用func(2)调用,会出现无法为非类型形参a推演出参数的错误;可以用显示模板实参来解决,如用func<int, 3>(2); 把非类型形参a设置为整数3。

  • 形参实参间所允许的转换 
//1 数组到指针,函数到指针的转换
template<int *a>
class A { };
int b[10];
A<b> m;      //数组转换成指针 

//2 const修饰符的转换
template<const int *a>
class A { };
int b;
A<&b> m;     //从int*转换成const int *

//3 提升转换
template<int a>
class A { };
const short b = 2;
A<b> m;       //short到int提升

//4 整数转换
template<unsigned int a>
class A { };
A<3> m;    //int到unsigned int转换

//5 常规转换

  

由用户指定栈的大小,并实现栈的相关操作 

  1 #include <iostream> 
  2 #include <string> 
  3 #include <stdexcept>  //std::out_of_range
  4 #include <cstdlib>    //EXIT_FAILURE
  5 using namespace std;
  6 
  7 /*********模板类,声明开始,一般都是放在头文件的*********/
  8 
  9 template<class T, int MAXSIZE>
 10 class myStack
 11 {
 12 public:
 13     myStack();
 14     void push(T const &);  //入栈
 15     void pop();            //出栈
 16     T    top() const;      //返回栈顶
 17 
 18     bool empty() const     //判断是否为空
 19     {
 20         return size == 0;
 21     }
 22 
 23     bool full() const      //判断栈是否已满
 24     {
 25         return size == MAXSIZE;
 26     }
 27 
 28 private:
 29     T   elems[MAXSIZE];    //使用数组存放栈元素,由于非类型形参MAXSIZE在类内是一个常量,所以可以用来声明数组大小
 30     int size;              //栈已使用空间
 31 };
 32 
 33 /**********模板类,声明结束,定义成员函数开始********/
 34 
 35 template<class T, int MAXSIZE>
 36 myStack<T, MAXSIZE>::myStack(): size(0)      //构造函数,初始化为空
 37 {
 38 }
 39 
 40 template<class T, int MAXSIZE>
 41 void myStack<T, MAXSIZE>::push(T const &new_elem)     //入栈
 42 {
 43     if(size == MAXSIZE)
 44     {
 45         throw out_of_range("myStack::push(): stack is full");
 46     }
 47     elems[size++] = new_elem;
 48 }
 49 
 50 template<class T, int MAXSIZE>
 51 void myStack<T, MAXSIZE>::pop()       //栈顶出栈
 52 {
 53     if(size <= 0)
 54     {
 55         throw out_of_range("myStack::pop(): stack is empty");
 56     }
 57     --size;
 58 }
 59 
 60 template<class T, int MAXSIZE>
 61 T myStack<T, MAXSIZE>::top() const    //返回栈顶元素
 62 {
 63     if(size <= 0)
 64     {
 65         throw out_of_range("myStack::top(): stack is empty");
 66     }
 67     return elems[size - 1];
 68 }
 69 
 70 /***********成员函数定义结束**********************/
 71 
 72 int main(int argc, char *argv[])
 73 {
 74     try
 75     {
 76         myStack<int,    20>  int20Stack;    //显示模板实参
 77         myStack<int,    40>  int40Stack;
 78         myStack<string, 40>  stringStack;
 79 
 80         int20Stack.push(7);
 81         cout << int20Stack.top() << endl;   //输出7
 82         int20Stack.pop();
 83 
 84         for(int i = 0; i < 40; ++i)
 85             int40Stack.push(i);
 86         cout << int40Stack.top() << endl;   //输出39
 87         //int40Stack.push(41);              //继续添加元素,会抛出异常,输出Exception: myStack::push(): stack is full
 88         
 89         stringStack.push("hello");
 90         cout << stringStack.top() << endl;  //输出hello
 91         stringStack.pop();
 92         //stringStack.pop();                //继续出栈,会抛出异常,输出Exception: myStack::push(): stack is empty
 93 
 94         return 0;
 95     }
 96     catch(out_of_range const &ex)
 97     {
 98         cerr << "Exception: " << ex.what() << endl;
 99         return EXIT_FAILURE;
100     }
101 }

  

3 默认形参

类模板可以有默认值,函数模板不能有默认值。

2 类模板,类型形参,默认值形式为:

template<class T1, class T2 = int>   //为第二个模板类型形参提供int型的默认值
class A
{
};

3 类模板,类型形参,默认值和普通函数的默认参数一样,如果有多个类型形参,则从第一个设定了默认值的形参之后,所有模板形参都要设定默认值

template<class T1 = int, class T2>  //错误!如果T1有默认值,T2也必须有
class A
{
};

4 类模板,类型形参,外部定义类的成员函数。template 后的形参列表应省略掉默认值。

template<class  T1, class T2 = int> 
class A
{
public: 
    void func();
}; 

template<class T1,class T2>   //定义方法,省略掉默认值
void A<T1,T2>::func()
{
}

 

:有默认值的类模板

 1 #include <iostream>
 2 using namespace std; 
 3 
 4 template<typename T1, typename T2 = double, int abc = 5>  //第二个类型形参和非类型形参有默认值
 5 class A
 6 {
 7 public:
 8     void print(T1 a, T2 b);
 9 };
10 
11 template<typename T1, typename T2, int abc>   //类外定义时不能有默认值,毕竟类的声明是作为接口给别人看的
12 void A<T1, T2, abc>::print(T1 a, T2 b)
13 {
14     cout << a << ' ' << b << endl;
15     cout << abc << endl;16 }
17 
18 int main(int argc, char *argv[])
19 {
20     A<int> a;
21     a.print(2.2, 2.1);   //输出 2 2.1 5, 输出2是因为指定为int型,进行了类型转换,输出5是默认值
22 
23     return 0;
24 }

 

posted @ 2014-09-09 01:48  阿杰的专栏  阅读(508)  评论(0编辑  收藏  举报