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

重生之从0开始学习c++之模板初级

1. 泛型编程 —— 为什么需要模板?

如何实现一个通用的交换函数呢?

voidSwap(int&left,int&right){inttemp=left;left=right;right=temp;}voidSwap(double&left,double&right){doubletemp=left;left=right;right=temp;}voidSwap(char&left,char&right){chartemp=left;left=right;right=temp;}

因为c++支持函数重载,所以如果我们想用不同类型的参数,是不是可以这么写啊,但是这样写是不是有点麻烦和冗余啊,因为它们的逻辑完全相同,仅仅是类型不同。

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
就像这样:

┌─────────────────────────────────────────────────────────────┐ │ 模具(模板)│ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ │ │voidSwap(T&left,T&right)│ │ │ │{│ │ │ │ T temp=left;│ │ │ │ left=right;│ │ │ │ right=temp;│ │ │ │}│ │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ │ │ ▼ 倒入绿色液体 ▼ 倒入蓝色液体 ▼ 倒入红色液体 ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Swap │ │ Swap │ │ Swap │ │<int>│ │<double>│ │<char>│ └─────────┘ └─────────┘ └─────────┘

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。

核心思想:泛型编程——编写与类型无关的通用代码,由编译器根据实际使用时的类型,自动生成针对该类型的代码。

2. 函数模板

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

2.1 函数模板的语法

template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}

template<typenameT>// T 是模板参数,可以是 typename 或 classvoidSwap(T&left,T&right){T temp=left;left=right;right=temp;}

2.2 函数模板的原理

函数模板本身不是函数,它只是一个蓝图。编译器遇到函数模板的调用时,才会根据实参类型,生成一个具体的函数。这个过程叫做模板实例化。

流程图解:编译器在编译期的推演过程

