Hello,大家好,我们大家都知道,C++这个编程语言是由C语言继承而来的,因为是继承,所以我们的C++就要做出一些区分,要不然的话,就和C语言没有本质上的区别了,我们现在在社会中使用比较多的是C++而非是C语言,是因为这里我们C++的祖师爷在C语言的基础之上又设计了一个模板相关的内容,这个模板就受到了很多人的欢迎。
1.前情提要
我们在C语言中经常会使用到一些相同的函数,就比如说Swap函数,我们在前面的C语言编程中就经常会使用到这个Swap函数来交换两个同类型的数据,我们要交换的数据的类型往往不止一种,我们要交换两个int类型的数据,double类型的数据等等,我们要为每一个类型的数据交换都要写一个Swap交换函数,这样就很费时间和空间,这些函数明明达到的效果都是一样的,但是却要写好多的一模一样的函数,很不爽,我们C++的祖师爷考虑到了这种情况,于是,就发明了模板这个东西来改变这种情况。
2.泛型编程
编写于类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的一种基础。模板又分为函数模板和类模板。
3.函数模板
3.1函数模板的概念
函数模板代表了一个函数家族,该函数模板与类型无关,我们在使用时被参数化,根据实参类型从而产生函数的特定类型版本。
3.2函数模板的格式
cpp
template<class T>;
class T 就是参数列表的类型,编译器会根据类型来生成对应的函数,为了避免不必要的麻烦,因此这里建议参数列表中有几个形参,这里就写几个:
cpp
template<class T1,class T2......>
比如:在这里写一个Swap交换函数。
cpp
#include<iostream>
using namespace std;
template<typename T>
void Swap(T a1, T a2)
{
T tmp=a1;
a1 = a2;
a2 = a1;
}
int main()
{
int a = 11, b = 22;
Swap(a, b);//这里我们调用Swap函数的时候,那个Swap模板中的T就被编译器自动识别为int,编译器会自动构造一个int类型的Swap函数。
cout << a << " " << b << endl;//11 22
double c = 1.1, d = 2.2;
Swap(c, d);//当我们传double类型的数据过去的时候,这个Swap函数中的T就会被编译器自动识别为double,编译器会自动构造一个double类型的Swap交换函数。
cout << c << " " << d << endl;//1.1 2.2
char e = 'e', f = 'z';
Swap(e, f);//当我们传char类型的数据过去的时候,这个Swap函数中的T就会被编译器识别为char,编译器会自动构造一个cahr类型的Swap交换函数。
cout << e << " " << f << endl;//z e
return 0;
}
注:typename是用来定义模板参数的关键字,我们这里再定义模板参数的时候,其实不仅仅能使用typename这一个关键字,我们还可以使用class这个关键字来代替上述代码中的typename关键字(class/typename这两个关键字的效果都是一样的,不管写谁都可以)。切记:这里不可以使用struct来代替class。
cpp
template<class T1,class T2>
3.3函数模板的原理
函数模板类似于就是一个蓝图一样,是编译器的使用方式产生特定具体类型函数的摸具。总结下来就是将原本应该有我们做的重复的事情交给了编译器去做。
cpp
template<typename T1,typename T2>
void swap(T1& x,T2& y)
{ }
在编译器的编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演并生成对应类型的函数,以供我们去使用,就比如说,当int类型处理上述的函数时,编译器会通过对实参类型的推演将T1,T2确定为是int类型,然后产生一份专门处理int类型的代码,任何类型均是如此。
3.4函数模板的实例化
用函数模板生成对应的函数这个过程成为是模板的实例化。
(1).隐式实例化:让编译器根据参数来推演模板参数的实际类型。
cpp
#include<istream>
using namespace std;
template<class T>//这里我们传过来的参数有几种类型,我们在这里就定义几个class/typename。
T add(T& a1, T& a2)
{
return a1 + a2;
}
template<class T1,class T2>//这里我们传过来的参数有几种类型,我们在这里就定义几个class/typename。
void Add(T1& b1, T2& b2)
{
int a = b1 + b2;
}
int main()
{
int a1 = 1, a2 = 2;
add(a1,a2);//编译器会自动根据出过去的实参(也就是a1和a2的类型)确定出T为int。
double b1 = 1.1, b2 = 2.2;
add(b1, b2);//编译器会自动根据出过去的实参(也就是b1和b2的类型)确定出T为double。
Add(a1,b2);//编译器会自动根据出过去的实参(也就是a1和b2的类型)确定出T1为int类型,确定出T2为double类型。
return 0;
}
注意:这里我们传过来的参数有几种类型,我们在这里就定义几个class/typename,如果我们传过去的参数类型有两种的话,但是我们定义的class/typename只有一种的话,那么,这样的话,编译器就无法分清这里定义的这个T是哪一个类型,就会出现编译报错的问题。
(2).显示实例化:在函数名后面的< >中指定模板参数的类型。
cpp
#include<istream>
using namespace std;
template<class T1,class T2>//这里我们传过来的参数有几种类型,我们在这里就定义几个class/typename。
void Add(T1& b1, T2& b2)
{ }
int main()
{
int a1 = 1, a2 = 2;
Add<int, int>(a1, a2);//T1是int类型,T2也是int类型。
double b1 = 1.1, b2 = 2.2;
Add<double, double>(b1, b2);//T1是double类型,T2也是double类型。
Add<int, double>(a1, b2);//T1是int类型,T2是double类型。
return 0;
}
注意:1>.这个显示实例化的知识我们必须要掌握,这个知识点我们在后面会大量使用。
2>.如果类型不匹配的话,那么编译器就会尝试着进行类型转换的操作,如果这个类型转换无法成功的话,编译器就会进行报错的操作。
3.5模板参数的匹配原则
(1).一个非模板函数可以和一个同名的函数木模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
(2).对于非模板函数和同名函数模板,如果其他的条件都相同的话,那在调用时会优先调用非模板函数,而不会从该模板中产生一个实例。如果模板可以产生一个具有更好匹配的函数,那么就会选择模板(换句话说,就是有现成的就用现成的,而不会自己创造一个)。
(3).模板函数不允许自动类型转换,但普遍函数却可以进行自动类型转换。
4.类模板
4.1类模板的格式
cpp
template<typename T1,typename T2>
class date//date是类模板名
{
//类内成员变量
};
4.2类模板的实例化
类模板的实例化与函数模板的实例化不同,类模板实例化需要在类模板名字后面跟< >,然后将类型放在< >中即可,类模板的名字其实不是真正的类,而实例化的结果才是真正的类。
cpp
#include<iostream>
using namespace std;
template<typename T>
class date//定义了一个类类型的模板
{
public:
//我们在模板里面实现一个Add函数
T Add(T& a1,T& a2)
{ }
void Swap(T& b1, T& b2);
};
//接下来,我们来写一下Swap函数的定义,在写之前这里还必须要补充一个知识点,就是每一个模板的作用域它仅限于当前的这个类,除了这个类之后就不管了,因此,对于类外的函数来说,我们就必须还得重新再写一个模板。
template<typename T>
void date<T>::Swap(T& b1, T& b2)
{ }
int main()
{
date<int> d;//类型名/类型:date<int>
//(date<int>即是类型名,同时也是类型)
return 0;
}
注意:类模板都是显示实例化。
今天我们关于初阶函数模板的知识就先讲到这里了,谢谢大家的支持,你们的支持就是我坚持的巨大动力。