11. C++模板深入解析:理解、使用与高级技巧

一、模板的理解与使用

首先要理解一个基本事实,模板的作用是让我们程序员少写代码,以此来简化开发流程的。

它对于最终编译生成的代码并没有任何影响。

所以即使不用模板,大多数时候我们同样也能够实现相关的功能。

这里以一个简单通用的代码来理解模板:

int add(int a, int b) {
	return a + b;
}
double add(double a, double b) {
	return a + b;
}

比如像上面这两个函数类似的一系列函数,它们函数名字完全相同,代码逻辑也完全相同,唯一不同的是它们所操作的数据类型,一个为int,一个为double,甚至还可以继续添加其它数据类型。

那么使用模板就可以将其简化为一段代码:

template<typename T>
T add(T a, T b) {
	return a + b;
}

此时我们不再使用特定的intdouble之类的类型,而是使用一个类似于变量的T类型。

方法就是在函数前面写上一个模板关键字template,然后在其后的<>中声明模板变量名。

声明模板变量,使用typename或者class均可,只不过由于class一般使用于类,所以模板声明变量的时候大多数时候应该还是使用的typename,便于区分。

如果需要有多个模板变量,直接跟在后面写即可:template<typename T,typename T1>

当然这里的T,你也可以自己随便取其它名字,只不过习惯取T,即Type的首字母。

此时这个函数要操作的类型就是不确定的了,所以我们在使用的时候就需要指定类型:

add<int>(1,3);
add<double>(1.1, 3.4);
add<char>('s','a');

也就是在add的后面跟上一个<>,里面填上我们想要使用哪种类型,然后就可以像正常函数那样使用了。

是不是有种似曾相识的感觉?STL模板库不就是这么用的嘛!

同时要注意,它并不是只有一个函数,虽然此时看上去我们只写了一个函数,但这只是表象。

实际上是编译器帮我们根据上面的那个模板函数,分别生成三个操作intdoublechar类型的函数。

进一步来说,模板是完全给编译器看的,并不会参与到最终的可执行文件中

上面这一点便是模板的精髓!

为了更加直观的理解,我们来看一下最终生成的三个函数的内存地址:

在这里插入图片描述

总结来说就是,模板并没有减少最终的代码量,它仅仅只是减少了我们程序员需要写的代码量。

并且这个过程是在编译期间就完成了的,这一点很重要!

二、模板类

除却用在函数中,模板还可以用在类上,比如我们使用的STL库,基本全是用模板实现的,所以看上去才那么怪异,一旦出错就很难排查,这也是使用模板的缺点之一。

image-20231008151455516

模板类与模板函数使用方法都是一样的,在前面添加template然后声明模板变量,之后就可以在这个模板类中随意使用这个模板变量了,将它视为一个真正的类型使用即可。

同时在调用的时候,你还需要指定它的类型,就和使用STL中的stack库是一样的。

三、注意事项

虽然模板使用起来确实简单,可以简化我们程序员的代码量。

但也有一些要注意的事项,比如,你的模板类中的函数,声明与实现都要写在头文件中才行,否则就会报错。

首先是声明与实现都写在头文件中:

image-20231008152217413

然后是分开写,但同样都在头文件中:

image-20231008152502662

注意模板分开写时,每个函数前面都必须要写上模板变量,比较麻烦,但依旧正确。

image-20231008152712598

一旦你将两者分开成两个文件,就会开始报错。

其原因在于这个类事先并不存在,只有当我们使用了Stack<int>时,编译器才会开始实例化一个操作int类型的类,一旦分开写,编译器就找不到对应的实现文件,从而报错。

可能的原因就是,是cpp文件包含的h文件,而main所在文件包含的也是h文件,编译器也就只会去实例化h文件中的代码。

如果将main文件中的#include"Stack.h"替换为#include"Stack.cpp"就不会报错了,但这样做就有点多此一举了,不如直接将代码全写在h文件中。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux