当前位置: 首页 > news >正文

C++:模板精讲

泛型编程

当我们实现一个交换函数,想要实现不同类型的交换,可以使用函数重载:

#include<iostream>using namespace std;voidSwap(int&left,int&right){inttemp=left;left=right;right=temp;}voidSwap(char&left,char&right){chartemp=left;left=right;right=temp;}voidSwap(double&left,double&right){doubletemp=left;left=right;right=temp;}

但是这样有几个不好的地方:

  • 重载的函数仅仅是类型不同,代码复用率较低,只要有新类型出现,就需要用户自己增加对应的函数。
  • 代码的可维护性比较低,一个出错可能所有的重载均出错。

所以在C++中,就出现了模板这个概念,编写与类型无关的代码,是代码复用的一种基础,模板是泛型编程的基础。

函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
被参数化的这个过程叫做函数模板的示例化

函数模板的格式

template<typenameT1,typenameT2>

即返回类型 + 函数名(参数列表){}

#include<iostream>usingnamespacestd;template<typenameT>voidSwap(T&left,T&right){T temp=left;left=right;right=temp;}intmain(){chara='x';charb='c';Swap(a,b);cout<<a<<" "<<b<<endl;return0;}

注意
typename 是用来定义模板参数关键字,也可以使用class,切记,不能使用struct代替class。typename和class 只有一种情况不能等价,
(比如 T::iterator 前面):只能用 typename,说明这是一个类型

