一. 泛型编程
函数重载可以实现不同类型的交换函数——
void Swap(int& x1, int& x2) { int tmp = x1; x1 = x2; x2 = tmp; } void Swap(double& x1, double& x2) { int tmp = x1; x1 = x2; x2 = tmp; } int main() { int i1 = 10, i2 = 20; double d1 = 1.1, d2 = 2.2; Swap(i1, i2); Swap(d1, d2); return 0; }
但是有两个不好的地方:
代码的逻辑仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
🔥泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础
类比我们的活字印刷术
二. 函数模板
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
🌍函数模板格式
template后面类型名字T是随便取,Ty、K、V、一般是大写字母或者单词首字母大写
T 代表是一个模板类型(虚拟类型)
template<typename T1, typename T2,......,typename Tn>//模板参数(模板类型) swap(参数列表) //类似函数参数(对象) { //..... }
typename是用来定义模板参数关键字,也可以使用class(不能使用struct代替class)
//template<class T> template<typename T> void Swap(T& left, T& right) { T temp = left;//这里最好不用异或,异或只使用整形int,那其他类型?? left = right; right = temp; }
🌍函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器根据传入的实参类型来推演生成对应类型的函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器(感慨,懒人创造世界啊)
转到反汇编,我们可以看到——调用的函数地址都不一样
注意这个和auto不一样
🌍函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化
1.🌊 隐式实例化:让编译器根据实参推演模板参数的实际类型
上述代码都是隐式实例化
看如下代码,该语句不能通过编译。因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a将T推演为int,通过实参d将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错
ps:模板函数不支持隐式类型转化
此时有两种处理方式:① 强制类型转换 ② 显式实例化
template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { //强制类型转化 ———— 对自己先处理 cout << Add((int)1, 2) << endl; cout << Add(1.1,(double) 2) << endl; }
🔥显示实例化:在函数名后的<>中指定模板参数的实际类型
#include<iostream> using namespace std; template<class T> T Add(const T& left, const T& right) { return left + right; } int main() { int a = 1; double d = 2.2; // 显式实例化 ————告诉编译器,指定了 cout << Add<int>(a, d) << endl; cout << Add<double>(a, d) << endl; return 0; }
🌍函数模板的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在 ,如果其他条件都相同,在调动时会优先调用普通函数而不会从该模板产生出一个实例
如果模板可以产生一个具有更好匹配的函数, 那么优先选择模板
💚:有现成的就用现成的,没有再套模板
ps:模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
三. 类模板
C语言没有数据结构,很大程度上是因为不支持泛型编程。虽然我们用typedef int DataType; 类型重命名,可以稍作改动来更改数据类型,但是如果在一个大型工程里,我们同时要用int栈和char栈,只能勉强把相同逻辑的代码写两遍。C++有了类模板,编译器可以根据被实例化的类型生成真正的类
🎨类模板的定义格式
我们以栈为例——
template<class T> class Stack { public: Stack(int capacity = 4) :_top(0) , _capacity(capacity) { _a = new T[_capacity]; } ~Stack() { delete[] _a; _a = nullptr; _top = _capacity = 0; } private: T* _a; int _top; int _capacity; };
🎨类模板的实例化
类模板实例化与函数模板实例化不同,函数模板可以指定模板参数类型,也可以不指定,编译器会根据实参进行推演。但是类模板实例化只支持显示实例化,因为没法推演
Stack<int> st1; // 存储int Stack<char> st2; // 存储char
注:这里Stack是类名,Stack<T>才是类型!
🍅:模板不支持分离编译,后续我们来谈 可以在当前文件声明和定义分离
//typedef int STDataType; template<typename T> class Stack { public: Stack(size_t capacity = 4) :_a(nullptr) ,_capacity(0) ,_top(0) { if (capacity > 0) { _a = new T[capacity]; _capacity = capacity; _top = top; } } ~Stack() { delete[] _a; _a = nullptr; _capacity = _top = 0; } void Push(const T& x); void Pop() { assert(_top > 0); --top; } private: T _a; size_t top; size_t capacity; }; template<class T> void Stack<T>::Push(const T& x) { if (_top == _capacity) { size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2; //1、开新空间 //2、拷贝数据 //3、释放旧空间 T* tmp = new T[newCapacity]; if (_a) { memcpy(tmp, _a, sizeof(T) * _top); delete[] _a; } _a = tmp; _capacity = newCapacity; } _a[_top] = _x; ++_top; } int main() { try { //类模板都是显示实例化 Stack<int> st1; //Stack<char> st1; st1.Push(1); st1.Push(2); st1.Push(3); st1.Push(4); } catch (const exception& e) { cout << e.what() << endl; } return 0; }
📢写在最后
能看到这里的都是棒棒哒🙌!
想必模板也算是C++中重要🔥的部分了,如果认真看完以上部分,肯定有所收获。
接下来我还会继续写关于📚《SLT》等…
💯如有错误可以尽管指出💯
🥇想学吗?我教你啊🥇
🎉🎉觉得博主写的还不错的可以`一键三连撒