c++模板初阶 逃离我推掉我的手 2024-04-01 17:47 103阅读 0赞 ### 前言 ### 在我们学习c语言中,我们发现很多逻辑一样但函数的数据类型不一样,我们都需重新写,这样就有点代码冗余了。当来到了c++就可以很好的解决这一问题,运用模板。这个模板其实就像一个模具,比如中秋吃月饼,我们手工做的话,一个两个就直接做了,可是做很多个我们就用一个月饼的模具,里面我们可以加不同的馅,想吃什么加什么。 ![890d3edeec2d4a9bb730cd97416b263d.png][] -------------------- **目录** 前言: 泛型编程 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 模板参数的匹配原则 类模板 类模板的定义格式 类模板的实例化 -------------------- ### **泛型编程** ### 当我们有多种类型时,如何实现一个通用的交换函数呢? 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; } ...... 使用**函数重载**虽然可以实现,但是有一下**几个不好**的地方: 1. 重载的函数仅仅是类型不同,**代码复用率比较低**,只要有新类型出现时,就需要用户自己增加对应的函数 2. **代码的可维护性比较低**,一个出错可能所有的重载均出错 那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢? **泛型编程** 编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。 ![f23b0db08a904cd09daf8ebcfa442387.png][] ### 函数模板 ### #### **函数模板概念** #### 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本。 #### 函数模板格式 #### > template <typenameT1, typename T2,......,typename Tn> > > 返回值类型 函数名(参数列表)\{ > > \} template<typename T> void Swap( T& left, T& right) { T temp = left; left = right; right = temp; } **注意:**typename是用来定义模板参数**关键字**,也可以使用class(切记:不能使用struct代替class) #### 函数模板的原理 #### 为什么会出现函数模板呢? 其实就是因为人们为了更方便,也可以说因为人懒。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。 函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模 板就是将本来应该我们做的重复的事情交给了编译器 #include <iostream> using namespace std; template <typename T> void Swap(T&a, T& b) { T temp = a; a = b; b = temp; } int main() { double d1 = 2.0; double d2 = 3.0; cout << d1<< ' ' << d2 << endl; Swap(d1, d2); cout << d1 << ' ' << d2 << endl; int i1 = 20; int i2 = 30; cout << i1 << ' ' << i2 << endl; Swap(i1, i2); cout << i1 << ' ' << i2 << endl; char ch1 = '1'; char ch2 = '2'; cout << ch1 << ' ' << ch2 << endl; Swap(ch1, ch2); cout << ch1 << ' ' << ch2 << endl; } ![64761a3eced84b289bc7d2d5aae06784.png][] ![6c121ab804ac4ec98e96cf86ac04063b.png][] **在编译器编译阶段**,对于模板函数的使用,**编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用**。 比如:**当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码**,对于字符类型也是如此。 #### **函数模板的实例化** #### **用不同类型的参数使用函数模板时**,称为函数模板的**实例化**。模板参数实例化分为:**隐式实例化和显式实例化**。 **隐式实例化:**让编译器根据实参推演模板参数的实际类型 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; int ret1 = Add(a1, a2); double ret2 = Add(d1, d2); cout << ret1 << endl; cout << ret2 << endl; /* 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅*/ //Add(a1, d1);//error // 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化 int ret3 =Add(a1, (int)d1);//强制制转化 cout << ret3 << endl; return 0; } **显式实例化:**在函数名后的<>中指定模板参数的实际类型 template<class T> T Add(const T& left, const T& right) { return left + right; } int main(void) { int a = 10; double b = 20.0; //显式实例化 int ret = Add<int>(a, b); cout << ret << endl; return 0; } **注意:**如果**类型不匹配**,编译器会尝**试进行隐式类型转换**,如果无法转换成功编译器将会报错。 #### **模板参数的匹配原则** #### 1.一个**非模板函数**可以和一个**同名的函数模板同时存在**,而且该**函数模板**还可以**被实例化为这个非模板函数** // 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } void Test() { int ret1 = Add(1, 2); // 与非模板函数匹配,编译器不需要特化 int ret2 = Add<int>(1, 2); // 调用编译器特化的Add版本 cout << ret1 << ' ' << ret2 << endl; } int main() { Test(); return 0; } 2. 对于**非模板函数**和**同名函数模板**,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板 // 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } void Test() { int ret1=Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化 double ret2=Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 cout << ret1 << ' ' << ret2 << endl; } int main() { Test(); return 0; } 3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换 // 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } void Test() { int ret1 = Add(1, 2.0); //普通函数可以进行自动类型转换(隐式转化)。 double ret2=Add(1, 2.0); // 模板函数不允许自动类型转换,这里是T1是int类型,T2是double类型 cout << ret1 << ' ' << ret2 << endl; } int main() { Test(); return 0; } ![bb0ccdaf72e746119725932f5d51b31a.png][] ### 类模板 ### #### 类模板的定义格式 #### > templateclass T1, class T2, ..., class Tn> > > class 类模板名 > > \{ > > // 类内成员定义 > > \}; // 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具 template<class T> class Vector { public: Vector(size_t capacity = 10) : _pData(new T[capacity]) , _size(0) , _capacity(capacity) {} // 使用析构函数演示:在类中声明,在类外定义。 ~Vector(); void PushBack(const T& data); void PopBack(); // ... size_t Size() { return _size; } T& operator[](size_t pos) { assert(pos < _size); return _pData[pos]; } private: T* _pData; size_t _size; size_t _capacity; }; // 注意:类模板中函数放在类外进行定义时,需要加模板参数列表 template <class T> Vector<T>::~Vector() { if (_pData) delete[] _pData; _size = _capacity = 0; } #### 类模板的实例化 #### 类模板实例化与函数模板实例化不同,**类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。** > // Vector类名,Vector< >才是类型 > > Vector<int> s1; > > Vector<double> s2; #### 类模板未被实例化--Link(error) #### **代码:** Stack.h #pragma once #include <iostream> using namespace std; template <typename T> class Stack { public: Stack(int capacity = 4); ~Stack(); void Push(const T& x); private: T* _a; int _Top; int _capacity; }; Stack.cpp #include "Stack.h" template<class T> Stack<T>::Stack(int capacity = 4) { cout << "Stack(int capacity = )" << capacity << endl; _a = (T*)malloc(sizeof(T)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = capacity; } template<class T> Stack<T>::~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } template<class T> void Stack<T>::Push(const T& x) { // .... // _a[_top++] = x; } test.cpp #define _CRT_SECURE_NO_WARNINGS #include "Stack.h" int main() { Stack<int> st; st.Push(1); st.Push(2); Stack<double> stt; stt.Push(1.1); stt.Push(2.2); return 0; } **当代码运行后这里会发生链接错误!** ![0e52ed61d5fa4cb7b96eccee60459af5.png][] ![920b24e1da104317a67e947e09abd26d.png][] **解释图:** ![e8395f9598274b3ba812071bbc18831d.png][] 如何解决呢?其实也很好解决,我们把声明和定义放在一起就好了。 **方法1:** **显示实例化**\--在Stack.cpp中的代码加入 > **template > class Stack<int>;** 但是方法1是不采取的,因为换个实例化又需要重新写 **方法2:** **不分离定义到两个文件中--**将Stack.cpp文件的代码写入Stack.h中 #pragma once #include<iostream> using namespace std; template<typename T> class Stack { public: Stack(int capacity = 4); ~Stack(); void Push(const T& x); private: T* _a; int _top; int _capacity; }; template<class T> Stack<T>::Stack(int capacity = 4) { cout << "Stack(int capacity = )" << capacity << endl; _a = (T*)malloc(sizeof(T)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = capacity; } template<class T> Stack<T>::~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } template<class T> void Stack<T>::Push(const T& x) { // .... // _a[_top++] = x; } ![82bbc66e83414018bb477d5f4a65c0c7.gif][] [890d3edeec2d4a9bb730cd97416b263d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/ad985bdf71ee46fbb3039c0cb0deff56.png [f23b0db08a904cd09daf8ebcfa442387.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/b0f3eacb1f2849908113ba69533af0d7.png [64761a3eced84b289bc7d2d5aae06784.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/b639efa780c348b5a10d997165d7256d.png [6c121ab804ac4ec98e96cf86ac04063b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/44c16c2d557040ebbbdb0b51a53e50ea.png [bb0ccdaf72e746119725932f5d51b31a.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/233e6fe0aebd4d14ac9e0d2c81baaf7f.png [0e52ed61d5fa4cb7b96eccee60459af5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/6044c806484548c2ac3ae3d4bc6081bc.png [920b24e1da104317a67e947e09abd26d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/2d7391e319be46b391e210c2b0b95712.png [e8395f9598274b3ba812071bbc18831d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/e69f0aaae57b45249d44c0b13fd577de.png [82bbc66e83414018bb477d5f4a65c0c7.gif]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/01/b6a270e19436493aa108c6dbc65396ba.gif
相关 【C++初阶】想要编译器为你干活吗?来试试模板吧(模板初阶) 【C++初阶】想要编译器为你干活吗?来试试模板吧(模板初阶) 妖狐艹你老母/ 2024年04月22日 12:10/ 0 赞/ 81 阅读
相关 c++模板初阶 前言 在我们学习c语言中,我们发现很多逻辑一样但函数的数据类型不一样,我们都需重新写,这样就有点代码冗余了。当来到了c++就可以很好的解决这一问题,运用模板。这个模板其实 逃离我推掉我的手/ 2024年04月01日 17:47/ 0 赞/ 104 阅读
相关 【C++初阶】函数模板与类模板 文章目录 引言.泛型编程 一.函数模板 1.基本使用 2.拔高训练 2-1自动推演实例化和显式实例化 忘是亡心i/ 2024年04月01日 15:11/ 0 赞/ 91 阅读
相关 【C++初阶】:模板初阶 模板初阶 一.函数模板 1.简单使用 2.模板原理 3.函数模板的实例化 4.模板参数的匹配原则 二.类 短命女/ 2024年03月22日 19:12/ 0 赞/ 102 阅读
相关 【C++初阶】:模板进阶 模板进阶 一.非类型模板参数 二.模板的特化 1.概念 2.函数模板特化 3.类的特化 1.全特化 雨点打透心脏的1/2处/ 2024年03月18日 00:20/ 0 赞/ 121 阅读
相关 【C++精华铺】8.C++模板初阶 目录 1. 泛型编程 2. 函数模板 2.1 函数模板的概念及格式 2.2 函数模板的原理 2.3 模板的实例化 2.4 模板参数的匹配原则 3. 类模板 た 入场券/ 2023年10月14日 21:26/ 0 赞/ 24 阅读
相关 模板初阶 一、泛型编程 1、函数重载的缺点: (1)重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数 (2)代码的可维护性比较低,一个 拼搏现实的明天。/ 2022年03月21日 04:30/ 0 赞/ 228 阅读
相关 【C++】模板初阶 文章目录 一、泛型编程 二、函数模板 1.函数模板概念 2.函数模板格式 3.函数模板的实例化 三、类模板 本是古典 何须时尚/ 2021年09月09日 03:40/ 0 赞/ 375 阅读
还没有评论,来说两句吧...