【C++】模板初阶入门:什么是模板?怎么用?这篇文章帮你打通 “第一关”
前言:在之前的文章中我们说过,C++是比C语言更加高级的语言,那么如何体现C++的高级呢?看完本篇文章会给你答案。下面就来探索C++模板的奥秘。
✨ 坚持用清晰易懂的图解+代码语言, 让每个知识点都简单直观!
🚀 个人主页 :MSTcheng · ZEEKLOG
🌱 代码仓库 :MSTcheng · Gitee
📌 专栏系列 :📖 《C语言》🧩 《数据结构》💡 《C++由浅入深》💬 座右铭 :“路虽远行则将至,事虽难做则必成!”
文章目录
一,什么是模板?
模板的概念:模板指预先设计好的标准化框架或结构,用于快速生成特定格式的内容。它通过固定部分(不变元素)和可变部分(占位符)的组合,提高重复性工作的效率。
下面再举一个例子让大家加深对模板的理解:
最近中秋节就要来了,在中秋节每家每户都会吃月饼,而这些月饼的形状是怎么做到统一大小的呢?答案是——模板。月饼生产场会根据不同的口味更换月饼内部的馅,但是不同口味的月饼都是使用一个摸具压出来的,从而就能获得形状大小均一样,口味不一样的月饼了。所以模板就可以抽象的理解成是一个磨具,每一种不同口味的月饼通过这一个摸具都能变成一个大小形状均一样的月饼。

二,模板的使用和作用
2.1泛型编程
在C语言阶段,如果我们想写一个同类型的交换函数是比较困难的,就像下面的代码:
voidSwap(int& left,int& right){int temp = left; left = right; right = temp;}voidSwap(double& left,double& right){double temp = left; left = right; right = temp;}voidSwap(char& left,char& right){char temp = left; left = right; right = temp;}这就是我们在C语言阶段所写的不同类型的交换函数。试想:如果有100种甚至更多的类型需要比较如果我们还要一个一个的去写的话,那么浪费人力不说效率是极低的。既然如此,那有没有什么更好的方法解决上面的问题呢?有的,在上面的代码中我们自己写的函数就相当于我们要自己用手捏出月饼的形状这效率是极低的,但如果我们有一个制作月饼的模板,将月饼团放上去压一下形状就出来了,这样能够极大的提高效率。同样,如果我们能够做出一个函数模板,我们只需要改类型就行函数,的内容不变这样也能达到上面的效果。
所以泛型编程就是模板的作用之一,它就像制作月饼的那个摸具一样,无论什么种类的月饼它都能压出来。
2.2模板的使用
//函数模板的格式使用 template关键字 typename定义的是模板参数template<typenameT>voidSwap( T& left, T& right){ T temp = left; left = right; right = temp;}注意:定义模板要以template关键字开头,尖括号内跟上模板参数类型模板参数以typename/class关键字开始。(类型模板参数代表未知的数据类型)
然后将函数内部有类型的地方使用模板参数替换,比如我的模板参数使用的是T,那么我定义的tmp的类型就由原来具体的int,double类型替换成T(未知类型)。
三、模板的分类
C++中的模板分成函数模板和类模板。函数模板:可以创建适用于多种数据类型的函数,无需为每种类型重复编写相同逻辑。类模板:可以创建适用于多种数据类型的类,同样也无需为每种类型重复编写相同的部分。
3.1函数模板
其实在上面的例子引用中我们就已经介绍了函数模板。但要注意的是:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
template<typenameT>voidSwap( T& left, T& right){ T temp = left; left = right; right = temp;}有了上面的认识,接着我们就来说说它的原理:
3.1.1函数模板的原理
函数模板的原理实际上跟我们前面说的用摸具制作月饼大同小异,函数模板更像是一张蓝图,它本身不是函数是编译器用使用方式产生特定具体类型函数的模具。