template<classT>voidfunc(){// 这里必须写 typename!不能写 class!typenameT::iterator it;}

函数模板的原理

函数模板是一个标准复制件,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的摸具。所以模板就是将本来我们要重复做的事交给了编译器。
在编译器阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。函数模板实例化分为:隐式实例化和显式实例化

  • 隐式实例化:
#include<iostream>usingnamespacestd;template<classT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){inta1=10,a2=20;doubleb1=5.2,b2=13.14;cout<<Add(a1,a2)<<endl;cout<<Add(b1,b2)<<endl;return0;}
  • 显式实例化:
#include<iostream>usingnamespacestd;template<classT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){inta=10;doubleb=20.0;//显示实例化cout<<Add<int>(a,b)<<endl;return0;}

注意:如果在实例化中,类型不匹配,编译器一般不会进行类型转化操作,因为一旦转化出现问题,编译器就需要背锅。

模板参数的匹配机制

  • 1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被示例化为这个非模板函数。
#include<iostream>usingnamespacestd;intAdd(intleft,intright){returnleft+right;}template<classT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){Add(1,2);Add<int>(1,2);}
  • 2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择该模板。
#include<iostream>usingnamespacestd;intAdd(intleft,intright){returnleft+right;}template<classT1,classT2>T1Add(constT1&left,constT2&right){returnleft+right;}intmain(){Add(1,2);Add(1,2.0);return0;}

模板函数不允许自动类型转化,但是普通函数可以进行自动类型转化。

类模板

类模板的定义格式

template<classT1,clasT2,......classTn>class类模板名{//类内成员变量}
#include<iostream>usingnamespacestd;template<typenameT>classStack{public:Stack(size_t capacity=4){_array=newT[capacity];_capacity=capacity;_size=0;}private:T*_array;size_t _capacity;size_t _size;};intmain(){Stack<int>st1;Stack<double>st2;return0;}

类模板的实例化

类模板的实例化和函数模板的实例化不同,类模板实例化需要在类模板名字后跟< >,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

非类型模板参数

模板参数分类:
类型形参和非类型形参
类型形参:出现在模板参数列表中,跟在class 或者typename之类的参数类型名称之后。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可以将该参数当成常量来使用。

#include<iostream>usingnamespacestd;namespaceyjjh{template<classT,size_t N=10>classarray{public:array(){for(inti=0;i<N;i++){_array[i]=T();}_size=N;}T&operator[](size_t index){return_array[index];}constT&operator[](size_t index)const{return_array[index];}size_tsize()const{return_size;}boolempty()const{return0==_size;}private:T _array[N];size_t _size;};}intmain(){yjjh::array<int,5>a1;cout<<a1[4]<<endl;return0;}

注意

  • 浮点数,类对象以及字符串是不允许作为非类型模板参数的
  1. 浮点数在计算机里是二进制近似存储,模板要求:不同值必须生成不同类
    浮点数因为精度问题,做不到绝对安全区分
  2. 类对象太复杂,有构造、析构、成员变量编译器无法在编译期把一个完整对象编码进类型
  3. 字符串是数组 / 指针,地址不确定,编译器无法把不确定地址当作模板常量
  • 非类型模板参数必须在编译期间就确认结果

模板的特化

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。

#include<iostream>usingnamespacestd;classDate{public:Date(intyear=1900,intmonth=1,intday=1){_year=year;_month=month;_day=day;}booloperator<(constDate&d2){if(_year>d2._year){returnfalse;}elseif(_year==d2._year){if(_month>d2._month){returnfalse;}elseif(_month==d2._month){return_day<d2._day;}}returntrue;}private:int_year;int_month;int_day;};template<classT>boolLess(T left,T right){returnleft<right;}intmain(){cout<<Less(1,2)<<endl;Dated1(2026,4,23);Dated2(2025,6,7);cout<<Less(d1,d2)<<endl;cout<<Less(p1,p2)<<endl;return0;}

可以看到,Less大多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部没有比较p1和p2指向的对象内容,而比较的是p1 和 p2 指针的地址,这就无法到达预期而错误。
所以我们需要模板特化

模板特化

模板特化:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
函数模板特化步骤

  • 1.必须要先有基础的函数模板
  • 2.关键字template后面接一对尖括号<>
  • 3.函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 4.函数形参表:必须要和模板函数的基础函数类型完全相同,如果不同编译器可能会报一些奇怪的错误
#include<iostream>usingnamespacestd;classDate{public:Date(intyear=1900,intmonth=1,intday=1){_year=year;_month=month;_day=day;}booloperator<(constDate&d2){if(_year>d2._year){returnfalse;}elseif(_year==d2._year){if(_month>d2._month){returnfalse;}elseif(_month==d2._month){return_day<d2._day;}}returntrue;}private:int_year;int_month;int_day;};template<classT>boolLess(T left,T right){returnleft<right;}//对Less模板进行特化template<>boolLess<Date*>(Date*left,Date*right){return*left<*right;}intmain(){cout<<Less(1,2)<<endl;Dated1(2026,4,23);Dated2(2025,6,7);cout<<Less(d1,d2)<<endl;Date*p1=&d1;Date*p2=&d2;cout<<Less(p1,p2)<<endl;return0;}

注意
一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。该实现简单明了,对于一些参数类型复杂的函数模板,特化后代码可读性不强。

类模板特化

  • 必须要先有基础的函数模板
  • 关键字template后面接一对尖括号<>
  • 类名后面接一对尖括号<>,填上类型
全特化

全特化是将模板参数列表中所有的参数都确定化

#include<iostream>usingnamespacestd;template<classT1,classT2>classData{public:Data(){cout<<"Data<T1,T2>"<<endl;}private:T1 _d1;T2 _d2;};template<>classData<int,char>{public:Data(){cout<<"Data<T1,T2>"<<endl;}private:int_d1;char_d2;};
  • 偏特化
    偏特化:任何针对模板参数进一步进行条件限制设计的特化版本。
    1.部分特化
usingnamespacestd;template<classT1,classT2>classData{public:Data(){cout<<"Data<T1,T2>"<<endl;}private:T1 _d1;T2 _d2;};template<classT1>classData<T1,int>{public:Data(){cout<<"Data<T1,int>"<<endl;}private:T1 _d1;int_d2;};

2.参数更进一步的限制

#include<iostream>usingnamespacestd;template<classT1,classT2>classData{public:Data(){cout<<"Data<T1,T2>"<<endl;}private:T1 _d1;T2 _d2;};template<typenameT1,typenameT2>classData<T1*,T2*>{public:Data(){cout<<"Data<T1*,T2*>"<<endl;}private:T1 _d1;T2 _d2;};template<typenameT1,typenameT2>classData<T1&,T2&>{public:Data(){cout<<"Data<T1&,T2&>"<<endl;}private:T1 _d1;T2 _d2;};

类模板特化的应用

有如下专门按照小于补交的类模板Less:

#include<iostream>#include<vector>#include<algorithm>usingnamespacestd;classDate{friendostream&operator<<(ostream&out,constDate&d);public:Date(intyear=1900,intmonth=1,intday=1){_year=year;_month=month;_day=day;}booloperator<(constDate&d2)const{if(_year>d2._year){returnfalse;}elseif(_year==d2._year){if(_month>d2._month){returnfalse;}elseif(_month==d2._month){return_day<d2._day;}}returntrue;}private:int_year;int_month;int_day;};ostream&operator<<(ostream&out,constDate&d){out<<d._year<<"-"<<d._month<<"-"<<d._day;returnout;}template<classT>structLess{booloperator()(constT&x,constT&y)const{returnx<y;}};

模板的分离编译

假如有一下场景,模板的声明与定义分离开,在头文件中进行声明,在源文件中完成定义:会直接报错

// a.htemplate<classT>voidfunc(T a);// 声明// a.cpptemplate<classT>voidfunc(T a){}
  • 普通函数 / 类:编译.cpp就生成具体机器码,声明放头文件、定义放源文件,支持分离编译。
  • 模板(函数 / 类)本质是代码蓝图,没有具体类型,不会主动生成实体代码。
    模板只有在 被指定类型使用(实例化) 时,才会替换类型、生成对应代码。
  • 若分离编译:
    模板定义写在.cpp,外部文件只引入头文件声明;
    编译各自文件时,两边都没法实例化生成代码,链接阶段找不到函数实体,报错。

解决方法:

  • 将声明和定义放到一个.h文件中
  • 模板定义的位置显示实例化(不推荐使用)

模板总结

优点

  • 模板复用了代码,节省资源,更快的迭代开发
  • 增强了代码的灵活性

缺点

  • 模板会导致代码膨胀问题,也会导致编译时间变长
  • 出现模板编译错误时,错误信息非常凌乱,不一定位错误
http://www.jsqmd.com/news/695829/

相关文章:

  • Aetina AIE-CP1A-A1边缘AI系统解析与工业应用
  • CUDA 13.0与Jetson Thor平台:边缘计算新纪元
  • YOLOv8炼丹笔记:用ECA注意力模块提升小目标检测精度(附三种YAML配置)
  • Pytest及相关测试工具实战指南
  • ChatGPT Images 2.0 技术升级与全场景落地实操指南
  • 深度学习实现图像自动描述生成的技术解析
  • Linux kernel 5.10+下C++ MCP网关偶发丢包率突增300%?eBPF trace发现glibc malloc隐式锁争用黑洞
  • 云服务器配置远程桌面
  • AI 多智能体 Agent+Unity 虚拟仿真:数字孪生 3D 场景智能调度教程
  • 神经形态硬件在强化学习机器人控制中的低功耗实践
  • 我们有最牛的数据系统,却输给了一个“没人回复的推送”
  • DeepEar开源对话系统:从语音识别到多轮对话的完整实践指南
  • VSCode实时协作优化进入深水区:E2E加密延迟、光标冲突消解算法、离线变更合并队列——这3个底层机制你必须今天就掌握
  • Hyperf 开箱即用的多语言、多币种、多时区、国际支付、全球物流PHP标准化组件
  • 【进程间通信】————匿名管道、模拟实现进程池
  • NREL风速数据API参数详解:从wkt坐标到interval间隔,新手避坑指南
  • 机器学习模型方差问题分析与实战解决方案
  • 嵌入式——认识电子元器件——三极管系列
  • 以线性代数的行列式理解数学应用备忘
  • 从 LangGraph 死循环到 Skill 驱动:我把 Text2SQL 升级成了SKILL模式
  • 2026宝鸡高端装修设计实测:宝鸡市,宝鸡,渭滨宝鸡装修(核心词),宝鸡靠谱家装公司,排行一览! - 优质品牌商家
  • 2026年比较好的硅酸钙板建材专业公司推荐 - 品牌宣传支持者
  • 差分放大器在高速信号链中的关键作用与设计实践
  • keil未指定 PY32F0 具体芯片型号导致编译报错及无法烧录问题
  • 为什么92%的CVE-2025高危漏洞仍源于C内存错误?——2026年NASA、Linux内核与AUTOSAR联合验证的4类零容忍写法
  • 数据标准:梳理业务主题、对象和事件的粒度应如何把握(干货)
  • 港科大DeepTech 20| AI驱动的自动化智能正畸治疗方案设计系统
  • 2026年儿童防开启包装测试审核应对机构top5排行:reach检测,tds报告,检测认证,玩具检测,优选推荐! - 优质品牌商家
  • 统计学与机器学习:差异、融合与应用实践
  • 为什么92%的C项目仍在用不安全strcpy?2026规范强制迁移路线图,含37个API替换对照表