【C++】泛型编程 _函数模板和类模板的基本使用 谁践踏了优雅 2024-03-30 10:18 11阅读 0赞 ## 1.泛型编程 ## 如何实现一个通用的交换函数?这在C语言中是无法实现的,但是C++却可以。 C语言一次只能实现一个类型的交换: void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } void Swap(char& left, char& right) { char temp = left; left = right; right = temp; } 使用函数重载虽然可以实现,但是有一些问题:只要交换的类型不同,就需要增加对应的函数;重载的函数只是类型不同,代码复用率比较低;代码可维护性较低,一个函数出错所有重载都会出错。 解决这些问题的简单方法就是使用泛型编程。 **泛型编程:编写与类型无关的通用代码,从而实现代码的复用。** 像古代的活字印刷术,只要有了一个模板,就可以反复的造轮子; ![在这里插入图片描述][80e5ad3f151644438f041606d5040868.png] C++中的模板可以分为函数模板和类模板。 ## 2.函数模板 ## ### 2.1 函数模板概念 ### 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。 ### 2.2函数模板格式 ### template<typename T1, typename T2,......,typename Tn> 返回值类型 函数名(参数列表){ } 由于函数模板的模板参数类型可以有多个,所以这里使用T1, T2 … Tn来标识。 typename后面的类型名T1,T2……是随便取的,但是要尽量做到见名知意,一般首字母大写。 使用函数模板定义一个通用的交换函数: template<typename T> void Swap( T& left, T& right) { T temp = left; left = right; right = temp; } **注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)。** ### 2.3 函数模板的原理 ### ![在这里插入图片描述][a349ebbb17154ebabcad99953b6ce18a.png] 下面从汇编语言看一下,当不同类型的变量调用Swap函数的时候调用的是不是同一份: ![在这里插入图片描述][1e589d0f9efb4a2cbe5732f97456716f.png] 不同的类型编译器会推演出不同的函数,调用的也就是不同的函数。 交换函数使用的频率还是很高的,C++库(在std命名空间中)中有一个交换函数swap是可以直接使用的。 ### 2.4 函数模板的实例化 ### 用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例 化。 **1.隐式实例化:让编译器根据实参推演模板参数的实际类型。** template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a1 = 10, a2 = 20; double d1 = 10.0, d2 = 20.0; Add(a1, a2); Add(d1, d2); //Add(a1, d1); 编译报错 return 0; } 在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。 **2. 显式实例化:在函数名后的<>中指定模板参数的实际类型。** int main() { int a = 10; double b = 20.0; Add<int>(a, b); return 0; } ### 2.5 模板参数的匹配原则 ### 模板函数使得我们的代码更简洁,但是也有着匹配规则,下面看这样一段代码: int Add(int left, int right) { return left + right; } template<class T> T Add(T left, T right) { return left + right; } template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } 两个函数模板同时出现,还有一个处理int类型的函数,下面看一下调用规则: Add(1, 2); // 与非模板函数匹配,编译器不需要实例化模板,直接调用 Add<int>(1, 2); // 显示实例化 Add(1, 2.0);//需要实例化两个类型参数的模板 Add(1.0, 2.0); //优先实例化一个类型参数的模板 ## 3.类模板 ## 相比于函数模板,类模板并没有特殊的地方,只是**类模板只能显示实例化**。 下面以一个栈(Stack)为例: typedef int STDataType; class Stack { private: STDataType* _a; int top; int capacity; }; 数据类型是使用typedef进行重命名的,那么要想同时存储两种数据类型,还需要写一份完全一样的代码,只是类型不同;这样的话就很麻烦,所以使用到了类模板。 template<class T> class Stack { private: T* _a; int top; int capacity; }; 这样编译器就可以根据不同的类型进行显示实例化。 int main() { Stack<int> s1; Stack<char> s2; return 0; } [80e5ad3f151644438f041606d5040868.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/90573fc71089428fb8d5a4f610b0186f.png [a349ebbb17154ebabcad99953b6ce18a.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/5ad00845282a43979216c2a5b5cb239a.png [1e589d0f9efb4a2cbe5732f97456716f.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/03/30/10e1dcd8488c45b2b0689094da5088fe.png
还没有评论,来说两句吧...