3.1.2模板的实例化
模板实例化:模板实例化就是一个得到具体类型的过程。比如函数模板的实例化就是将有具体类型的参数传给这个函数模板,让T有了确定的类型。比如:
template<classT> T Add(const T& left,const T& right){return left + right;}intmain(){int a1 =10, a2 =20;double d1 =10.0, d2 =20.0;//a1和a2是确定的类型int 因此将a1和a2传给Add函数 Add函数生成了一个int的比较函数这一过程就是函数模板的实例化过程Add(a1, a2);Add(d1, d2);//上面的传参要么都是int 要么都是double如果各传一个呢?//Add(a1,d2);//这时候编译器就会报错,因为我们此时提供了int和double两个参数而模板参数只定义了一个参数,换句话说我们定义的类型模板参数T不知道到底是接收int还是double 而且在模板中编译器一般不会进行类型转换操作。//所以要解决这一问题有两种方案:1,将其中一个强转(如下) 2,使用显示实例化Add(a,(int)d);return0;}上面函数模板的实例化是隐式实例化、隐式实例化实际上就是实参类型推演模板参数。隐式实例化会引发一个问题就是面对两个或两个以上的类型参数,在函数模板参数只有一个的情况下编译器会因为不知道接收哪个类型参数而报错。
解决这种问题的第二种方法就是显示实例化:
显示实例化:就是在函数名后的尖括号<>中指定模板参数的实际类型
intmain(void){int a1 =10, a2 =20;double d1 =10.0, d2 =20.0;//显示实例化(显示指定模板参数) Add<int>(a1, d1); Add<double>(a2,d2);return0;}显示实例化:实际上就是在尖括号内部指定模板参数,从而让模板生成指定的加法函数。这样编译器就能明确生成具体函数就不会报错了。
3.1.3模板参数的匹配原则
假设一个普通函数和一个函数模板同时存在会先调用哪一个呢?
#include<iostream>usingnamespace std;// 专门处理int的加法函数intAdd(int left,int right){//调用此函数前先打印一下该函数 cout <<"Add(int left, int right)"<< endl;return left + right;}// 通用加法函数template<classT> T Add(T left, T right){//调用此函数前先打印一下该函数 cout <<"T Add(T left, T right)"<< endl;return left + right;}intmain(){Add(1,2); cout << endl;//显示调用 Add<int>(1,2);return0;}
从上面的结果我们能很明显的看到:当普通函数与函数模板同时存在时编译器会优先调用普通函数。如果想要调用函数模板生成的函数就要使用显式实例化。
到这可能会有人有疑问:只要普通函数与函数模板同时存在一定优先调用普通函数吗?当然不是,如果模板可以产生一个具有更好匹配的函数, 那么将选择模板,比如:
// 专门处理int的加法函数intAdd(int left,int right){return left + right;}// 通用加法函数template<classT1,classT2> T1 Add(T1 left, T2 right){return left + right;}intmain(){Add(1,2);Add(1,2.2);return0;}在第二个Add函数的调用中,此时普通函数也可以调已经存在的Add函数用但是由于强转会丢失精度,而函数模板能实例化一个更加适合的函数,所以就优先调用板生成的函数。
注意:函数模板是不能进行类型转换的,普通函数可以自动进行类型转换
3.2类模板
3.2.1类模板的定义
template<classT1,classT2,...,classTn>class 类模板名 {// 类内成员定义....};类模板的定义与函数模板类似,template关键字尖括号内部跟类型模板参数,模板参数可以有多个、类内部的成员的类型根据需求替换成类型模板参数T。3.2.2类模板的实例化
#include<iostream>usingnamespace std;// 类模版template<typenameT>classStack{public:Stack(size_t capacity =4){ _array =new T[capacity]; _capacity = capacity; _size =0;}voidPush(const T& data);private: T* _array; size_t _capacity; size_t _size;};//声明和定义分离 要加tamplate不然编译器分不清是普通函数还是模板函数template<classT>voidStack<T>::Push(const T& data){// 扩容 _array[_size]= data;++_size;}intmain(){ Stack<int> st1;// int Stack<double> st2;// doublereturn0;}关于类模板有两点注意事项:类模板的实例化一定要显式实例化。所以类模板后要紧跟<>,然后将要实例化的类型放在尖括号内即可,注意类模板名字不是真正的类,实例化的结果才是真正的类。在类模板中,最好不要将类中成员函数的声明和定义分离。这样可能会引发链接错误。
| 以上就是本篇文章的所有内容了,感谢各位大佬观看,制作不易还望各位大佬点赞支持一下!有什么问题可以加我私信交流! |