源代码: ─────────────────────────────────────────────────────────────intmain(){doubled1=2.0,d2=5.0;Swap(d1,d2);// 调用点1inti1=10,i2=20;Swap(i1,i2);// 调用点2chara='0',b='9';Swap(a,b);// 调用点3}───────────────────────────────────────────────────────────── │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 编译器推演: │ │ 编译器推演: │ │ 编译器推演: │ │ 实参类型double│ │ 实参类型int│ │ 实参类型char│ │ 推导 T=double│ │ 推导 T=int│ │ 推导 T=char│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ 生成的函数: │ │ 生成的函数: │ │ 生成的函数: │ │voidSwap(│ │voidSwap(│ │voidSwap(│ │double&left,│ │int&left,│ │char&left,│ │double&right │ │int&right)│ │char&right)│ │){...}│ │{...}│ │{...}│ └─────────────────┘ └─────────────────┘ └─────────────────┘

编译器为每种不同的类型组合,生成一份独立的函数代码。最终的可执行文件中,包含了Swap<double>Swap<int>Swap<char>三个具体的函数,就像你手动写了三个重载一样。

2.3 函数模板的实例化

用具体类型使用函数模板,称为实例化。分为两种:

  1. 隐式实例化

让编译器自动根据实参类型推导模板参数。

template<classT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){Add(1,2);// 两个实参都是 int,推导 T = intAdd(1.0,2.0);// 两个实参都是 double,推导 T = double// Add(1, 2.0); // 编译错误!一个 int,一个 double,编译器推导冲突}

错误图解:

调用Add(1,2.0): │ ├─ 实参1(1)类型为int→ 推导 T=int├─ 实参2(2.0)类型为double→ 推导 T=double│ └─ 冲突!模板参数列表中只有一个 T,编译器无法确定 T 到底是int还是double。 编译器不会进行隐式类型转换,因为转换可能丢失数据,编译器不背这个锅。

解决方案:

Add(a,(int)d);// 方案1:用户手动强转Add<int>(a,d);// 方案2:显式实例化(推荐)
  1. 显式实例化

在函数名后用 <类型> 强制指定模板参数。

Add<int>(10,20.0);// 强制 T = int,20.0 会被隐式转换为 int

流程图:

调用Add<int>(a,b): │ ▼ ┌─────────────────────────────────────────┐ │ 编译器:用户已指定 T=int,不用推导了 │ │ 实参a(int)→ 匹配 │ │ 实参b(double)→ 尝试隐式转换为int│ │ (如果能转就编译通过,否则报错) │ └─────────────────────────────────────────┘ │ ▼ 生成函数:intAdd(constint&left,constint&right)

2.4 模板参数的匹配原则

原则一:非模板函数可以和同名模板函数共存

// 非模板函数(专门处理 int)intAdd(intleft,intright){returnleft+right;}// 模板函数(通用版本)template<classT>TAdd(T left,T right){returnleft+right;}

原则二:优先调用非模板函数,除非模板能生成更好的匹配

Add(1,2);// 调用非模板函数(完全匹配,且非模板优先)Add<int>(1,2);// 强制调用模板实例化的版本Add(1,2.0);// 非模板不匹配(参数类型不同),模板可以生成更好的匹配(如果模板有两个参数)

决策流程图:

遇到函数调用Add(1,2): │ ├─ 查找同名非模板函数 → 找到intAdd(int,int)→ 完全匹配 → 调用 │ └─ 即使模板能生成完全相同的函数,也不考虑,非模板优先 遇到函数调用Add(1,2.0): │ ├─ 查找同名非模板函数 →intAdd(int,int)不匹配(第二个参数类型不对) │ └─ 查找模板 → 若有template<classT1,classT2>版本,可生成匹配函数 → 调用模板实例化版本

原则三:模板函数不允许自动类型转换,普通函数可以

voidfunc(intx,inty){}// 普通函数template<classT>voidfunc(T x,T y){}// 模板函数func(1,'a');// 调用普通函数:'a' 自动转换为 int(ASCII 97)// 模板函数不会考虑,因为 char 和 int 推导冲突

3. 类模板

3.1 为什么需要类模板?

以 Stack(栈)为例,我们需要存储 int 的栈,也需要存储 double、string 的栈。如果不用模板,要么为每种类型写一个类,要么用 void* 或继承(不类型安全)。

类模板就是类的模具。

3.2 类模板的定义格式

template<classT1,classT2,...,classTn>class类模板名{// 类内成员定义};

我们来举一个我们很熟练的栈的例子:

template<classType>classStack{public:Stack(intcapacity=4):_arr(newType[capacity]),_size(0),_capacity(capacity){}~Stack(){delete[]_arr;_arr=nullptr;_size=_capacity=0;}voidPush(constType&x){if(_capacity==_size){Type*tmp=newType[_capacity*2];memcpy(tmp,_arr,sizeof(Type)*_size);delete[]_arr;_arr=tmp;_capacity=_capacity*2;}_arr[_size++]=x;}voidPrint()const{for(inti=0;i<_size;++i){cout<<_arr[i]<<" ";}}private:Type*_arr;int_size;int_capacity;};intmain(){Stack<int>st1;st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);st1.Print();return0;}

关键点:

  • 类模板名字是 Stack,但真正的类型是Stack<int>Stack<double>等。
  • 成员函数在类外定义时,必须写成template<class T> void Stack<T>::Push(...)
  • 模板的声明和定义通常不分离到 .h 和 .cpp 两个文件,否则会导致链接错误(原因涉及模板实例化的编译模型,后面会深入)。

3.3 类模板的实例化

类模板必须显式实例化(不能像函数模板那样隐式推导)。

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

Stack<int>st1;// 实例化出一个存储 int 的栈类Stack<double>st2;// 实例化出一个存储 double 的栈类
程序代码段(编译后): ┌───────────────────────────────────────────────────────────┐ │ Stack<int>类(编译器生成) │ │-_array:int*│ │-Push(constint&)│ ├───────────────────────────────────────────────────────────┤ │ Stack<double>类(编译器生成) │ │-_array:double*│ │-Push(constdouble&)│ └───────────────────────────────────────────────────────────┘ ↑ ↑ │ │ 使用 Stack<int>st1 使用 Stack<double>st2
http://www.jsqmd.com/news/659434/

相关文章:

  • 2026玻璃钢地埋式管道厂家推荐 河北博翔产能领先+专利护航+服务全面 - 爱采购寻源宝典
  • Stable Diffusion Anything V5保姆级教学:快速搭建AI绘画平台
  • 当Copilot写出恶意反序列化代码时——智能代码生成安全风险评估的“黄金45分钟”响应协议(含SAST+DAST+LLM-Sandbox三重验证机制)
  • Golang go mod vendor怎么用_Golang vendor教程【必备】
  • 不用人类训练?这款开源大模型已开启自我进化
  • 全栈开发vs垂直领域:2026收益对比
  • 2026风道加热器厂家推荐排行榜江苏中仁以产能、专利、服务三维度领跑全国 - 爱采购寻源宝典
  • 别再手动拖模型了!Babylon.js Scene Loader 动态注册与按需加载实战(附NPM最佳配置)
  • 2026排水沟厂家推荐排行榜河北欧意科技领衔,产能与专利双优认证 - 爱采购寻源宝典
  • Phi-4-mini-reasoning百度SEO适配:技术博文如何用其生成高质量内容
  • AISQL生成不是噱头,是生产力革命:37个真实生产环境SQL生成失败案例全复盘
  • 双膜储气柜:专业生产与品质保障
  • Pixel Couplet Gen惊艳案例:海外华人社区用Pixel Couplet Gen传播春节文化
  • 苏州大学联合百度提出Flux Attention
  • 2026 三相油浸式变压器 厂家推荐 权威榜单(产能/专利/质量三维度对比) - 爱采购寻源宝典
  • Qwen3-Reranker应用案例:AI编程助手中的代码片段语义重排序实践
  • Jmeter 的使用
  • 2026电加热器厂家推荐排行榜从产能规模到专利技术权威解析 - 爱采购寻源宝典
  • MedGemma-X创新架构:图神经网络在医疗影像中的应用
  • 造相-Z-Image常见问题解决:RTX 4090部署、生成、优化全攻略
  • 2026在线监测仪厂家推荐排行榜产能、专利、质量三维度权威对比 - 爱采购寻源宝典
  • 2026玻璃钢树篦子厂家推荐 河北员力盛世以产能、专利、服务三维度领跑行业 - 爱采购寻源宝典
  • Bugku MISC 键盘 zip伪加密(超详细题解 WP)
  • 2026铜芯电力电缆厂家推荐排行榜产能与专利双优企业领衔 - 爱采购寻源宝典
  • 周红伟:Hermes vs OpenClaw:到底谁代表了Agent的下一阶段?
  • wan2.1-vae实操手册:解决人物变形、模糊、水印等6类常见问题
  • 2026蜂窝板厂家推荐排行榜产能、专利、服务三维度权威解析 - 爱采购寻源宝典
  • 网络存储方式
  • 吉林大学、阿里云等:为什么AI挑选训练数据时总爱“偏心“长句子?
  • RHEL虚拟机安装(red hat_6.10),RHEL 6.10 镜像百度云下载,VMware 安装 RHEL 6.10