C++中的模版初识
目录
1.泛型编程
2.函数模板
什么是函数模板
如何定义函数模板
函数模板的原理
如何使用函数模板
隐式实例化
显示实例化
模板参数的匹配原则
3.类模板
如何定义类模板
如何使用类模板
1.泛型编程
所有的编程语言都具有各种各样的数据类型,不同数据类型的数据可能需要相同的功能。这个时候,如果是C语言的话,只能针对每种数据类型单独写一个函数,并且函数名还不能相同,如果是C++的话,虽然支持同名函数的出现,但是,也仅仅是函数名相同,针对不同的数据类型还是需要单独实现一个函数。于是,C++就引入了一种无关类型的编程方式 —— 泛型编程。
在编程的世界中,不同类型的数据就好像是不同类型的原材料一般,在泛型编程的世界中,我们并不关心原材料的类型,不管你给我什么类型我都实现同样的功能;我们可以对比生活中的例子来理解,不知道大家有没有见过月饼的制作,过程大概如下:
- 用皮把馅料包起来。
- 然后塞进模板。
- 然后拍出来。
在这个过程中,模板并不关心是什么馅料,不管你是什么馅料,都能形成该卸料对应的月饼。没错,泛型编程也是如此,我们告诉编译器一个模板,编译器根据不同类型的数据利用该模板生成对应的代码。
在C++中,模板有两种,一种是函数模板,一种是类模板。(哦吼,这让我想起了友元函数和友元类)下面依次介绍
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。
2.函数模板
什么是函数模板
月饼模板是用来生产月饼的,那函数模板就是用来生产函数的。
函数模板是一个蓝图,本身并不是函数。
该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
如何定义函数模板
使用格式如下:
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
- template是定义模板的关键字
- typename是定义类型参数的关键字
- T1……Tn是模板参数
- 其中,typename可以用class代替。
举个例子,实现一个通用的交换函数:
template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}
函数模板的原理
函数模板会根据参数类型生成特定版本的函数,这个工作是编译器来做的。在编译器编译阶段,调用函数模板的时候,编译器根据传入类型的参数推演实例化出该类型的函数。
以Swap函数为例:当我们传入int类型的数据时,编译器就会根据模板推演实例化出一份针对int类型的交换函数;如果传入的是char类型的数据,就会推演实例化出一份针对char类型的函数。
如何使用函数模板
函数模板并不是最终使用的函数,需要推演实例化出针对具体类型的函数才能使用,这个过程就叫做函数模板的实例化。实例化分为两种:隐式实例化和显示实例化。
隐式实例化
隐式实例化就是让编译器根据实参推演模板参数的具体类型。
像这个例子中就是通过隐式实例化使用模板参数:
显示实例化
显示实例化的格式如下:
- 函数名 <类型> (参数)。其实就是在调用函数的时候,在函数后添加一对<>,并在<>中指明类型。
- 如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
注意:前面示范的是单个模板参数的情况,多个模板参数的情况只需要类比函数参数使用即可。
模板参数的匹配原则
我们以 一个非模板函数和一个同名的函数模板同时存在为例,看下面这段代码:
// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}// 通用加法函数
template<class T>
T Add(T left, T right)
{return left + right;
}int main()
{Add(1, 2); // 与非模板函数匹配,编译器不需要特化Add<int>(1, 2); // 调用编译器特化的Add版本Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数return 0;
}
我们可以这样理解,非模板函数是家里做的饭菜,函数模板生成的函数是外卖;可以形象的总结一下:
- 合适匹配的情况下,有现成的就吃现成。
- 没有现成的,就点外卖吃。
- 如果现成的不符合胃口,也点外卖。
注意,普通函数可以进行自动类型转换,函数模板不允许进行自动类型转换。(如果转换出来不是我们想要的,编译器就要背黑锅了,毕竟,谁都不愿意背黑锅)
3.类模板
C++中不仅仅有函数模板,还有类模板。比如:我们使用数据结构的时候,可能需要存储int类型的数据,可能需要存储char类型的数据,甚至需要存储自定义类型的数据,这个时候,不就又可以使用模板了吗?
如何定义类模板
定义类模板格式如下:
- 和函数模板的定义大差不差。
我们以数据结构中的栈为例:
// 类模板
template<class T>
class Stack
{
public:Stack(int capacity = 4){cout << "Stack(int capacity = 4)" << endl;_arr = new T[capacity];_top = 0;_capacity = capacity;}~Stack(){cout << "~Stack()" << endl;delete[] _arr;_arr = nullptr;_top = 0;_capacity = 0;}
private:T* _arr;int _top;int _capacity;
};
当类模板的成员函数的声明和定义分离,且再同一个文件中的时候,需要再定义的前面加模板参数列表。
如下所示:
// 类模板
template<class T>
class Stack
{
public:~Stack();
private:T* _arr;int _top;int _capacity;
};template<class T>
Stack<T>::~Stack()
{cout << "~Stack()" << endl;delete[] _arr;_arr = nullptr;_top = 0;_capacity = 0;
}
注意:类模板的声明和定义不能分在不同的文件,否则会造成链接错误。
如何使用类模板
类模板也是模板,使用的时候也需要进行推演实例化,只不过,类模板只能显示实例化。
并且,对于类模板来说,类名不是类型,类名+<模板参数类型> 才是类型。
使用方式如下:
Stack<int> s1;Stack<double> s2;Stack<char> s3;
推演过程如下: