@TOC

1.非类型模板参数

模板参数分为 类型形参 和 非类型形参 类型形参即出现在模板参数列表中, 跟在class或者typename之类的参数类型名称 非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将参数当成常量使用


#include<iostream>
using namespace std;
#define N 10
template <class T>//类型模板参数
class Array
{
public:
private:
    T _a[N];
};
int main()
{
    Array<int> a;//10
    Array<double>b;//100
    return 0;
}

使用类型模板参数,虽然看似可以解决问题,但若a要10个int的数组,b要100个double的数组,宏就没办法解决了


所以为了解决这个问题,引入非类型模板参数,定义的是常量,一般是整形常量


#include<iostream>
using namespace std;
template <class T,size_t N>//非类型模板参数
class Array
{
public:
private:
    T _a[N];
};
int main()
{
    Array<int,10> a;
    Array<double,20>b;
    return 0;
}

同样非类型模板参数还可以给缺省值 ,由于是整型常量,所以缺省值也是常量

【C++】模板进阶_Less


同样在函数内部,无法对常量值进行修改

#include<iostream>
using namespace std;
template<class T,size_t N=20>
void func(const T& a)
{
    N = 20;//左操作数必须为左值
}
int main()
{
    func(1);
}

必须为整形常量 整形包括 int 、char、 long 、long long 、short

【C++】模板进阶_Data_02

若为double类型就会报错

2. 模板特化

使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理

函数模板特化


#include<iostream>
using namespace std;
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    bool operator<(const Date& d)const
    {
        return (_year < d._year) ||
            (_year == d._year && _month < d._month) ||
            (_year == d._year && _month == d._month && _day < d._day);
    }
    bool operator>(const Date& d)const
    {
        return (_year > d._year) ||
            (_year == d._year && _month > d._month) ||
            (_year == d._year && _month == d._month && _day > d._day);
    }
    friend ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};

template<class T>
bool Less(T left, T right)
{
    return left < right;
}


int main()
{
    cout << Less(1, 2) << endl; // 可以比较,结果正确
    Date d1(2022, 7, 7);
    Date d2(2022, 7, 8);
    cout << Less(d1, d2) << endl; // 可以比较,结果正确
    Date* p1 = &d1;
    Date* p2 = &d2;
    cout << Less(p1, p2) << endl; // 可以比较,结果错误
    return 0;
}

若p1与p2都为Date*,则调用Less ,会变成 left与right指针的比较 ,不符合预期,我们想要的是日期与日期之间的比较


所以为了防止这种 特殊情况的发生,所以使用 模板特化 (对某些类型进行特殊化处理)

【C++】模板进阶_Data_03

此时就可以正常比较两个日期


【C++】模板进阶_Data_04

函数模板特化意义不大,可以直接使用函数重载的方式来实现的

类模板特化

偏特化

偏特化——进一步的限制 必须在原模版的基础上才可以使用

【C++】模板进阶_实例化_05

针对的是指针这个泛类

全特化

必须在原模版的基础上才可以使用

【C++】模板进阶_实例化_06

只针对其中一个,如指针中的日期类

半特化

对部分参数特化

template<class T1, class T2>
class Data
{
public:
    Data() { cout << "Data<T1, T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};

//半特化
template <class T1>
class Data<T1, int>//只将第一个模板参数特化
{
public:
    Data() { cout << "Data<T1, int>" << endl; }
private:
    T1 _d1;
    int _d2;
};
void TestVector()
{
    Data<int, int> d1;//Data<T1, int>
    Data<int*, int> d3;
//Data<T1, int>
    Data<double, int> d4;
//Data<T1, int>
}
int main()
{
    TestVector();
    return 0;
}

此时虽然有两个参数,但是只特化了一个 ,无论传的是 int、int*、double 都是走到半特化

参数的进一步限制
两个参数特化为引用类型

【C++】模板进阶_Less_07

两个参数特化为指针类型

【C++】模板进阶_Data_08

3. 模板的分离编译

模板并不支持分离编译 即声明在一个文件,定义在一个文件

【C++】模板进阶_Data_09

此时调用模板add 就会报错,因为模板的声明和定义不在同一文件中


【C++】模板进阶_Data_10

调用普通函数func,即可正常运行

模板会发生链接错误

func.h func.cpp test.cpp预处理:头文件展开/ 注释的消除/ 条件编译/ 宏的替换

【C++】模板进阶_实例化_11


【C++】模板进阶_实例化_12

编译: 检查语法,生成汇编代码func.s test.s汇编:汇编代码转换为二进制机器码func.o test.o 链接:合并生成可执行文件 a.out


由预处理阶段到 编译阶段时,通过func.i生成func.s ,生成汇编指令 ,但是只能生成func的,不能生成add的

【C++】模板进阶_实例化_13

函数被编译后生成指令,才可以找到第一句指令的地址 即函数地址

func会被编译成一堆指令,所以在func.o有func函数的地址,但是没有add的地址,因为add没有实例化 ,没办法确定T


就像是你差一点就可以交首付了,你打电话给你哥们借钱,你哥们说没问题,就像声明一样,这是一种承诺,所以在编译阶段 add就可以过了 即call add( ? )
?代表现在没有地址 等到链接阶段才能拿到地址 ,而现在只是说得到承诺,到时候可以去拿到地址啦 但是 当你要交首付的时候,你哥们突然说借不了钱了,那这个房子首付也就交不上了 就好比 链接时 找不到add的函数地址了,所以会链接错误 链接之前不会交互,各走各的,不知道不能实例化,因为没办法确定T

解决链接错误的问题

显示实例化

【C++】模板进阶_Data_14

虽然设置成double可以解决double的问题,但是传int 等又会报错,所以还是有很大的缺陷 局限性,实际中一般不使用

将声明和定义放在一个文件中

【C++】模板进阶_Less_15

此时在预处理阶段将头文件展开,由于声明和定义都在一起,所以此时add函数 call时有地址存在,不需要向func一样,链接时去寻找函数地址了声明和定义放在一起,直接就可以实例化,编译时就有地址,不需要链接

4. 模板总结

缺陷

1. 模板会导致代码膨胀问题,也会导致编译时间变长模板会进行实例化,所以运行时间变长,因为是一个模板,若传过来int 、char、double 类型都要实现,所以代码会膨胀

2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误通常为一报错就一大堆错误,一般只需要找到第一个错误并解决它,就可以了