ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【C++ 模板初阶】一篇搞懂:函数模板、实例化与类模板

【C++ 模板初阶】一篇搞懂:函数模板、实例化与类模板 【C 模板初阶】一篇搞懂函数模板、实例化与类模板本文是模板系列的第一篇初阶只讲最核心、最常用的部分。后面的进阶内容模板特化、非类型模板参数、模板的分离编译、可变参数模板等会单独成篇敬请期待。如果你是刚接触模板的小白跟着本文一步一步走保证能看懂、能上手。一、先说两句为什么要有模板很多小伙伴刚学完函数重载会有一个疑问我写一个Swap交换函数int要写一遍double要写一遍string又要写一遍……这也太累了代码全是重复的看着都头疼。有没有一种办法让我只写一遍就能通吃所有类型呢有这就是泛型编程。泛型编程编写与类型无关的通用代码让代码在使用时再绑定具体类型从而实现代码复用。而模板Template就是泛型编程的基础。你把它想象成一个模具就行模具本身不是产品但往里一浇就能浇出对应类型的产品。简单来说模板写一次类型随便换。二、函数模板2.1 概念函数模板代表的是一个函数家族它本身与类型无关在使用时会被参数化——编译器会根据你传入的实参类型自动生成一个特定类型的函数版本。2.2 格式函数模板的格式长这样templatetypenameT1,typenameT2,...,typenameTn返回值类型 函数名(参数列表){...}一个经典的交换函数模板templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}这里有几个易错点先给你打个预防针typename是用来定义模板参数的关键字也可以换成class两者在模板参数这里等价。⚠️千万不能用struct代替class会编译报错这个坑不少人踩过。2.3 一句话搞懂原理模板不是函数很多人会问模板到底是不是函数记住函数模板只是一个蓝图它本身并不是函数。在编译阶段当你真正调用模板时编译器会根据你传入的实参类型去推演deduce模板参数T然后现场生成一个对应类型的真实函数。举个例子当你用double类型调用这个模板时编译器把T推演成double然后生成一份专门处理double的代码再去调用它。所以一句话概括编译阶段根据传入模板的类型编译器现场生成一个函数模板本身不是函数只是一个生产函数的模板机。三、函数模板的实例化用不同类型的参数去使用同一个函数模板这个过程就叫做模板实例化。实例化分为两种隐式实例化和显式实例化。3.1 隐式实例化看个加法模板templateclassTTAdd(constTleft,constTright){returnleftright;}intmain(){inta110,a220;doubled110.0,d220.0;Add(a1,a2);// 编译器把 T 推演成 intAdd(d1,d2);// 编译器把 T 推演成 doublereturn0;}这种不用你管编译器自己根据实参推导类型的方式就是隐式实例化。⚠️注意下面这个大坑Add(a1,d1);// ❌ 编译报错为什么报错因为在编译期间编译器看到这个调用要通过实参a1把T推演成int又通过实参d1把T推演成double。可模板参数列表里只有一个T编译器犯了选择困难症——到底该把T定成int还是double没法确定直接报错。划重点在模板中编译器一般不会帮你做隐式类型转换。这时候你有两种解决办法方法一自己强制类型转换Add(a1,(int)d1);// 手动把 double 强转成 int方法二使用显式实例化下面马上讲。另外提醒一句隐式实例化只有在T作为传入参数时才能推导。如果T压根没出现在参数列表里编译器就没法看菜下碟这时候就必须用显式实例化去规定T的类型。3.2 显式实例化显式实例化在函数名后面的中直接指定模板参数的实际类型。intmain(){inta10;doubleb20.0;Addint(a,b);// 显式规定 T 是 intreturn0;}这里把T明确指定成了int。如果传入的类型不匹配编译器会尝试做一次隐式类型转换要是转换不了就直接报错。简单记能推导就隐式推不了就显式。3.3 模板参数的匹配原则重要当一个非模板函数和同名函数模板同时存在时编译器会遵循下面这几条原则一个非模板函数可以和同名函数模板同时存在而且模板还能被实例化成这个非模板函数。如果其他条件都相同调用时会优先调用非模板函数不会从模板再生成一个实例。如果模板能产生一个匹配更好的版本那么就选模板。看个例子就明白了// 专门处理 int 的加法函数非模板intAdd(intleft,intright){returnleftright;}// 通用加法模板templateclassT1,classT2T1Add(T1 left,T2 right){returnleftright;}voidTest(){Add(1,2);// 和非模板函数完全匹配 → 调用非模板函数Add(1,2.0);// 模板能生成更匹配的版本 → 选择模板生成新函数}Add(1, 2)两个参数都是int和现成的非模板函数完全匹配直接用现成的没必要再实例化模板。Add(1, 2.0)一个int一个double非模板函数对不上但模板可以现场生成一个更合适的版本于是选模板。这一条完全匹配优先用非模板模板更合适才用模板的规则很多面试题会考务必记牢。四、类模板初阶函数有模板类当然也有模板。比如我们要写一个通用的Stack栈不想为int、double各写一遍就可以用类模板。4.1 定义格式templateclassT1,classT2,...,classTnclass类模板名{// 类内成员定义};一个栈的类模板示例templatetypenameTclassStack{public:Stack(size_t capacity4):_array(newT[capacity]),_capacity(capacity),_size(0){}voidPush(constTdata);private:T*_array;size_t _capacity;size_t _size;};4.2 关于声明和定义分离这里有个大坑⚠️ 注意模板不建议把声明和定义分离到两个文件.h和.cpp里否则会出现链接错误。因为模板只有在被实例化时才生成代码而.cpp文件里如果没有具体实例化编译器就不知道要为它生成啥链接时自然找不到定义。正确姿势声明和定义写在同一个文件里。哪怕要分离也只在同一文件内分离并且每个成员函数定义时都要单独带模板参数templateclassTvoidStackT::Push(constTdata){// 这里一般会有扩容逻辑先省略_array[_size]data;_size;}注意定义处StackT这个写法——成员函数属于类模板StackT而不是Stack。4.3 类模板的实例化类模板的实例化和函数模板不太一样函数模板很多时候靠推导隐式而类模板必须在名字后面跟把具体类型放进去手动指定。intmain(){Stackintst1;// 存放 int 的栈Stackdoublest2;// 存放 double 的栈return0;}这里再强调一个很关键的概念很多人一开始会懵Stack只是类模板的名字它本身不是一个真正的类Stackint才是真正的类型。所以定义对象时一定要写Stackint这种带尖括号的完整形式而不是只写Stack。五、小结与进阶预告来把今天的内容串一遍知识点一句话记忆泛型编程写一份与类型无关的通用代码实现代码复用模板泛型编程的基础是蓝图/模具本身不是函数也不是类函数模板编译时根据实参类型推演T现场生成对应函数隐式实例化编译器根据实参自动推导类型显式实例化在里手动指定类型如Addint(...)匹配原则完全匹配优先非模板模板更合适才用模板类模板Stackint才是真正的类型Stack只是名字本文属于模板系列初阶篇我们把最核心的地基打好了。但是模板的世界远不止这些。后面的进阶篇还会带你搞定非类型模板参数模板参数不只是类型模板的特化全特化 偏特化模板的分离编译extern template那些事可变参数模板...的魔法模板与 STL 的关系如果觉得这篇对你有帮助欢迎点赞、收藏、关注进阶篇正在路上我们不见不散参考本人 C 模板初阶学习笔记整理。
返回列表