Fork me on GitHub

C++模板

说完了C++的内存管理,今天我们讲讲泛型编程的基础:模板

先说上那么两句

首先模板在我看来是懒的产物,我们厌倦了一次只实现一种功能,像是卖票的阿姨,不管你是什么身份,票都应该是一样卖才是,而我们的语言在没有实现模板之前,这样的事情是需要我们提前“说好的”:对于学生你怎么卖票,对于老师你怎么卖票。。。等等
于是聪明的大佬们想出了一个好办法:模板,我们只告诉你有人买票你就这样卖:这样下来无论是谁来买票你都会自适应地转换成对这种人怎么卖票了。

函数模板

说完了上面的一小段,让我们考虑如何实现通用的交换函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Swap(int& left,int& right)
{
inttemp=left;
left=right;
right=temp;
}
void Swap(double& left,double& right)
{
doubletemp=left;
left=right;
right=temp;
}
void Swap(char& left,char& right)
{
chartemp=left;
left=right;
right=temp;
}
......

诸如此类的函数我们可以一直写下去(自定义类型的数量是无穷的),这对于我们来说绝对是一个难受的事情
使用函数重载虽然可以实现,但是有以下几个不好的地方:

  1. 重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错
    那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
    于是就出现了函数模板:

函数模板格式
template < typename T1, typename T2,……,typename Tn >
返回值类型函数名(参数列表){}

1
2
3
4
5
6
7
template <typename T>
void Swap(T& left,T& right)
{
Ttemp=left;
left=right;
right=temp;
}

那么函数模板是怎么办事的呢?

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例
化。

  • 隐式实例化:让编译器根据实参推演模板参数的实际类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    template<class T>
    T Add(const T& left,const T& right)
    {
    return left+right;
    }
    int main()
    {
    int a1=10,a2=20;
    double d1=10.0,d2=20.0;
    Add(a1,a2);
    Add(d1,d2);
    /*
    该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
    通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int或者double类型而报错
    在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
    Add(a1, d1);
    */ //*
    //此时有两种处理方式:1.用户自己来强制转化2.使用显式实例化
    Add(a, (int)d);
    return 0;
    }
  • 显式实例化:在函数名后的<>中指定模板参数的实际类型

    1
    2
    3
    4
    5
    6
    7
    8
    intmain(void)
    {
    int a=10;
    double b=20.0;
    //显式实例化
    Add<int>(a,b);
    return 0;
    }//如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //专门处理int的加法函数
    int Add(int left,int right)
    {
    return left+right;
    }
    //通用加法函数
    template<class T>
    T Add(T left,T right)
    {
    return left+right;
    }
    void Test()
    {
    Add(1,2); //与非模板函数匹配,编译器不需要特化
    Add<int>(1,2); //调用编译器特化的Add版本
    }
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模
    板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //专门处理int的加法函数
    int Add(int left,int right)
    {
    return left+right;
    }
    //通用加法函数
    template<class T1,class T2>
    T1 Add(T1 left,T2 right)
    {
    return left+right;
    }
    void Test()
    {
    Add(1,2);
    Add(1,2.0);
    }
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

类模板

类模板的定义格式

1
2
3
4
5
template<class T1,class T2, ...,class Tn>
class 类模板名
{
//类内成员定义
};//注意:不是具体的类,是编译器根据被实例化的类型生成具体类的模具

类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>
中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
比如实现了一个模板类list list是类的名字 只有加上示例类型如list这才是一个类型,可以用来定义一个变量。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!