C++要笑着学:函数模板 - 哦哟谢天谢地了

   ​​​​​​           爆笑教程   《C++要笑着学》  火速订阅  

C++要笑着学:函数模板 - 哦哟谢天谢地了_第1张图片

C++要笑着学:函数模板 - 哦哟谢天谢地了_第2张图片

写在前面

我是柠檬叶子C,首先要说的是 —— 我们的《C++要笑着学》排版升级了!阅读体验更上一层楼,强烈建议电脑端阅读!本章将正式开始介绍C++中的模板,为了能让大家更好地体会到用模板多是件美事!我们将会举例说明,大家可以试着把自己带入到文章中,跟着思路去阅读和思考,真的会很有意思!如果你对网络流行梗有了解,读起来将会更有意思!


Ⅰ.  泛型编程

0x00  引入 - 通用的交换函数

在C语言中,我们实现两数交换,不用花的方法(异或啥的),中规中矩的写法是通过 tmp 交换。

比如我们这里想交换 变量a变量b 的值,我们可以写一个 Swap 函数:

void Swap(int* px, int* py) {
	int tmp = *px;  // 创建临时变量,存储a的值
	*px = *py;      // 将b的值赋给a
	*py = tmp;      // 让b从tmp里拿到a的值
}

int main(void)
{
	int a = 0, b = 1;
	Swap(&a, &b);   // 传址

	return 0;
}

变量a变量b 是整型,如果现在有了是浮点型的 变量c 变量d

还可以用我们这个整型的 Swap 函数交换吗?

void Swap(int* px, int* py) {  
	int tmp = *px; 
	*px = *py;      
	*py = tmp;      
}

int main(void)
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;  // 浮点型
	Swap(&a, &b);
	Swap(&c, &d);

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第3张图片

似乎不太行,因为我们实现的 Swap 函数接受的是整形数据,这里传的是浮点数了。

 我们可以再写一个浮点数版本的 Swap 函数…… 叫 SwapDouble

void SwapDouble(double* px, double* py) {
	double tmp = *px; 
	*px = *py;      
	*py = tmp;      
}

不错,问题是解决了。但是我现在又出现了字符型的 变量e 和 变量f 呢?

C++要笑着学:函数模板 - 哦哟谢天谢地了_第4张图片 ……

那我现在又出现了各种乱七八糟的类型呢?

C++要笑着学:函数模板 - 哦哟谢天谢地了_第5张图片

SwapIntSwapDoubleSwapChar 真是乱七八糟的,

❓ 能不能实现一个通用的 Swap 函数呢?

C++要笑着学:函数模板 - 哦哟谢天谢地了_第6张图片 那我们不用C语言了!我们用C++,C++里面不是有 函数重载 嘛!

用C++我们还能用引用的方法交换呢,直接传引用,取地址符号都不用打了,多好!

test.cpp: 

于是咔咔咔,改成了C++之后 ——

