C++【模板初阶】

简介: 早在北宋年间,中国的毕昇就已经发明了泥活字,标志着四大发明之一的活字印刷术正式诞生,从此文化传播取得了革命性突破,各种文学作品得以走进千家万户。倘若这项技术还没有被发明,那么恐怕我们现在的书本都还得靠逐字手抄传播,效率是非常低的我们的程序也是如此,很多需要频繁使用的函数每次都得手动写,这可难不倒程序员,于是在上世纪80年代末,范型编程思想正式诞生,它就像是印刷文字的模具,将程序主体刻在其中,需要使用时让编译器根据参数类型生成即可,这就是我们今天的主角模板

✨个人主页: Yohifo

🎉所属专栏: C++修行之路

🎊每篇一句: 图片来源


No one saves us but ourselves, no one can and no one may. We ourselves must walk the path.


除了我们自己,没有人能拯救我们,没有人可以,也没有人可以。我们自己必须走这条路。

f69527deb5ad1b75ea78185a170d37a.png



📘前言


早在北宋年间,中国的毕昇就已经发明了泥活字,标志着四大发明之一的活字印刷术正式诞生,从此文化传播取得了革命性突破,各种文学作品得以走进千家万户。倘若这项技术还没有被发明,那么恐怕我们现在的书本都还得靠逐字手抄传播,效率是非常低的


我们的程序也是如此,很多需要频繁使用的函数每次都得手动写,这可难不倒程序员,于是在上世纪80年代末,范型编程思想正式诞生,它就像是印刷文字的模具,将程序主体刻在其中,需要使用时让编译器根据参数类型生成即可,这就是我们今天的主角模板

1db366cae7bfb4ad209ab502ff9d481.png



📘正文


模板的产生源自于范型编程的思想,简单来说,就是将算法抽象化编写


📖范型编程


那么什么才是一个抽象化的算法呢?


比如我们常用的两数相加函数,按照以前的写法,处理整型数据时,编写整型的方法;处理浮点型时,又得编写一个浮点型的加法,好在C++支持函数重载,使得我们可以存在同名函数,假若是C语言实现时,我们甚至要写两个不同名的相加函数


//处理整型的加法函数
int Add(const int& a, const int& b)
{
  return a + b;
}
//处理浮点型的加法函数
double Add(const double& a, const double& b)
{
  return a + b;
}


两数相加,直接返回两数之和就行了,我们实现方法时,没必要关注具体数据类型


将具体问题抽象化,直接假设数据类型为 T,利用模板实现如下:


//利用模板实现函数
template <class T>  //模板关键字
T Add(const T& a, const T& b)
{
  return a + b;
}


此时我们只编写了一个加法函数模板,而所有类型的参数都可以调用加法函数

dace9abb5887f3629fb38fc1553df92.png

具体问题抽象化就是范型编程的核心思想


📖函数模板


首先来看模板在函数实现上的运用


注意:


模板关键字为 template

形式为 template <class T> 或者 template <typename T>

其中的T是模板中的参数名,我们可以自定义

模板中可以存在多个参数,通过 , 号分隔

🖋️使用方法


模板函数即在函数实现之前,写好模板,再根据模板中定义的变量名实现函数


//实现所有类型数组的打印
//这种模板写法也是没有问题的
template <typename Type>
void CoutArray(const Type& arr)
{
  //范围 for ,C++11 中的语法糖
  for (auto e : arr)
  {
  cout << e << " ";
  }
  cout << endl;
}

7094bf7308975ec9d0c0a039b8930cb.png


我们还可以实现多参数模板


//多参数模板
//这里实现的是val2强制类型转换为val1,并取得和
template <class T1, class T2>
T1 getTrunVal(const T1& val1, const T2& val2)
{
  const T1 tmp = (const T1)val2;
  return val1 + tmp;
}


bd45066ffef6240de2fe59d14513b0c.png


总之,在函数模板的存在下,我们不再需要再编写不同类型参数的相似函数了


🖋️实现原理


这个模板看着挺厉害,那么它的实现原理是什么呢?


其实很简单,只需要两样东西:编译器 和 函数重载


当我们编写好函数模板后,编译器会记住这个模板的内容,当我们使用模板时,编译器又会根据参数类型,创建相应的、具体的函数供参数使用,而这就是函数重载的道理


