模板的基本概念
什么是模板
模板是C++支持参数化多态的工具,使用模板可以使用户为类或函数声明一种模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
- 模板是C++的一种特性,允许函数或类通过泛型的形式表现或运行。
- 模板可以使得函数或类在对应不同的型别的时候正常工作,而无需为每一份代码都写一份代码。
- 模板是一种泛型编程的机制,也是一种代码复用的手段。
- 模板是通用语言的特性,模板又叫参数化类型。
模板的作用
实现C++的泛型机制,摆脱对类型的依赖,实现通用性
模板的类型
函数模板
函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),**等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。函数模板中定义的类型参数可以用在函数声明和函数定义
类模板
C++ 除了支持函数模板,还支持类模板(Class Template)。类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
模板实例化和实参推演
在调用点,通过具体的类型替换虚假的类型,整个替换过程就是实例化过程。
编译器调用模板函数时,编译器会根据实参的类型,推演出模板的类型,并再生产相应的代码,称为模板的实参推演
A<int> a; //类模板只能显式实例化,没法推演
fun<int>(1,2); //函数模板的显式实例化,不需要推演
fun(1,2) //函数模板的隐式实例化,需要进行推演
在实参推演时必须注意:不能产生二义性,必须有实参
类模板的实例化
- 不允许实参推演
- 类模板的实例化是选择实例化(使用哪个函数,才会去实例化哪个函数)
模板类型参数
类型参数由关键字class或typename后接说明符构成
template<class T>
void fun(T a){}
T就是一个类型形参,类型形参的名字用户可自定义。
template<class T>
void fun(T a, T b){},
// fun(12,56.2) == > error
语句调用fun(12,56.2)将出错,因为该语句给同一模板形参T指定了两种类型,第一个实参2把模板形参T推演成为int,而第二个实参3.2把模板形参推演为double,两种类型的形参不一致,编译器会产生二义性,会出错。
模板非类型参数
即就是内置类型参数
template<class T, int a>
class B{};
- 必须是常量
- 不能是浮点类型和类类型。
sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参
模板的编译
分为两个阶段:
- 定义点只编译模板的头部
- 在调用点编译实例化后的模板函数(针对函数模板来说)
模板特例化
针对特殊的类型,模板无法满足特殊需求时,我们就可以提供模板特例化版本:特殊的实例化
//无法满足需求时
template<typename T>
bool Compare(const T a,const T b) const 修饰 a,b
{
cout<<"template<typename T>Compare(T,T)"<<endl;
return a>b;
}
//实现特例化版本
template<>
bool Compare<char *>(const char* a,const char* b) const 修饰*a *b
{
cout<<"template<char*>Compare(char*,char*)"<<endl;
return strcmp(a,b)>0;
}
特例化又称为专用化
完全特例化(全特化)
某个版本对一个类型不能满足需求时,就要做完全特例化
C++ 98 函数模板只支持完全特例化
部分特例化(偏特化)
某个版本对一部分类型无法满足需求时,针对一部分特殊处理就是部分特例化
模板重载
指普通函数版本、模板版本、模板特例化版本可以共存
- 普通函数版本 优先
- 模板特例化版本 次优先
- 模板版本 最后
要求类型必须完全匹配
bool Compare(char *a,char *b)
{
return strcmp(a,b)>0;
}
template<typename T>
bool Compare(T a,T b)
{
cout<<"template<typename T>Compare(T,T)"<<endl;
return a>b;
}
template<>
bool Compare<char *>(char* a,char* b)
{
cout<<"template<char*>Compare(char*,char*)"<<endl;
return strcmp(a,b)>0;
}
int main()
{
Compare("hello","world");//模板实例化
//针对这个字符串 是常量 当模板实参推演时 const char* 普通函数不符合 模板特例化不符合 然后就是模板实例化
/*
}
1.为什么我们要把模板的整个定义写在.h文件中?
普通函数(声明写在.h,定义实现写在源文件)
在函数包含中不会太大,并且只需要编译一次,甚至可以通过共享库加载。
如果模板定义在一个源文件,而另一个源文件调用这个模板,就会出错。
因为模板是编译时期时在定义点只编译头部,而这个源文件没有调用点,也就意味着没有函数符号的生成,当其他源文件调用时,通过模板实例化生成了外部符号放在了UND未定义区,但是在链接阶段进行符号解析时,在符号表中却找不到相应的符号,就会出错。
而将模板的定义写在.h,预编译阶段会直接展开.h,在调用时就会有模板的定义。
2.typename和class的区别
在模板类型参数列表中等价,在模板之外,typename可以声明模板中的类型,class作为类标识。