目录
泛型编程
函数模板
函数模板的概念
函数模板的格式
函数模板的原理
函数模板的实例化
函数模板的匹配原则
类模板
类模板的定义格式
类模板的实例化
c++的模板大致可以分为:
- 函数模板
- 类模板
首先在我们引入模板之前,先进行介绍泛型编程
泛型编程
在c语言中如果让你写一个swap函数
那么在c语言中我们可以这样写
// 交换两个整型
void swap_int(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
// 交换两个双精度浮点型
void swap_double(double* p1, double* p2)
{double tmp = *p1;*p1 = *p2;*p2 = tmp;
}
在c++中我们知道由函数重载这一特点,但是在c语言中这并没有,所以在对应不同类型的值进行交换时,会对应要有对应的函数。并且要注意进行址传递,而不是值传递。
而在后来的c++引入函数重载后,swap又可以进行这样的书写代码
// 交换两个整型
void swap(int& p1, int& p2)
{int tmp = p1;p1 = p2;p2 = tmp;
}
// 交换两个双精度浮点型
void swap(double& p1, double& p2)
{double tmp = p1;p1 = p2;p2 = tmp;
}
C++的函数重载使得用于交换不同类型变量的函数可以拥有相同的函数名,并且传参使用引用传参,使得代码看起来不那么晦涩难懂。
但是即使这样设计,还是有不足之处
1:重载的多个函数仅仅只是类型不同,代码的复用率比较低,只要出现新的类型需要交换,就需要新增对应的重载函数。
2、代码的可维护性比较低,其中一个重载函数出现错误可能意味着所有的重载函数都出现了错误。
那么是否存在一种模板,使得我们无论要交换什么类型,他都可以让编译器根据不同的类型利用该模子来生成相应的代码呢?
就像涂鸦石膏一样,已经给出了一个标准的模具,我们使用不同的颜色进行涂鸦,就可以得到不同颜色的石膏奥特曼。
如果在C++中,也能够存在这样一个模具,通过给这个模具填充不同颜色的材料(类型),从而得到形状相同但颜色不同的石膏(生成具体类型的代码),那将会大大减少代码的冗余。巧的是前人早已将树栽好,我们只需在此乘凉。
答案是存在的,也就是后来c++祖师爷所创建的c++的模板;
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
函数模板
函数模板的概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板的格式
template<typename T1,typename T2,…,typename Tn> 返回类型 函数名(参数列表) {//函数体 }
就比如用swap举例:
template<class T>
void swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}
注意:typename是用来定义模板参数的关键字,也可以用class代替,但是不能用struct代替。
函数模板的原理
那么函数模板的底层原理是什么呢?就拿现代的人工智能来说,
客户服务:聊天机器人和虚拟助手可以自动处理客户查询、提供帮助和支持,减轻人工客服的压力。早期的时候,这其实就是先给机器人一个对应固定回复模板来进行聊天。
马云:世界是懒人创造的
这句话是一种幽默的说法,意思是人类会创造出各种方便的工具和技术来减少工作量,从而变得“懒惰”。实际上,人类通过创造和发明来提高效率,创造更好的生活条件,并不一定代表懒惰。我们的创造力和创新精神推动着社会的发展和进步。
函数模板是标准的蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数(这一步就叫做实例化)以供调用,以进行编译。比如,当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于double类型也是如此。
函数模板的实例化
用不同类型的参数使用模板时,称为模板的实例化。模板实例化分为隐式实例化和显示实例化。
一、隐式实例化:让编译器根据实参推演模板参数的实际类型
#include<iostream> using namespace std; template<class T> void Swap(T& x, T& y)//注意不能用swap,因为引用了using namespace std;标准库里面存在标准的库函数swap {T tmp = x;x = y;y = tmp; } int main() {int a = 10, b = 20;cout << "a=" << a << " " << "b=" << b << endl;Swap(a, b);cout << "a=" << a << " " << "b=" << b << endl;return 0; }
运行结果:
我们在此基础上进行略微修改
其实修改后是编译不通过的。
因为在编译期间,编译器根据实参推演模板参数的实际类型时,根据实参a将T推演为int,根据实参b将T推演为double,但是模板参数列表中只有一个T,编译器无法确定此处应该将T确定为int还是double。
此时,我们有两种处理方式,第一种就是我们在传参时将b强制转换为int类型,第二种就是使用下面说到的显示实例化。
二、显示实例化:在函数名后的<>中指定模板参数的实际类型
#include<iostream> using namespace std; template<class T> T Add(const T& x, const T& y) {return x + y; } int main() {int a = 10;double b = 20;int c = Add<int>(a, b); //指定模板参数的实际类型为intcout << c;return 0; }
运行结果:
注意:使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,则编译器将会报错。
就比如:
#include<iostream>
using namespace std;
template<class T>
void Swap(T& x,T& y)
{T tmp = x;x = y;y = tmp;
}
int main()
{int a = 10;double b = 20;cout << "a=" << a << " " << "b=" << b << endl;Swap<int>(a, b);cout << "a=" << a << " " << "b=" << b << endl;return 0;
}
观察仔细的话可以发现我们这里没有用const修饰,这就是其中一个问题。
因为b要进行隐式类型转化,如果不加const的话,那么就会存在权限放大的错误。
但是加上const的话又会报错,不能修改其值。
所以来说不是所有的都是要加const,不是所有都可以及进行要进行隐式类型转化的显示实例化;
函数模板的匹配原则
一、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{return x + y;
}
//通用类型加法的函数模板
template<class T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = 10, b = 20;int c = Add(a, b); //调用非模板函数,编译器不需要实例化int d = Add<int>(a, b); //调用编译器实例化的Add函数return 0;
}
二、对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板
#include <iostream>
using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{return x + y;
}
int main()
{int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化int b = Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数return 0;
}
三、模板函数在隐式实例化不允许自动类型转换,但普通函数可以进行自动类型转换
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译return 0;
}
编译无法通过,因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。
类模板
类模板的定义格式
template<class T1,class T2,…,class Tn>
class 类模板名
{
private://类内成员声明
};
就比如:
#include<iostream> using namespace std; template<class T> class A { public:A(T val) :a(val){}void print(){cout << a << endl;} private:T a; }; int main() {A<int> a(2);a.print();return 0; }
运行结果:
注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。
#include<iostream>
using namespace std;
template<class T>
class A
{
public:A(T val) :a(val){}void print();
private:T a;
};
template<class T>
void A<T>::print()
{cout << a << endl;
}
int main()
{A<int> a(2);a.print();return 0;
}
类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后面根<>,然后将实例化的类型放在<>中即可。
注意:类模板名字不是真正的类,而实例化的结果才是真正的类。