void Swap(int& rx, int& ry) {
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
void Swap(double& rx, double& ry) {
	double tmp = rx;
	rx = ry;
	ry = tmp;
}
void Swap(char& rx, char& ry) {
	char tmp = rx;
	rx = ry;
	ry = tmp;
}

int main(void)
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;
	char e = 'e', f = 'f';

	Swap(a, b);
	Swap(c, d);
	Swap(e, f);

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第7张图片 场面一度尴尬…… 

好像靠函数重载来调用不同类型的 Swap,只是表面上看起来 "通用" 了 ,

实际上问题还是没有解决,有新的类型,还是要添加对应的函数…… 

C++要笑着学:函数模板 - 哦哟谢天谢地了_第8张图片

❌ 用函数重载解决的缺陷:

① 重载的函数仅仅是类型不同,代码的复用率很低,只要有新类型出现就需要增加对应的函数。

② 代码的可维护性比较低,一个出错可能导致所有重载均出错。

 哎!要是能像做表情包那样简单就好了……

你看我做表情,有些是可以靠模板去制作的,比如这种 "狂粉举牌" 表情:

C++要笑着学:函数模板 - 哦哟谢天谢地了_第9张图片

链接: 鹏哥C语言从入门到精通(视频教程)   


这就是模板!如果在C++中也能够存在这样一个模板该有多好?

就像这里,只要在板子上写上名字(类型),

就可以做出不同的 "举牌表情"(生成具体类型的代码)。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第10张图片 那将会节省很多头发!

巧妙的是!C++里面有这种神器!

C++要笑着学:函数模板 - 哦哟谢天谢地了_第11张图片而且大佬已经把神器打造好了,你只要学会如何使用就能爽到飞起!

下面让我们开始函数模板的学习!在这之前我们再来科普一下什么是泛型编程。

0x01  什么是泛型编程

 泛型,就是针对广泛的类型的意思。

泛型编程: 编写与类型无关的调用代码,是代码复用的一种手段。 模板是泛型编程的基础。

Ⅱ.  函数模板

0x00  函数模板的概念

上面我们提到了 "神器" ,现在我们来学会如何去使用它,我们先来介绍一下概念。

  函数模板代表了一个函数家族,该函数模板与类型无关,

在使用时被参数化,根据实参类型产生函数的特定类型版本。

0x01  函数模板格式

template
返回值类型 函数名(参数列表){}

template 是定义模板的关键字,后面跟的是尖括号 < >

typename 是用来定义模板参数的关键字

T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第12张图片  就像这个表情包模板,我给他取名为 "狂粉举牌" 表情。

解决刚才的问题:

① 我们来定义一个叫 Swap 的函数,我们这不给具体的类型:

void Swap();

② 然后在它的前面定义一个具体的类型:

template    // template + 
void Swap();

 ③ 这时候,我们就可以用这个模板名来做类型了:

template         // 模板参数列表 ———— 参数类型
void Swap(T& rx, T& ry) {    // 函数参数列表 ———— 参数对象
	T tmp = rx;
	rx = ry;
	ry = tmp;
}

这,就是函数模板!虽然参数的名字我们可以自己取 (你写成 TMD 也没人拦你 )

 但是我们一般喜欢给它取名为 T,因为 T 代表 Type(类型),

有些地方也会叫 TPTYX ,或者 KV结构(key-value-store)我们还会给它取名为 KING

当然,如果你需要多个类型,也是可以定义多个类型的:

template

 注意事项:

① 函数模板不是一个函数,因为它不是具体要调用的某一个函数,而是一个模板。就像 "好学生",主体是学生,"好" 是形容 "学生" 的;这里也一样,"函数模板" 是模板,所以 函数模板表达的意思是 "函数的模板" 。所以,我们一般不叫它模板函数,应当叫作函数模板。

"函数模板不是一个实在的函数,编译器不能为其生成可执行代码。定义函数模板后只是一个对函数功能框架的描述,当它具体执行时,将根据传递的实际参数决定其功能。"  —— 《百度百科》

② 我们在用 template< > 定义模板的时候,尖括号里的 typename 其实还可以写成 class

template     // 使用class充当typename (具体后面会说)
void Swap(T& rx, T& ry) {
	T tmp = rx;
	rx = ry;
	ry = tmp;
}

现在我们把完整的代码跑一下看看:

template
void Swap(T& rx, T& ry) {
	T tmp = rx;
	rx = ry;
	ry = tmp;
}

int main(void)
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;
	char e = 'e', f = 'f';

	Swap(a, b);
	Swap(c, d);
	Swap(e, f);

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第13张图片 (代码成功运行)

调试,打开监视看看是否都成功交换了:

C++要笑着学:函数模板 - 哦哟谢天谢地了_第14张图片

搞定!我们使用模板成功解决了问题,实现了通用的 Swap 函数!

如果是自定义类型,函数里面就要是拷贝构造,你要实现好就行。

因为 T 没有规定是什么类型,所以任意类型都是可以的,内置类型和自定义类型都可以的。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第15张图片 真是太香了!这,就是模板!

0x02  模板函数的原理

❓ 思考:这下面三个调用调用的是同一个函数吗?

C++要笑着学:函数模板 - 哦哟谢天谢地了_第16张图片

不是同一个函数。这三个函数执行的指令是不一样的,你可以这么想,

它们都需要建立栈帧,栈帧里面是要开空间的,你就要给 rx 开空间,

rx 的类型都不一样(double int char)。所以当然调用的不是同一个函数了。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第17张图片 我们来思考一下模板函数的原理是什么。

 比如说我现在想把杜甫写的《登高》做出一万份出来,怎么做?

C++要笑着学:函数模板 - 哦哟谢天谢地了_第18张图片 C++要笑着学:函数模板 - 哦哟谢天谢地了_第19张图片

 C++要笑着学:函数模板 - 哦哟谢天谢地了_第20张图片

最后我们传递出去的也不是印诗的模具,而是印出来的纸,

不管是手抄还是印刷,传递出去的都是纸。

所以我们再来看这里的代码:

template
void Swap(T& rx, T& ry) {
	T tmp = rx;
	rx = ry;
	ry = tmp;
}

int main(void)
{
	int a = 0, b = 1;
	double c = 1.1, d = 2.2;
	char e = 'e', f = 'f';

	Swap(a, b);
	Swap(c, d);
	Swap(e, f);

	return 0;
}

和上面说的一样,我们不会把印诗的模具传递出去,而是印出来的纸,

 所以这里调用的当然不是模板,而是这个模板造出来的东西。

而函数模板造出 "实际要调用的" 的过程,叫做模板实例化。

编译器在调用之前会干一件事情 —— 模板实例化。

我们下面就来探讨一下模板实例化。

Ⅱ.  函数模板实例化

0x00  引入:这些不同类型的Swap函数是怎么来的

int a = 0, b = 1;
Swap(a, b);

编译器在调用 Swap(a, b) 的时候,发现 a b 是整型的,编译器就开始找,

虽然没有找到整型对应的 Swap,但是这里有一份模板 —— 

template   // 大家好我是模板,飘过~
void Swap(T& rx, T& ry) {
	T tmp = rx;
	rx = ry;
	ry = tmp;
}

这里要的是整型,编译器就通过这个模板,推出一个 T int 类型的函数。

这时编译器就把这个模板里的 T 都替换成 int,生成出一份 T int 的函数。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第21张图片

char e = 'e', f = 'f';
Swap(e, f);

一样的,如果要调用 Swap(e, f) ,e f 是字符型,编译器就会去实例化出一个 char 的。

 C++要笑着学:函数模板 - 哦哟谢天谢地了_第22张图片

你调的函数还是那些函数,只是你写一份模板出来,让编译器去用模板生成那些函数。

前面注意事项那里我们说过,函数模板本身不是函数。

它是是编译器使用方式产生特定具体类型函数的模具,在编译器编译阶段,

对于模板函数的使用,编译器需要根据传入的实参类型来推演,生成对应类型的函数以供调用。

比如:当用 double 类型使用函数模板时,编译器通过对实参类型的推演,

T 确定为 double 类型,然后产生一份专门处理 double 类型的代码,对于字符类型也是如此。

0x01  转到反汇编观察

我们刚才调试的时候在监视窗口已经看到了,它们的值成功交换了。

现在我们再调试一次,这次转到反汇编,去验证一下编译器通过模板生成函数这件事:

C++要笑着学:函数模板 - 哦哟谢天谢地了_第23张图片

C++要笑着学:函数模板 - 哦哟谢天谢地了_第24张图片

0x01  模板实例化的定义

模板将我们本来应该要重复做的活,交给了编译器去做。

编译器不是人,它不会累,让编译器拿着模板实例化就完事了。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第25张图片 用手搓衣服舒服,还是用洗衣机洗舒服?

 自己手写舒服,还是编译器自己去生成舒服?

 用不同类型的参数使用模板参数时,成为函数模板的实例化。

模板参数实例化分为:隐式实例化 显式实例化 ,下面我们来分别讲解一下这两种实例化。

0x02  模板的隐式实例化

定义:让编译器根据实参,推演模板函数的实际类型。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第26张图片

 我们刚才讲的 Swap 其实都是隐式实例化,就是让编译器自己去推。

现在我们再举一个 Add 函数模板做参考:

#include 
using namespace std;

template
T Add(const T& x, const T& y) {
	return x + y;
}

int main(void)
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第27张图片

❓ 现在思考一个问题,如果出现 a1 + d2 这种情况呢?实例化能成功吗?

Add(a1, d2);

C++要笑着学:函数模板 - 哦哟谢天谢地了_第28张图片

这必然是失败的, 因为会出现冲突。一个要把它实例化成 int ,一个要把它实例成 double,

C++要笑着学:函数模板 - 哦哟谢天谢地了_第29张图片

  解决方式

① 传参之前先进行强制类型转换,非常霸道的解决方式:

template
T Add(const T& x, const T& y) {
	return x + y;
}

int main(void)
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

	cout << Add((double)a1, d2) << endl;

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第30张图片

② 写两个参数,那么返回的参数类型就会起决定性作用:

#include 
using namespace std;

template
T1 Add(const T1& x, const T2& y) {   // 那么T1就是int,T2就是double
	return x + y;      // 范围小的会像范围大的提升,int会像double "妥协"
} // 最后表达式会是一个double,但是最后返回值又是T1,是int,又会转

int main(void)
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;

	cout << Add(a1, d2) << endl;   // int,double  

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第31张图片

当然,这种问题严格意义上来说是不会用多个参数来解决的,

 这里只是想从语法上演示一下,我们还有更好地解决方式,我们继续往下看。

③ 我们还可以使用 "显式实例化" 来解决:

Add(a1, d2);     // 指定实例化成int
Add(a1, d2)   // 指定实例化成double

C++要笑着学:函数模板 - 哦哟谢天谢地了_第32张图片我们下面先来详细介绍一下显式实例化,然后再回来看看它是如何解决的。

0x03  模板的显式实例化

定义:在函数名后的 < > 里指定模板参数的实际类型。

简单来说,显式实例化就是在中间加一个尖括号 < >  去指定你要实例化的类型。

(在函数名和参数列表中间加尖括号)

函数名 <类型> (参数列表);

代码:解决刚才的问题

template
T Add(const T& x, const T& y) {
	return x + y;
}

int main(void)
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

	cout << Add(a1, d2) << endl;     // 指定T用int类型
	cout << Add(a1, d2) << endl;  // 指定T用double类型

	return 0;
}

