C++模板详解

简介

(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)
④用户定义的转换,如类声明中定义的转换。

实参形参
TypeType&
Type&Type
Type[]Type*
Typeconst Type
Typevolatile 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&);
};
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值