模板简单介绍
模板是C++基于C语言的
优点:
1.复用代码,更快的迭代开发,让脏活累活由编译器来完成
2.模板让我们的代码更加的灵活
缺点:
1.看起来节省了代码。但是编译后还是存在代码膨胀的问题
2.模板出现错误之后,有时报错混乱且不准确。有时不容易定位错误位置
3.模板不支持分离编译
模板的全特化
小测试:比较a, b变量的大小
//1.编写一个模板函数
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
//使用上述模板函数进行测试
int b = 20;
int a = 10;
cout << Less(a, b) << endl;
cout << Less(&a, &b) << endl;
return 0;
}
//查看运行结果
[clx@VM-20-6-centos inherit_C++]$ ./test
1
0
结论:我们发现因为栈是由上向下增长的,所以a的地址大于b的地址。我们的本意是比较a,b变量的大小,但是第二次比较显然并没达到目的,所以我们需要对特殊的例子进行特化处理,使函数达到目的
解决方案:
方法一:直接构造特例函数
bool Less(int* left, int* right)
{
return *left < *right;
}
//因为编译器遇到函数首先去找是否存在已经匹配的函数,所以直接写一个现成的,编译器就不会根据模板自动生成
方法二:函数模板特化
template<>
bool Less<int*>(int* left, int* right)
{
return *left < *right;
}
当模板的所有参数都已经规定好了,这样的特化叫做全特化。若部分确定,则为偏特化
模板的偏特化
普通偏特化
template<class T1, class T2> //Data类有两个模板参数
class Data
{
public:
Data()
{
cout << "Data<T1, T2>" << endl;
}
private:
T1 _d1;
T2 _d2;
};
template<class T>
class Data<T, char> //Data类的偏特化,只设置一个模板参数
{
public:
Data()
{
cout << "Data<T, char>" << endl;
}
private:
T _d1;
char _d2;
};
int main()
{
Data<int, char> d1; //实例化时若第二个参数为char则调用偏特化模板
Data<int, int> d2;
return 0;
}
[clx@VM-20-6-centos inherit_C++]$ ./test
Data<T, char>
Data<T1, T2>
特殊偏特化
偏特化的模板参数数量并非一定小于模板的参数数量
template<class T1, class T2>
class Data<T1*, T2*> //若参数类型是指针的话就会调用此模板
{
public:
Data()
{
cout << "Data<T1*, T2*>" << endl;
}
private:
T1* _d1;
T1* _d2;
};
int main()
{
Data<int*, char*> d;
return 0;
}
[clx@VM-20-6-centos inherit_C++]$ ./test
Data<T1*, T2*>
此处模板参数使用的是指针, 当然模板参数如果是引用也可以识别
小结:特化是对参数的进一步限制
非类型模板参数特化
template<size_t N>
class Te
{
public:
Te()
{
cout << "Te<size_t N>" << endl;
}
};
template<>
class Te<1000>
{
public:
Te()
{
cout << "Te<1000>" << endl;
}
};
int main()
{
Te<1> t1;
Te<1000> t2;
return 0;
}
[clx@VM-20-6-centos inherit_C++]$ ./test
Te<size_t N>
Te<1000>
模板的分离编译
模板不支持分离编译,即使用.h和.cpp分别声明和定义模,模板声明定义合在一个文件中
分离编译的价值:便于维护,.h文件便于了解框架设计和基本功能, .cpp 了解具体实现细节
为什么模板不支持分离编译呢?这得从可执行程序生成的过程来解释
首先我们先了解一下一个可执行程序生成的几个步骤,并且此程序由pro.cpp, pro.h, test.cpp组成
1.预处理:头文件展开/宏替换/删除注释/条件编译
2.编译: 检查语法错误, 没有问题生成汇编代码
3.汇编: 将汇编转化成机器码
4.链接: 将函数地址填入
普通函数或类:
编译阶段test.cpp文件中包含函数声明和调用,声明告诉编译器存在此函数,请链接的时候去找。pro.cpp文件中包含函数的定义和声明,编译器将函数定义转换成汇编代码,并且生成一个函数表存储此文件中函数的地址(各函数生成第一条汇编代码的地址)。
在链接阶段,test.cpp生成的汇编代码从pro.cpp生成的函数表中获取函数地址,填入自己的代码当中,这样就生成了一份可执行程序
模板函数或类:
编译阶段test.cpp文件中包含模板实例化的类型信息以及模板声明。但是因为并没有模板的定义,所以编译器并不知道应该怎么样实例化函数模板,但是因为拥有声明,编译器认为函数体在其他地方,链接时去找。在pro.cpp文件中,有函数的声明和定义,但是其并不知道模板的实例化类型信息,无法推测类型信息也就无法生成正确的函数汇编代码和函数表。
在链接阶段,test.cpp文件去找pro.cpp文件函数表中查找地址,但pro.cpp没有实例化信息无法生成函数,更无法生成函数地址。所以就会产生链接错误
如何解决:
方法一:当模板的声明定义在同一个文件中 pro.hpp test.cpp
因为test.cpp 中引用了pro.hpp作为头文件,在预处理时展开。这样这个文件中就有了函数或类的定义,声明,实例化信息。编译器就可以通过实例化信息来实例化函数或类,实例化的函数就可以生成正确的汇编代码和地址
方法二:显示实例化(有缺陷)
template
void Print<double>(const int& m){};
缺陷:用一个就得显示实例化一个,失去了模板的价值,非常麻烦不,推荐使用