【C++ 系列笔记】04 C++ 泛型编程
泛型编程
模板是泛型的实现,泛型是模板的思想。
泛型的本质是类型的参数化。
函数模板
函数模板
-
基本语法
template<class T> void function(T param1, T param2){ // ... }
template<typename T> void function(T param1, T param2){ // ... }
<class T>
和<typename T>
没有区别。注意!
template<class/typename T>
声明仅对紧跟着的代码块有效。 -
调用方式
-
自动类型推导
int a; int b; function(a, b); // 没问题
int a; char b; function(a, b); // 报错
-
显示指定类型
function<int>(a, b);
注意!不传参时必须显式指定类型,看下面的例子。
template<class T> void function(); int main(){ function();// 此时编译器无法自动推导,必须显式指定 }
-
-
函数模板与普通函数的区别
-
类型检查
模板函数类型检查更严格,不允许隐式转换,例子:
template<class T> T templateFun(T param1, T param2); int function2(int param1, int param2); int main(){ int a; char b; // 调用模板函数不允许隐式转换 templateFun(a, c); // 报错 // 调用普通函数会发生隐式转换 function2(a, c); // 没问题 }
不过,右值传参是允许隐式转换的,例如:
templateFun(1, 2); // 这是合法的
-
-
调用顺序
重载时,优先调用普通函数
template<class T> T function(T param1, T param2); int function(int param1, int param2); int main(){ int a; int b; function(a, b); // 报错 }
如果想要强制调用模板函数,可以显式指定空模板参数
function<>(a, b);
如果模板函数拥有更好的参数匹配,则会调用模板函数
template<class T> T function(T param1, T param2); int function(int param1, int param2); int main(){ char a; char b; // 模板函数拥有更少的隐式转换,所以这里会匹配模板函数 function(a, b); }
-
模板函数允许重载
template<class T> T function(T param1, T param2); template<class T> T function(T param1, T param2, T param3);
模板实现机制
-
函数模板通过具体类型产生不同的函数
-
二次编译:
首先检测函数模板的语法错误并编译。然后生成对应的模板函数,再次编译。
模板具体化
-
局限性
函数模板具有一定的局限性,
例如算数运算:
对于数组,不存在 arr1 + arr2 这种语法。
若是对象 obj1 + obj2,则需要提供相应的重载函数。
-
解决
函数模板的具体化:
template<class T> void function(T a){ // ... 模板实现 } // 具体化 template<> void function<Type>(Type& a){ // ... 该模板函数的具体实现 }
即针对某些特定类型进行重载。
类模板
-
基本语法
template<class type1, class type2> class Type { private: type1 __a; type2 __b; public: Type(type1 a, type2 b); }
注意!与函数模板不同,实例化对象时必须显式指定数据类型。
Type<int, char> obj(1, '2');
-
默认参数
类模板可以使用类型的默认参数。
template<class type1, class type2 = char>
当然,函数模板也可以使用默认参数,只不过没什么用。
-
其余与函数模板基本相同
类模板的类外实现
代码:
template<class type1, class type2>
class Type {
private:
type1 __a;
type2 __b;
public:
Type(type1 a, type2 b);
void method(type1 a);
};
// 类外实现
template<class type1, class type2>
Type<type1, type2>::Type(type1 a, type2 b){
// ...
}
template<class type1, class type2>
void Type<type1, type2>::method(type1 a){
// ...
}
类模板的分文件编写
首先要了解一点,类模板的方法函数在编译阶段不会生成,他在运行时生成。
我们调库的一般方式是这样的:
#include"Type.h"
int main(){
Type obj;
obj.method();
}
会报错,无法解析的外部命令
,即在链接阶段找不到对应的函数去调用。
因为此时还未创建具体的类模板方法函数,当然找不到。
此时可以这样做:
#include"Type.cpp"
int main(){
Type obj;
obj.method();
}
不过,正常情况下,我们一般对类模板不做分文件编写,将声明和实现放在一个 .h
文件中。
类模板实例做函数参数
模板实例内部的数据类型不确定,想作为参数传递,可以在参数列表里直接指定,或作为一个函数模板去定义。
-
指定类型
指定只允许传入此参数类型的实例:
void function(Type<int, string> obj);
-
模板函数
允许此模板类的任何实例传入:
template<class T1, class T2> void function(Type<T1, T2> obj);
-
完全抽象
其实这个方法不用说也都知道:
template<class T> void function(T obj);
类模板与友元
-
类内实现
注意!虽然是类内实现,但其作用域仍在全局。
template<class type1, class type2> class Type { friend void function(Type<type1, type2>& obj){ // ... } private: type1 __a; type2 __b; };
-
类外实现
首先看错误实现:
template<class type1, class type2> class Type { friend void function(Type<type1, type2>& obj); private: type1 __a; type2 __b; }; template<class type1, class type2> void function(Type<type1, type2>& obj){ // ... }
此时若调用该函数,则报错
无法解析的命令
,即没有找到该函数的实现。由于类内声明是一个普通函数,而类外实现是一个函数模板,而当参数列表相同时,编译器会优先调用普通函数,从而导致了找不到函数实现,链接出错。
两种方法:
-
声明模板函数
此时将类内的友元声明声明为一个模板函数即可。
template<class type1, class type2> class Type { template<class type1, class type2> friend void function(Type<type1, type2>& obj); private: type1 __a; type2 __b; }; template<class type1, class type2> void function(Type<type1, type2>& obj){ // ... }
-
函数模板具体化
或者先声明该模板函数,然后类内声明友元,并进行参数的具体化。
// 声明 template<class type1, class type2> class Type<type1, type2> template<class type1, class type2> void function(Type<type1, type2>& obj); template<class type1, class type2> class Type { // 参数具体化声明 friend void function<>(Type<type1, type2>& obj); private: type1 __a; type2 __b; }; // 类外具体化实现 template<class type1, class type2> void function(Type<type1, type2>& obj){ // ... }
-
模板中的继承
错误示例:
不允许继承抽象的类
template <class T>
class Base {};
// 继承
class Type : public Base {};
要继承,可以抽象,也可以具体。
-
具体制定要继承的模板类
template <class T> class Base {}; // 继承 class Type : public Base<int> {};
-
抽象指定
template <class T> class Base {}; // 继承 template <class T> class Type : public Base<T> {};