运行结果:

C++要笑着学:函数模板 - 哦哟谢天谢地了_第33张图片

  解读:

像第一个 Add<int>(a1, a2)  ,a2 是 double,它就要转换成 int

第二个 Add<double>(a1, a2),a1 是 int,它就要转换成 double

这种地方就是类型不匹配的情况,编译器会尝试进行隐式类型转换。

double int 这种相近的类型,是完全可以通过隐式类型转换的。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第34张图片 如果无法成功转换,编译器将会报错。

总结:

函数模板你可以让它自己去推,但是推的时候不能自相矛盾。

你也可以选择去显式实例化,去指定具体的类型。

0x04  模板参数的匹配原则

我们还是用刚才的 Add 函数模板来举例,现在我需要对整型的 a1 和 a2 进行加法操作:

template
T Add(const T& x, const T& y) {
	return x + y;
}

int main(void)
{
	int a1 = 10, a2 = 20;	
	cout << Add(a1, a2) << endl;

	return 0;
}

 我们是通过这个 Add 函数模板,生成 int 类型的加法函数的。

如果我们有一个现成的、专门用来处理 int 类型加法的函数:

// 专门处理int的加法函数
int Add(int x, int y) {
	return x + y;
}

// 通用加法函数
template
T Add(const T& x, const T& y) {
	return x + y;
}