形象化理解:


假设我们的整个程序就是一个大城市

在这个城市中,我们就是造物主,编译器则是负责协助我们处理事情的

假设在某一天,参数A提出它需要一栋房子(方法),造物主很不屑的给造好了房子

一天后,参数B也说它也需要一栋房子(方法),造物主很快就满足了它的需求

之后的每一天中,都会有参数说自己需要房子(方法),于是造物主坐不住了,他觉得这些参数很麻烦,明明大家都是同一个需求,还得自己不断重复实现

于是他想了一个办法:将建造房子的图纸(模板)交给编译器,编译器是完全服从于造物主的,造物主说:“小编啊,以后再有人找我建房子(方法),你就按照这个图纸(模板)去建造,建好后将房子所有者变成它就行了”,这样一来,造物主的工作量就减小了很多,重复相似的工作直接提供蓝图(模板),然后让编译器根据参数类型落实即可

于是,函数模板就这样诞生了


可以看出,不断建房子这件麻烦事仍然存在,毕竟不可能让所有参数都入住一栋房子,函数模板 的本质就是将实现不同参数的相似方法这件事交给编译器去完成,我们只需要提供蓝图(模板)即可


比如文章开头中的 Add 函数,我们提供了模板,当实际调用函数时,编译器会自动识别参数类型,然后生成对应的函数,供参数调用,也就是说,编译器根据不同参数,老老实实生成了 int、double、char 三个版本的 Add 函数,如果有需要,它还能继续生成


实际参数调用时,调用的是模板生成的对应函数,而非模板本身!


编译器在识别参数类型生成函数时,有两种途径:


自动识别 (隐式)

我们手动指定(显式)


💡隐式实例化


隐式实例化就是编译器自动识别参数后生成函数的过程


隐式实例化很方便,但可能存在问题


//Add 模板
template <class T>
T Add(const T& a, const T& b)
{
  return a + b;
}
int main()
{
  Add(2, 1.5);  //此时编译失败!
  return 0;
}

528d3ad528b48f02277935f2f05e4c7.png


原因:


此时我们的模板是单参数模板

因为是编译器隐式实例化,当编译器识别到 2 时,将生成 int 型方法

此时 Add 函数内的两个形参类型都为 int,实际函数名修饰为 _3Addii

而我们的参数2为 double ,是一个浮点型数据,实际函数调用时,找的是这个函数_3Addid

此时出现明显的链接错误,编译器索性直接在编译前就已经报错阻拦

解决方法:


将参数2强制类型转换为 int,或者将参数1强制类型转换为 double 都能解决问题

多参数模板也能解决问题,此时如果识别到两个不同的参数,编译器就会根据实际情况生成函数

还有一种解决方法就是显式实例化


注意:


强制类型转换后生成临时变量进行传参

临时变量具有常性,所以Add函数中的引用形参需要被 const 修饰

或者不用引用,这样也不需要 const ,但是此时效率会变低


💡显式实例化


显式实例化就是给编译器打招呼,让它在建房子时按照我们的意愿来

Add<int> (2, 3.14); //此时编译器会调用 _3Addii 函数,至于传参时的类型转换,由编译器完成
Add<char> (2, 5); //调用 _3Addcc 函数


这种行为是完全合法的,< > 符号也正式和我们见面了,在后面的 STL 学习中,< > 会经常使用到,比如生成一个类型为 int 的顺序表,直接 vector<int>,生成 char 类型的顺序表 vector<char>,一键生成,非常方便,当然还有很多容器都会用到显式实例化

aea230805930bed8dca41761a1425d9.png

🖋️匹配规则


具体函数调用时,隐式生成的模板函数并不会最先被调用


假设我们已经在程序中写好了参数需要的函数,而同时模板也能生成参数需要的函数,此时编译,编译器会先寻找是否存在目标函数,如果有,编译器便不再根据函数模板生成函数,避免造成代码冗余


我们可以通过调试来观察到这一现象

2d89a3cd93611904b4d568f757ae9ec.png



🖋️注意事项


注意:


函数调用时,并非直接调用函数模板,而是调用编译器根据参数类型和模板生成的函数

