1. 前言
在学习数据结构时会遇见以下的情况
数据结构中存储的类型往往不能确定
所以在实现数据结构时往往是这样做的
typedef int DateType
在写代码时用DateType来表示类型
如果想存储浮点型只需将int改为float
但是这样写会遇见一个问题:
写好数据结构类后在创建对象时
此.cpp文件只能创建一种类型的对象
对象存储的全是int/char/double类型
不能同时创建存储int的和char的对象
Data d1;//存储的int类型 Date d2;//存储的char类型
泛型编程:
编写与类型无关的通用代码
是代码复用的一种手段
模板是泛型编程的基础
本章重点:
本篇文章重点讲解函数模板
和类模板的使用以及特性
2. 函数模板
请看以下函数代码:
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; }
这样写非常的麻烦
使用模板可以使代码通用于不同类型:
swap函数模板:
template<typename T> void Swap( T& left, T& right) { T temp = left; left = right; right = temp; }
写好上面的代码后,传int类型进去
T就会被实例化为int,以此类推
template和typename是规定
好了必须这样写,T是自己取的名字
其中,typename可以用class替换
并且一次性可以定义多个类型:
template<class T1,typename T2,class T3>
3. 函数模板原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此
可以用下面这张图来理解:
4. 函数模板实例化
隐式实例化
让编译器根据实参推演模板参数实际类型
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); }
第一次调用的T被推演为int类型
第二粗调用的T被推演为double
不能这样写代码:
Add(a1, d1);
系统根据a1推演出T是int类型
但是d1是double类型不能用int
类型的参数啦接受,所以会报错
显示实例化
在函数名后的<>中指定模板参数的类型
int main(void) { int a = 10; double b = 20.0; // 显式实例化 Add<int>(a, b); return 0; }
如果类型不匹配
编译器会尝试进行隐式类型转换
若无法转换成功编译器将会报错
5. 函数模板参数的匹配规则
模板函数和普通函数可以同时存在:
// 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; }
在调用函数时若参数和非模板函数匹配
那么编译器会优先调用非模板函数
若非模板函数不匹配或模板函数更匹配
那么编译器会优先调用模板函数
Add(10,20)//调用非模板 Add(11.1,6.3);//调用模板
6. 类模板
类模板的应用非常广泛
像开头提到的数据结构问题
类模板的定义格式:
template<class T1, class T2, ..., class Tn> class example { // 类内成员定义 };
和函数模板类似,类模板也可以同时
定义多个模板参数
写一个简易的顺序表:
template<class T> class Vector { public : Vector(size_t capacity = 10) : _Data(new T[capacity]) , _size(0) , _capacity(capacity) {} T& operator[](size_t pos) { assert(pos < _size); return _Data[pos]; } private: T* _Data; size_t _size; size_t _capacity; };
所有实际类型需要出现的地方用T代替
7. 类模板的实例化
和函数模板不同,类模板没有隐式推演用户必须显示实例化
Vector<int> v1;
注意:
Vector是类名
Vector< int >才是类型
当在类中声明一个函数
但是想在类外定义时
若函数的参数或内部使用的类型
和模板有关系,那么必须这样写:
template<class T> class Vector { public : //类中声明函数 void push_back(T x); private: T* _Data; size_t _size; size_t _capacity; };
类外定义:
template<class T> void Vector<T>::push_back(T x) { _Date[_size] = x; _size++; }
注:必须要再加上类模板template
并且要指定类域
8. 总结以及拓展
泛型编程是C++的一大利器
它极大的减少了代码的复杂程度
并且增加了代码的可读性
C++基础部分的内容已经全部结束
下一阶段进入C++中阶:STL的使用
拓展:
🔎 下期预告:STL库的介绍以及使用 🔍