int main(void)
{
	int a1 = 10, a2 = 20;	
	cout << Add(a1, a2) << endl;

	return 0;
}

❓  思考:如果你是编译器,当 Add(a1, a2) 时你会选择用哪一个?

是用函数模板印一个 int 类型的 Add 函数,还是用这现成的 Add 函数呢?

我们继续往下看……

匹配原则:

① 一个非模板函数可以和一个同名的模板函数同时存在,

而且该函数模板还可以被实例化为这个非模板函数:

// 专门处理int的加法函数
int Add(int x, int y) {
	cout << "我是专门处理int的Add函数: ";
	return x + y;
}

// 通用加法函数
template
T Add(const T& x, const T& y) {
	cout << "我是模板参数生成的: ";
	return x + y;
}

int main(void)
{
	int a1 = 10, a2 = 20;	
	cout << Add(a1, a2) << endl;       // 默认用现成的,专门处理int的Add函数
	cout << Add(a1, a2) << endl;  // 指定让编译器用模板,印一个int类型的Add函数

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第35张图片

② 对于非模板函数和同名函数模板,如果其他条件都相同,

在调用时会优先调用非模板函数,而不会从该模板生成一个实例。

如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

// 专门处理int的加法函数
int Add(int x, int y) {
	cout << "我是专门处理int的Add函数: ";
	return x + y;
}

// 通用加法函数
template
T1 Add(const T1& x, const T2& y) {
	cout << "我是模板参数生成的: ";
	return x + y;
}

int main(void)
{
	cout << Add(1, 2) << endl;     // 用现成的
	//(与非函数模板类型完全匹配,不需要函数模板实例化)

	cout << Add(1, 2.0) << endl;   // 可以,但不是很合适,自己印更好
	//(模板参数可以生成更加匹配的版本,编译器根据实参生产更加匹配的Add函数)

	return 0;
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第36张图片

Ⅳ.  类模板

0x00  引入:和本篇开头本质上是一样的问题

就比如 Stack,如果我们定它是 int,那么它就是存整型的栈:

class Stack {
public:
	Stack(int capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new int[capacity];
	}
	~Stack() {
		delete[] _arr;
		_arr = nullptr;
		_capacity = _top = 0;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

❓ 如果我想改成存 double 类型的栈呢?

 当时我们在讲解数据结构的时候,是用 typedef 来解决的。

typedef int STDataType;
class Stack {
public:
	Stack(STDataType capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new int[capacity];
	}
	~Stack() {
		delete[] _arr;
		_arr = nullptr;
		_capacity = _top = 0;
	}
private:
	STDataType* _arr;
	int _top;
	int _capacity;
};

如果需要改变栈的数据类型,直接改 typedef 那里就可以了。

这依然是治标不治本,虽然看起来就像是支持泛型一样,

 它最大的问题是不能同时存储两个类型,你就算是改也没法解决:

int main(void)
{
	Stack st1;   // 存int数据
	Stack st2;   // 存double数据

	return 0;
}

你只能做两个栈,如果需要更多的数据类型……

C++要笑着学:函数模板 - 哦哟谢天谢地了_第37张图片那就麻烦了,你需要不停地CV做出各种数据类型版本的栈:

class StackInt {...};
class StackDouble {...};
……

这和文章开头提到的问题(Swap)本质上是一个问题,就是不支持泛型。

它们类里面的代码几乎是完全一样的,只是类型的不同。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第38张图片 函数我们可以使用模板,类也是可以的,我们下面就来讲解一下类模板。

0x01  类模板的定义格式

定义:和函数模板的定义方式是一样的,template 后面跟的是尖括号 < >

template
class 类模板名 {
    类内成员定义
}

代码:解决刚才的问题

template
class Stack {
public:
	Stack(T capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new T[capacity];
	}
	~Stack() {
		delete[] _arr;
		_arr = nullptr;
		_capacity = _top = 0;
	}
private:
	T* _arr;
	int _top;
	int _capacity;
};

int main(void)
{
	Stack st1;   // 存储int
	Stack st2;   // 存储double

	return 0;
}

 C++要笑着学:函数模板 - 哦哟谢天谢地了_第39张图片 但是我们发现,类模板他好像不支持自动推出类型,

 它不像函数模板,不指定它也可以根据传入的实参去推出对应的类型的函数以供调用。

C++要笑着学:函数模板 - 哦哟谢天谢地了_第40张图片

函数模板之所以能推,是因为有实参传形参这么一个 "契机" ,让编译器能帮你推。

你定义一个类,它能推吗?没这个能力你知道吧!

C++要笑着学:函数模板 - 哦哟谢天谢地了_第41张图片

所以这里只支持显示实例化,我们继续往下看。

0x02  类模板实例化

 基于上面的原因,我们想要对类模板实例化,我们可以使用显示实例化。

 类模板实例化在类模板名字后跟 < >,然后将实例化的类型放在 < > 中即可。

类名 <类型> 变量名;

代码演示:解决刚才的问题


template
class Stack {
public:
	Stack(T capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new T[capacity];
	}
	~Stack() {
		delete[] _arr;
		_arr = nullptr;
		_capacity = _top = 0;
	}
private:
	T* _arr;
	int _top;
	int _capacity;
};

int main(void)
{
	Stack st1;      // 指定存储int
	Stack st2;   // 指定存储double

	return 0;
}

注意事项:

① Stack 不是具体的类,是编译器根据被实例化的类型生成具体类的模具。

template
class Stack {...};

类模板名字不是真正的类,而实例化的结果才是真正的类。

② Stack 是类名,Stack<int> 才是类型:

Stack s1;
Stack s2;

0x03  类外定义类模板参数

❓ 思考问题:下面的 Push 为什么会报错?

template
class Stack {
public:
	Stack(T capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new T[capacity];
	}
	// 这里我们让析构函数放在类外定义
	void Push(const T& x);
	~Stack();
private:
	T* _arr;
	int _top;
	int _capacity;
};

/* 类外 */

void Stack::Push(const T& x) {   ❌
    ...
}

解答:

① Stack 是类名,Stack 才是类型。这里要拿 Stack<T> 去指定类域才对。

② 类模板中的函数在类外定义,没加 "模板参数列表" ,编译器不认识这个 T类模板中函数放在类外进行定义时,需要加模板参数列表。

这段代码第一个问题是没有拿 Stack<T> 去指定类域,

最大问题其实是编译器压根就不认识这个T

即使你用拿类型 Stack<T> 指定类域,编译器也一样认不出来:

我们拿析构函数 ~Stack 来演示一下:

template
class Stack {
public:
	Stack(T capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new T[capacity];
	}

	// 这里我们让析构函数放在类外定义
	~Stack();
private:
	T* _arr;
	int _top;
	int _capacity;
};

/* 类外 */
Stack::~Stack() {    ❌  // 即使是指定类域也不行 
    ...
}

C++要笑着学:函数模板 - 哦哟谢天谢地了_第42张图片

代码演示:我们现在来看一下如何添加模板参数列表!

template
class Stack {
public:
	Stack(T capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new T[capacity];
	}

	// 这里我们让析构函数放在类外定义
	~Stack();
private:
	T* _arr;
	int _top;
	int _capacity;
};

// 类模板中函数放在类外进行定义时,需要加模板参数列表
template 
Stack::~Stack() {   // Stack是类名,不是类型! Stack 才是类型,
	delete[] _arr;
	_arr = nullptr;
	_capacity = _top = 0;
}

 这样编译器就能认识了。

本章完!


文章信息

 [ 笔者 ]   王亦优
 [ 更新 ]   2022.4.8
❌ [ 勘误 ]   暂无
 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

参考资料

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

. C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. C++[EB/OL]. 2021[2021.8.31]. .

你可能感兴趣的:(《C++要笑着学》,泛型编程,函数模板,函数模板实例化,类模板,C++)