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

【C++】模板初阶: 解析模板原理、实例化与特化

📌 相关专栏

  • 【Linux专栏】
  • 【C语言专栏】
  • 【测试专栏】
  • 【MySQL专栏】
  • 【C++ 专栏】

📌 相关文章推荐

  • 【C++】STL:从零掌握STL容器特性与实战用法
  • 【C++】C++类与对象2:C++构造函数、运算符重载与流输入输出全面解析
  • 【测试】一文吃透软件测试全分类,入门必懂核心体系
  • 【Linux】一文搞懂HTTP协议:概念、报文格式与极简服务器实现

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


文章目录

  • 前言
  • 一、函数模板
    • 1.1 为什么需要函数模板?
  • 1.2 函数模板的语法
    • 1.3 模板的使用
  • 二、模板参数推导与实例化
    • 2.1 隐式实例化
    • 2.2 参数不匹配时的处理
    • 2.3 多类型模板参数
    • 2.4 显式实例化
  • 三、模板重载与匹配规则
    • 3.1 函数模板与普通函数可以重载
    • 3.2 匹配优先级
  • 四、类模板
    • 4.1 类模板的定义
    • 4.2 类模板成员函数的类外定义
    • 4.3 类模板的使用(显式实例化)
  • 五、模板的注意事项
    • 5.1 声明与定义分离的问题
    • 5.2 模板的编译原理
    • 5.3 模板的缺点
  • 六、完整示例:通用Stack类
  • 七、知识点汇总
  • 八、常见面试题

前言

在C语言中,如果我们要写一个交换两个整数的函数,再写一个交换两个浮点数的函数,我们只能写两个不同名的函数,或者用宏(但宏有很多坑)。

C++提供了模板来解决这个问题。有了模板,我们就可以写出类型相关的通用代码。

🐾这一篇我们来学习:

  • 函数模板:如何写一个通用的Swap函数
  • 模板参数推导:编译器如何自动推断类型
  • 显式实例化:强制指定模板参数类型
  • 类模板:如何写一个通用的Stack容器

🐶 🐾 ✨ 🐾 🐶


一、函数模板

1.1 为什么需要函数模板?

看看这个例子:我们需要交换两个变量的值,但int、double、char都需要写一个版本。

// 代码重复严重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;}

🐾函数模板

// 一个模板搞定所有类型template<typenameT>voidSwap(T&left,T&right){T temp=left;left=right;right=temp;}

1.2 函数模板的语法

// template 关键字 + <typename T> 或 <class T>template<typenameT>voidSwap(T&x,T&y){T tmp=x;x=y;y=tmp;}// 多个模板参数template<typenameT1,typenameT2>voidfunc(constT1&x,constT2&y){// ...}

注意typename和class在模板参数中完全等价,没有区别。

template<typenameT>// 推荐(更语义化)template<classT>// 也可以(C++早期用法)

1.3 模板的使用

