【C++】模板初阶: 解析模板原理、实例化与特化
2026/6/19 23:00:58 网站建设 项目流程

📌 相关专栏

  • 【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等)
  • 迭代器的使用

🐶 🐾 ✨ 🐾 🐶


谢谢你看到这里呀

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

👍 点赞 ⭐ 收藏 💬 评论

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询