C++模板详解

来源:互联网 发布:java 调用打印机 编辑:程序博客网 时间:2024/06/05 11:07

简介

(1)模板是允许以通用类型的方式来编写程序,其中的通用类型可以是int,double等具体类型。通俗的说使用模板就可以让程序猿编写与类型无关的代码。这种编程方式有时被称为通用编程,而由于类型是参数表示的,因此,模板特性有时也被称为参数化类型。
(2)模板通常有两种形式,一种是函数模板,一种是类模板。
(3)模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

函数模板

模板格式

 template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表) {     函数体 }

以swap函数为例:

template<class T>void swap(T& a,T& b){    T temp;    temp = a;    a = b;    b = temp;}

要建立一个模板,
①关键字template和class是必须的。当然可以使用typename替换class。
②必须使用尖括号。
③类型名T可以任意选择,只要符合C++的命名规则。

模板使用

函数模板的使用很简单,例如对于swap函数的使用,swap(3,5); 此时编译器就会生成如下代码:

void swap(int& a,int& b){    int temp;    temp = a;    a = b;    b = temp;}

注意:①函数模板不能缩减可执行程序。使用模板的好处是他是生成多个函数的定义更简单。更可靠。
②上面swap如果出现swap(1,2.1)则会则会出现错误。通过声明我们知道swap函数的两个类型应该是一致的。

模板重载

我们可以像重载常规函数那样来重载模板函数。
声明:

template<class T>void swap(T& a,T& b);template<class T>void swap(T* a,T* b,int n);

定义:

template<class T>void swap(T& a,T& b){    int temp;    temp = a;    a = b;    b = temp;}template<class T>void swap(T* a,T* b,int n){    T temp;    for(int i = 0;i < n;i++)    {        temp = a[i];        a[i] = b[i];        b[i] = temp;    }}

注意:①并不是所有的模板参数都必须是模板参数类型,也可以是具体类型。具体类型形参在模板定义的内部是常量值,也就是说具体类型形参在模板的内部是常量。 非类型模板的形参只能是整型,枚举,指针和引用,像int,double, String 这样的类型是不允许的。但是int&,int*,对象的引用或指针是正确的.调用具体类型模板形参的实参必须是一个常量表达式,即它必须能在编译时计算出结果。全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。另外,sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。另外。模板代码不能修改参数的值,也不能使用参数的地址。
②当模板函数和常规函数都存在时,并且在调用是函数名和参数都符合,那么优先调用的是常规函数。

显示具体化

在使用swap函数时,我们会遇到交换两个对象或者结构体的值,也会遇到交换两个对象或者结构体中某些变量的值,也就是说并不是交换所有变量的值。这种情况下,模板可能就不够用,所以我们可以通过提供一个具体化函数定义来实现需要的功能。这种方式就是显示具体化。
(1)对于给定的函数名,可以有模板函数,常规函数和显示具体化函数以及重载函数。
(2)显示具体化的原型和定义应以template<>开头,并明确指定类型。
(3)具体化将覆盖常规模板,而常规函数将覆盖具体化和常规模板。
以下是三种形式的swap函数:
常规函数:

void swap(int&,int&);

模板函数:

template<class T>void swap(T& a,T& b);

显示具体化:

template<>void swap<int>(int&,int&)

(4)具体化与实例化
①编译器通过模板为特定类型的生成函数定义时,得到的是模板实例。模板并非函数定义,但使用具体类型的模板实例是函数定义。这是实例化方式被称为隐式实例化。想对应的,显示实例化的句法如下:

template void swap<int>(int,int);

上面语句的含义是使用swap模板生成一哥使用int类型的实例,也就是’使用swap模板生成int类型的函数定义’。

②与显示实例化不同,显示具体化的句法如下:

template<> void swap<int>(int&,int&);template<> void swap(int&,int&);

上面的两个声明等价,显示具体化与显示实例化的区别在于,显示具体化它的含义是‘不要使用模板来生成函数定义,而应使用独立的,专门的函数定义显示的为特定类型生成函数定义’。显示具体化,必须有自己的函数定义。显示具体化声明在template后有<>,而显示实例化没有。
③隐式实例化,显示实例化和显示具体化统称为具体化,他们的相同处在于。它们的表示都是使用具体类型的函数定义,而不是通用描述。

调用哪个函数

对于函数重载,函数模板和函数模板重载,我们在调用函数时,编译器到底调用的是哪个函数??
编译器的这个过程被称为重载解析。具体流程如下:
(1)创建候选函数列表,函数列表包含于被调用函数的名称相同的函数或者模板函数。
(2)利用候选函数列表创建可行函数列表,这些都是函数参数数目正确的函数。这里有个隐式的转化序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如使用float实参可以将float转换为double,从而与double类型形参匹配,就可以使用double类型的模板为float类型生成一个实例。
(3)确定是否有最佳的可行函数。没有则函数调用错误。

参数匹配最佳到最差的顺序如下:
①完全匹配,常规函数优于模板函数。
②提升转换(char,short->int,float->double)
③标准转换(int->char,long->double)
④用户定义的转换,如类声明中定义的转换。

实参 形参 Type Type& Type& Type Type[] Type* Type const Type Type volatile Type Type* const Type Type* volatile Type*