使用模板是在麻烦编译器帮我们办事,实际事也是办成功的

当隐式实例化后的函数已存在时,不会去生成模板函数,而是直接使用已存在的函数

显式实例化后,编译器则会优先选择显式生成的普通函数

隐式生成的模板函数不存在类型隐式类型转换,显式后生成的是普通函数,可以隐式类型转换

模板中的参数类型不能为 strcut



template<struct T>  //这种定义是非法的

C++库中存在一个 swap 函数,它能实现所有数据类型的交换,其实它就是通过函数模板实现的

d48ef4e10070c00fa854655b2af17f9.png


📖类模板


模板除了可以用在函数上面外,还可以用在类上,此时称为 类模板


STL 库中的容器,都是 类模板 的形式,我们使用时,需要什么类型的 类,直接显式实例化为对应 模板类 即可


//简单演示下 STL 中的容器,这些都是类模板的实际运用
vector<int> v1; //实例化为整型顺序表类
list<double> l1;  //实例化为浮点型链表类


🖋️使用方法


类模板和函数模板有所不同,类模板只能显式实例化


//简单写一个栈模板
template<class T>
class Stack
{
public:
  //构造函数
  Stack(int capacity = 4);
  //析构函数
  ~Stack();
  //……
private:
  T* _pData;
  int _top;
  int _capacity;
};
//注意类模板中方法的实现方式!
//定义构造函数
template<class T>
Stack<T>::Stack(int capacity)
{
  _pData = new T[capacity]; //内存管理,一次申请4块空间
  _capacity = capacity;
  _top = 0;
}
//定义析构函数
template<class T>
Stack<T>::~Stack()
{
  delete[] _pData;  //注意:匹配使用
  _capacity = _top = 0;
}
//……



这就算 STL 库中 stack 的简陋版本,还有很多方法没实现,但大体逻辑都是如此

19203d0423e94333c63b09aa350ca4b.png

🖋️注意事项

类模板使用时需要注意一些问题:


模板类中的函数在定义时,如果没有在类域中,就需要通过 类模板+ 类域访问 的方式定义

类模板 不支持声明与定义分开在两个文件中实现,因为会出现链接错误


📘总结


以上就是关于 C++ 模板初阶 的全部内容了,模板是一个很实用的工具,它可以提高我们的编码效率,省去很多不必要的麻烦,善用模板,快乐编程!


如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!


如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正



目录
相关文章
|
23小时前
|
C++
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
【期末不挂科-C++考前速过系列P6】大二C++实验作业-模板(4道代码题)【解析,注释】
|
1天前
|
编译器 C语言 C++
c++初阶------类和对象(六大默认构造函数的揭破)-3
c++初阶------类和对象(六大默认构造函数的揭破)
|
1天前
|
编译器 C语言 C++
c++初阶------类和对象(六大默认构造函数的揭破)-2
c++初阶------类和对象(六大默认构造函数的揭破)
|
1天前
|
存储 编译器 C语言
c++初阶-------类和对象-2
c++初阶-------类和对象
|
1天前
|
安全 编译器 C语言
C++初阶------------------入门C++(三)
C++初阶------------------入门C++(三)
|
1天前
|
存储 Linux 编译器
C++初阶------------------入门C++(二)
C++初阶------------------入门C++(二)
|
1天前
|
编译器 C语言 C++
C++初阶------------------入门C++(一)
C++初阶------------------入门C++(一)
|
1天前
|
存储 算法 C++
详解C++中的STL(标准模板库)容器
【4月更文挑战第30天】C++ STL容器包括序列容器(如`vector`、`list`、`deque`、`forward_list`、`array`和`string`)、关联容器(如`set`、`multiset`、`map`和`multimap`)和容器适配器(如`stack`、`queue`和`priority_queue`)。它们为动态数组、链表、栈、队列、集合和映射等数据结构提供了高效实现。选择合适的容器类型可优化性能,满足不同编程需求。
|
3天前
|
运维 Serverless Go
Serverless 应用引擎产品使用之在阿里云函数计算中c++模板,将编译好的C++程序放进去部署如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
9 1
|
3天前
|
存储 C++
【C++模板】模板实现通用的数组
【C++模板】模板实现通用的数组