前言
本文主要讲解如何使用简单的模板,了解模板的原理以及基本知识.
一、函数模板
模板的作用:
C++中模板的作用是支持泛型编程。==泛型编程=是一种编程范式,它只考虑算法或数据结构的抽象,而不考虑具体的数据类型。通过使用模板,可以编写一种通用的算法或数据结构,而不需要为每种数据类型都编写一遍相关代码。模板可以用于函数、类、结构体等地方,以实现通用的算法和数据结构。使用模板可以提高代码的复用性和可读性,减少代码的重复编写。
示例:实现一个交换函数.
使用模板之前:
void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } void swap(double& a, double& b) { double tmp = a; a = b; b = tmp; } void swap(char& a, char& b) { char tmp = a; a = b; b = tmp; } void test1() { //交换整形 int a = 2, b = 3; cout << "a=" << a << " " << "b=" << b << endl; swap(a, b); cout << "a=" << a << " " << "b=" << b << endl; //交换字符型 char c1 = 'd', c2 = 'f'; cout << "c1=" << c1 << " " << "c2=" << c2 << endl; swap(c1, c2); cout << "c1=" << c1 << " " << "c2=" << c2 << endl; //交换... }
上述实现过程中使用函数重载实现.但是函数重载会有一些不合适的问题.
1.函数重载只是重载的函数类型不同,代码复用率比较低,对于一个新的类型又要增加新的函数.
2.由于功能基本一样,只是类型不同,导致代码的可维护性比较低,一个出错可能所有的重载均出错,均要修改.
这时,函数模板就派上用场了.
(1)函数模板的格式
template<typename T1, typename T2,......,typename Tn>
返回值类型+ 函数名 +(参数列表){}
其中,typename 可以使用class代替,不能使用struct代替.
示例:使用模板后的通用交换函数.
template <class T>//模板 void swap(T& a, T& b)//T会根据传参的对象进行推导为相应的类型 { T tmp = a; a = b; b = tmp; } void test1() { //交换整形 int a = 2, b = 3; cout << "a=" << a << " " << "b=" << b << endl; swap(a, b); cout << "a=" << a << " " << "b=" << b << endl; //交换字符型 char c1 = 'd', c2 = 'f'; cout << "c1=" << c1 << " " << "c2=" << c2 << endl; swap(c1, c2); cout << "c1=" << c1 << " " << "c2=" << c2 << endl; //交换... }
(2)函数模板的原理(重点)
函数模板类似于一个模具,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做.
这就类似于古代的印刷术,如果每本书都需要手写,那效率是否太低了,还有各种情况可能会出错.但是印刷术的使用,就可以使用模具生成.
函数模板的原理是通过将类型参数化,使函数能够在编译时根据实际参数的类型推断生成具体的函数实例。编译器会根据调用函数时的参数类型,实例化出适合该类型的函数版本。
比如:
当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码.当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码.如上图所示.
(3)模板参数的显示实例化
上面我们实现的交换函数,模板根据传参时不同的参数,自动推演出函数参数的实际类型.我们称这类通过编译器进行自动推导的实例化模板参数称为模板参数的隐式实例化.
那什么是显示实例化呢?
template <typename T> T add(const T& a, const T& b) { return a + b; } void test1() { int a = 2, b = 3; double d1 = 2.5, d2 = 4.1; cout << add(a, b) << endl; cout << add(d1, d2) << endl; //下面这句会报错,因为一个模板参数无法在一个函数中实例化为2个不同类型的参数,一个int,一个double //cout << add(a, d2) << endl; }
一个函数模板参数在同一个函数中,无法被识别为不同的两个实例类型参数,当编译器推导出a是int时,又推出d2是double类型,则编译器陷入两难.
就好比:
int:妈妈说今天不许出去玩!
double:爸爸说今天可以出去玩!
编译器:我听谁的.
解决方案:
直接将参数先强转为一样的,当模板函数接收到参数时,就只有一样的结果了.
//解决方法1:传参时将其中不同的参数强转,使参数们相同 cout << add(a, (int)d2) << endl; cout << add((double)a, d2) << endl;
模板参数的显示实例化:
让爸妈先商量好听谁的.
//解决方法2:显示指定模板的参数 cout << add<int>(a, d2) << endl; //听妈妈的 cout << add<double>(a, d2) << endl; //听爸爸的
我们应当是考虑如何在调用时采取不同的调用方式去满足我们的需求,千万不要想着去修改模板函数的返回值,参数使他们固定生成,那模板就不通用了,而且不是什么时候我们都可以去修改模板的.
错误示例:
template <typename T> T add(const T& a, const int& b)//直接修改参数,进行固定 { return a + b; }
(4)模板匹配
对于函数名相同的非模板函数和模板函数同时存在时,编译器会优先选择非模板函数.除非模板可以产生更好的匹配函数,才会选择模板.
编译器:有现成的为啥不用.
void swap(double& a, double& b) { double tmp = a; a = b; b = tmp; } //template <class T> template <typename T>//函数模板 void swap(T& a, T& b) { T tmp = a; a = b; b = tmp; } void test1() { //交换double double d1 = 2.5, d2 = 4.5; //非模板函数和模板函数同时存在时,编译器优先选择非模板函数,有现成的为啥不用?非得自己去再模具刻一个? swap(d1, d2); cout << "d1=" << d1 << " " << "d2=" << d2 << endl; //交换整形 int a = 2, b = 3; //没有现成的非模板函数,则编译器调用模板函数去实例化一份 swap(a, b); cout << "a=" << a << " " << "b=" << b << endl; }
交换double型数据时,会调用void swap(double& a, double& b)函数,因为有现成的可以调用.
交换int整形时,则会调用模板函数void swap(T& a, T& b),实例化生成int型的函数.
小知识:
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换.
因为模板函数的参数是通过参数类型进行推导的.
二、类模板
类模板的格式
template <typename T> class A { //成员 }
类模板在后续学习STL时候会具体介绍,目前了解一下即可,使用方法与函数模板类似,这里就不过多介绍了.
template <typename T> class A { public: A(size_t capacity = 10) : _data(new T[capacity]) , _size(0) , _capacity(capacity) {} void push_back() { //... } ~A() { delete _data; _size = 0; _capacity = 0; } private: T* _data; size_t _size; size_t _capacity; }; void test3() { A<int> a1; //实例化为存储int数据的类 A<double> a2; //实例化为存储double数据的类 }
本文只是对模板的初步了解,后续会遇到更加复杂的模板,比如多参数的模板等,知识一点点的学,不求速成,坚持一点点的积累,一起加油吧!
今天就讲到这里了,拜拜.