在满足完全匹配时,编译器就能找到最佳的调用函数,除非找到多个函数完全匹配。但有两种情况下,编译器仍能完成重载解析。
①指向非const数据的指针和引用优先于非const指针和引用参数匹配。不过,const于非const的区别只适用于指针和引用指向的数据。
②一种是模板函数,另一种是非模板函数。这种情况下,非模板函数优于模板函数。(包括显示具体化)
如果两个完全匹配的函数是两个模板函数,则较具体的模板函数优先,也就是说编译器推断使用哪种类型时,执行的转换最少。

类模板

模板格式

template<class  形参名,class 形参名,…>   class 类名{...};

示例如下:

template<class T>class stack{private:    enum{MAX=10};    T items[MAX];    int top;    int stacksize;public:    stack();    bool isempty();    bool isfull();    bool push(const T& item);    bool pop(T & item);    stack& operator=(const stack& st);}template<class T>stack<T>::stack(){    top = 0;}template<class T>bool stack<T>::isempty(){    return top == 0;}template<class T>bool stack<T>::isfull(){    return top == MAX;}template<class T>bool stack<T>::push(const T& item){    if(top < MAX)    {        items[top++] = item;        return true;    }    else        return false;}template<class T>bool stack<T>::pop(const T& item){    if(top > 0)    {        item = items[--top];        return true;    }    else        return false;}template<class T>stack<T>& stack<T>::operator=(const stack<T>& st){    if(this == &st)        return *this;    delete [] items;    stacksize = st.stacksize;    top = st.top;    items = new T[stacksize];    for(int i = 0;i < top;i++)        items[i] = st.items[i];    return *this;}

模板使用

使用方式如下:

stack<int> test1;stack<string> test2;

特性

(1)可以为类型提供默认值

template<class T1,class T2=int> class test{...};

虽然可以为类模板提供默认类型,但是却不可以为函数模板提供默认类型,但是却可以为二者的非参数类型提供默认值。
(2)递归调用模板

stack< stack<int> > test;

(3)模板可以使用多个类型参数

template<class T1,class T2>class test{...};

模板的具体化

(1)隐式实例化
声明了一个或多个对象,指出所需类型,编译器通过模板生成具体类型的类声明。
(2)显式实例化
当使用template关键字并指出所需类型来声明类时,编译器将生成类声明的显式实例化,实例如下:

template class stack<int>;

这种情况下,虽然没有创建或者提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板生成具体化。
(3)显示具体化
显示具体化是特性类型(用于替换模板中的通用类型)的定义,有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同,在这种情况下,可以创建显示实例化。
显示具体化模板格式:

template<> class classname<>{...};

(4)部分具体化

template<class T> class<T,int>{...};

如果有多个模板可供选择,编译器将会选择具体化程度最高的模板。
除了上面的方式,也可以使用指针对模板进行部分具体化。

template<class T>class test{...};template<class T*>class test{...};

如果提供的类型不是指针将会使用上面的模板,如果使用的是指针,将会使用下面的模板。

成员模板

C++中模板可用做结构,类或者模板类的成员。

template <class T>class test{private:    template<class T1>    class test_1    {    private:        T1 val;    public:        test_1(T1 v = 0):val(v){}        void show() const {cout<<val<<endl;}        T1 Value() const {return val;}      };    test_1<T> q;    test_1<int> n;public:    test(T t,int i):q(t),n(i){}    template<class U>    U test2(U u,T t){return ...}    void show() const {q.show();n.show();}};

将模板用作参数

template<template <typenameT> class Thing>class crab{private:    Thing<int> s1;    Thing<double> s2;public:    Crab(){};    bool push(int a,double x){return s1.push(a)&&s2.push(b);}    bool pop(int& a,double& x){return s1.pop(a)&&s2.pop(x);}}int main(){    Crab<stack> test;    test.push(4,3.5);    test.pop(4,3.5);    return 0;}

模板类与友元

模板的友元分三类:
1.非模板友元
2.约束模板友元,即友元的类型取决于类被实例化的类型。
3.非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。

模板类的非模板友元函数

template<class T>class HasFriend{    friend void counts();};

上面的声明使counts函数成为模板所有实例化的友元。
如果友元函数的参数是模板本身,该怎么定义??

template<class T>class HasFriend{    friend void counts(HasFriend<T> &);}

上面的声明含义,带HasFriend参数的counts()将成为HasFriend类的友元,同样,带HasFriend参数的counts()函数将是counts的一个重载版本,也是HasFriend类的友元。

模板类的约束模板友元函数

为约束模板友元做准备,要是类的每一个具体化都获得与友元匹配的具体化。具体步骤为三步。
(1)首先,在类定义的前面声明每个模板函数
template void counts();
template void report(T &);
(2)然后在函数中再次将模板声明为友元,这些语句根据类模板的参数的类型声明具体化:

template<class TT>class HasFriend{    friend void counts<TT>();    friend void report<>(HasFriend<TT>&);};

(3)为友元提供模板定义。

模板类的非约束模板友元函数

约束模板友元函数是在类外面声明的模板的具体化。int类具体化获得int函数的具体化,以此类推,通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类的具体化友元。

template<class T>class HasFriend{    template<class C,class D> friend void show(C&,D&);};