intmain(){inti=1,j=2;doublem=1.1,n=2.2;Swap(i,j);// 编译器推导 T = intSwap(m,n);// 编译器推导 T = double// Swap(i, n); // 错误!T被推导成int还是double?矛盾return0;}

🐶 🐾 ✨ 🐾 🐶


二、模板参数推导与实例化

2.1 隐式实例化

编译器会根据你传入的实参类型,自动推导模板参数:

template<typenameT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){inta1=10,a2=20;doubled1=10.1,d2=20.2;Add(a1,a2);// 隐式实例化:T → intAdd(d1,d2);// 隐式实例化:T → doublereturn0;}

2.2 参数不匹配时的处理

编译器根据你传入的实参类型,自动推导模板参数:

Add(a1,d1);// 错误:T被推导成int还是double?

🐾
解决方法1强制类型转换

cout<<Add(a1,(int)d1)<<endl;// 都转成intcout<<Add((double)a1,d1)<<endl;// 都转成double

🐾

解决方法2显式实例化(推荐)

cout<<Add<int>(a1,d1)<<endl;// 明确指定 T = intcout<<Add<double>(a1,d1)<<endl;// 明确指定 T = double

2.3 多类型模板参数

template<typenameT1,typenameT2>T1Add(constT1&left,constT2&right){returnleft+right;}intmain(){inta1=10;doubled1=20.2;cout<<Add(a1,d1)<<endl;// T1=int, T2=double,返回intreturn0;}

2.4 显式实例化

template<typenameT>T*func1(intn){returnnewT[n];}intmain(){// 无法推导T,必须显式指定int*p1=func1<int>(10);// T → intdouble*p2=func1<double>(10);// T → doubledelete[]p1;delete[]p2;return0;}

🐶 🐾 ✨ 🐾 🐶


三、模板重载与匹配规则

3.1 函数模板与普通函数可以重载

// 函数模板template<typenameT>TAdd(constT&left,constT&right){cout<<"template Add: ";returnleft+right;}// 普通函数(特化版本)intAdd(constint&x,constint&y){cout<<"normal Add: ";return(x+y)*10;}intmain(){inta1=10,a2=20;cout<<Add(a1,a2)<<endl;// 输出:normal Add: 300// 普通函数优先级更高cout<<Add<int>(a1,a2)<<endl;// 输出:template Add: 30// 显式指定<>,强制调用模板doubled1=1.1,d2=2.2;cout<<Add(d1,d2)<<endl;// 输出:template Add: 3.3// 没有匹配的普通函数,调用模板return0;}

3.2 匹配优先级

优先级匹配规则
1(最高)完全匹配的普通函数
2通过模板实例化得到匹配函数
3(最低)通过类型转换匹配

🐶 🐾 ✨ 🐾 🐶


四、类模板

4.1 类模板的定义

template<typenameT>classStack{public:Stack(intn=4):_array(newT[n]),_size(0),_capacity(n){}~Stack(){delete[]_array;_array=nullptr;_size=_capacity=0;}voidPush(constT&x);private:T*_array;size_t _capacity;size_t _size;};

4.2 类模板成员函数的类外定义

关键类外定义时需要加上template<typename T>,并用类名<T>::指定作用域。

// 类外定义成员函数template<typenameT>voidStack<T>::Push(constT&x){if(_size==_capacity){// 扩容逻辑T*tmp=newT[_capacity*2];memcpy(tmp,_array,sizeof(T)*_size);delete[]_array;_array=tmp;_capacity*=2;}_array[_size++]=x;}

4.3 类模板的使用(显式实例化)

注意类模板不支持隐式实例化,必须显式指定模板参数类型。

intmain(){// 显式实例化:指定T为intStack<int>st1;st1.Push(1);st1.Push(2);st1.Push(3);// 显式实例化:指定T为doubleStack<double>st2;st2.Push(1.1);st2.Push(2.2);st2.Push(3.3);// 动态分配类模板对象Stack<double>*pst=newStack<double>;pst->Push(10.5);deletepst;return0;}

🐶 🐾 ✨ 🐾 🐶


五、模板的注意事项

5.1 声明与定义分离的问题

模板的声明和定义通常不能分离到.h.cpp文件中。

// Stack.htemplate<typenameT>classStack{public:voidPush(constT&x);};// Stack.cpp 错误!链接时会找不到定义template<typenameT>voidStack<T>::Push(constT&x){/* ... */}

🐾解决办法:

  • 将定义直接写在.h文件中
  • 或者在.cpp文件末尾显式实例化需要的类型
// Stack.cpp - 显式实例化templateclassStack<int>;templateclassStack<double>;

5.2 模板的编译原理

模板在编译阶段根据使用情况生成具体代码:

  • 编译器看到模板定义时,不会生成代码
  • 编译器看到实例化(如Stack<int>)时,才会生成对应的类代码
  • 不同的实例化生成不同的类(Stack<int>和Stack<double>是不同类型)

5.3 模板的缺点

缺点说明
编译慢每次实例化都要重新生成代码
代码膨胀不同类型生成多份代码
错误信息复杂模板编译错误信息难以阅读
声明定义难分离通常只能写在头文件

🐶 🐾 ✨ 🐾 🐶


六、完整示例:通用Stack类

#include<iostream>#include<string>usingnamespacestd;template<typenameT>classStack{public:Stack(intn=4):_array(newT[n]),_size(0),_capacity(n){cout<<"Stack()"<<endl;}~Stack(){delete[]_array;_array=nullptr;_size=_capacity=0;cout<<"~Stack()"<<endl;}voidPush(constT&x){if(_size==_capacity){Expand();}_array[_size++]=x;}voidPop(){if(_size>0)_size--;}T&Top(){return_array[_size-1];}boolEmpty()const{return_size==0;}size_tSize()const{return_size;}private:voidExpand(){T*tmp=newT[_capacity*2];for(size_t i=0;i<_size;i++){tmp[i]=_array[i];}delete[]_array;_array=tmp;_capacity*=2;}T*_array;size_t _capacity;size_t _size;};intmain(){// int栈Stack<int>intStack;intStack.Push(10);intStack.Push(20);intStack.Push(30);while(!intStack.Empty()){cout<<intStack.Top()<<" ";intStack.Pop();}cout<<endl;// 30 20 10// string栈Stack<string>strStack;strStack.Push("hello");strStack.Push("world");cout<<strStack.Top()<<endl;// worldreturn0;}

🐶 🐾 ✨ 🐾 🐶


七、知识点汇总

知识点核心要点
函数模板template + 函数定义
模板参数typename和class完全等价
隐式实例化编译器自动推导参数类型
显式实例化FuncName(a, b)强制指定
类模板必须显式实例化,如Stack
类外定义需template + Stack::
匹配优先级普通函数 > 模板实例化 > 类型转换
编译特性模板在实例化时才生成代码

🐶 🐾 ✨ 🐾 🐶


八、常见面试题

🐾Q1:typename和class在模板中有什么区别?

  • 在模板参数中完全等价。但typename还可以用于嵌套依赖类型,例如typename T::iterator。

🐾Q2:函数模板可以隐式实例化,类模板为什么不行?

  • 函数模板编译器可以实参推导,类模板没有推导依据(构造函数实参可以推导C++17开始支持)。C++17开始类模板也支持部分隐式推导(CTAD)。

🐾Q3:模板声明和定义为什么不能分离?

  • 模板在实例化时才生成代码。如果定义在.cpp文件,其他文件包含.h时看不到定义,无法实例化,导致链接错误。

🐾Q4:模板代码膨胀怎么解决?

  • 将不依赖模板参数的公共代码抽取到基类或单独的函数中。

🐾下一篇我们来学习:

  • STL初识(vector、list、map等)
  • 迭代器的使用

🐶 🐾 ✨ 🐾 🐶


谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论

http://www.jsqmd.com/news/1045002/

相关文章:

  • 2026年余姚小家电AI搜索GEO优化服务商评测对比 - 起跑123
  • 初识 Claude Code
  • 探索Fider:解锁开源反馈系统的3个架构秘密
  • 2026福州防水补漏维修团队实测盘点TOP4:福州业主房屋渗漏修缮靠谱选择 - 宅安选房屋修缮
  • Appium自动化测试全解析:从核心原理到实战应用
  • 探索War3编辑器(5):物体编辑器的核心概念与实战应用
  • GHelper终极指南:华硕笔记本轻量级控制中心,告别Armoury Crate臃肿时代
  • 【Python】从IndexError到数据安全:NumPy/Pandas索引越界的深度防御与实战修复
  • QT程序依赖的dll--自动导入
  • 2026年新能源四轮扫地车十大品牌推荐,第一实至名归 - 工业清洁测评社
  • SSD1306驱动库全面解析:支持8种OLED/LCD显示屏的跨平台解决方案
  • Python命名规范与代码风格:写出优雅代码
  • 陪诊师考试难吗?90% 考生都在用的教科书式备考攻略 - 深鉴新闻
  • MC9S12XE时钟与复位系统深度解析:IPLL配置、看门狗与低功耗管理
  • 如何永久保存微信聊天记录?WeChatMsg终极本地化数据管理指南
  • Spec-kit配置及使用
  • 从零到项目上线:一张思维导图吃透 Vue3 全家桶
  • 2026年 北京防水堵漏/楼顶防水/外墙防水/卫生间防水/管道测漏/精准测漏榜单:专业施工与隐蔽工程口碑之选 - 品牌发掘
  • 2026厦门防水补漏维修团队实测盘点TOP4:厦门业主房屋渗漏修缮靠谱选择 - 宅安选房屋修缮
  • 2026年滁州GEO服务商代理加盟选型靠谱推荐丨滁州GEO优化公司代理服务商怎么选? - 企业新闻快传
  • 2026昆山玉山镇防水工程厂家适配指南:昆山鼎壹万防水补漏公司专业方案测评与行业服务商解析 专业防水公司排名推荐(2026年6月防水补漏最新TOP权威排名) - 鼎壹万修缮说
  • 2026昆山防水补漏服务商适配指南:昆山鼎壹万防水补漏公司及本地优质服务商深度解析 专业防水公司排名推荐(2026年6月防水补漏最新TOP权威排名) - 鼎壹万修缮说
  • 打造你的“开发战斗机”:VS Code 扩展推荐指南(从入门到入土版)
  • 深圳家电维修平台推荐:本地实测较好的几家服务商深度对比——2026年6月最新发布 - 一步到家
  • NSK高速精密滚珠丝杠PSS1520技术详述
  • 杭州家电维修平台推荐:本地用户反馈较好的几家服务商深度实测对比——2026年6月最新发布 - 一步到家
  • 2026苏州防水服务商适配指南:昆山鼎壹万防水补漏公司与区域品牌实力深度解析 专业防水公司排名推荐(2026年6月防水补漏最新TOP权威排名) - 鼎壹万修缮说
  • 2026优选镇海汽配厂短视频GEO排名服务商实测评测对比 - 起跑123
  • MATLAB与STK联合仿真实战:批量生成高精度TLE轨道星历
  • 20260619 之所思 - 人生如梦