一.非类型模板参数
模板参数分为类型形参与非类型形参。
类型形参:类作为模板参数,typename/class T (T就是类型形参)
非类型形参:内置类型作为模板参数,int double char ...(在C++20前只有 int 可以传)
这样我们就可以随便定义栈的大小。
注:因为n是常量所以是不能修改的。
(补充:array容器)
用array创建的数组和普通定义的数组有什么优势呢?
我们知道普通数组的越界检查是不完全的,越界读取数据是不报错的。
而array创建的对象数组越界检查更严格。
但array对比vector,array是直接在栈上建数组,而vector是在堆上建。(栈的空间比堆小,需要存入大量数据建议用vector)
typename标识模板中的类型
这里为什么需要加上typename?
为了帮助编译器知道iterator是一个类型而不是变量或者其他东西。
vetcor<T> 因为类模板没有经历实例化,vector类里面没有进行检查可能会有错误,所以编译器不会进入类里面去找iterator,就不能确定iterator是一个类型还是静态成员变量等等。
二.模板的特化
1.函数模板特化
**函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。**
template<class T>
bool Less(T left, T right)
{
return left < right;
}
//函数模板特化(对Date*类型特殊处理)
template<>
bool Less<Date*>(Date* left, Date* right)
{
return left < right;
}
函数模板很容易出错,看下面代码为什么出错呢?
因为模板const修饰的是传过来的形参,
所以特化的模板const也应该修饰形参,而特化模板形参是指针,const Date*& left修饰的是指针指向的内容。Date* const& left 才是修饰的形参指针本身。
所以不建议用函数模板特化,需要特殊处理建议直接写函数重载(与模板实例化的函数)
2.类模板特化
全特化
全特化即是将模板参数列表中所有的参数都确定化
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
//对int,char类型特殊处理
template<>
class Data<int, char>
{
public:
Data() { cout << "Data<int, char>" << endl; }
private:
int _d1;
char _d2;
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2;
}
int main()
{
TestVector();
}
偏特化
偏特化分为两种
1.部分特化
2.对参数进行限制 (eg.必须为指针类型,&引用类型)
1.部分特化
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 将第二个参数特化为int
//只要第二个参数为int 就走这个偏特化<int,int>也会走
template <class T1>
class Data<T1, int>
{
public:
Data() { cout << "Data<T1, int>" << endl; }
private:
T1 _d1;
int _d2;
};
2.对参数进行限制
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>-原模板" << endl; }
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout << "Data<T1&, T2&>" << endl;
}
private:
const T1& _d1;
const T2& _d2;
};
void test2()
{
Data<double, int> d1;
// 调用特化的int版本
Data<int, double> d2;
// 调用基础的模板
Data<int*, int*> d3;
// 调用特化的指针版本
Data<int&, int&> d4(1, 2);
// 调用特化的指针版本
}
如果为指针类型,就会进入指针偏特化模板。
那么模板中的T1,T2是就是指针呢?
可以看到,T1double T2int ,如果为指针类型应该都为8.
三.模板分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
编译运行后为什么找不到我们写的函数模板Add呢?
简单来说,函数模板没有实例化,没有函数地址,没有存入符号表,所以链接不到。
在test.cpp中普通函数会被编译生成函数地址存入符号表,而模板函数不知道实例化成什么,就不会被编译生成地址。
main.cpp中调用Add函数虽然知道实例化成什么,但只有声明没有定义。
解决办法:
最好的方法就是声明和定义放在同一个文件上。还有就是在定义文件中显示实例化。(不建议用因为要事先自己写入实例化的类型,太麻烦)
函数声明定义都放在同一个文件,还需要写声明吗?
虽然声明不写也可以,但在类模板中,短的函数直接写定义,默认inline。长的函数建议声明写在类里面,定义在类外(仍在同一个文件)。
模板:优点:
模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
增强了代码的灵活性
缺陷:
模板会导致代码膨胀问题,也会导致编译时间变长
出现模板编译错误时,错误信息非常凌乱,不易定位错误