news 2026/9/28 4:06:00

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++ 模板初阶】一篇搞懂:函数模板、实例化与类模板

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

本文是模板系列的第一篇(初阶),只讲最核心、最常用的部分。
后面的进阶内容(模板特化、非类型模板参数、模板的分离编译、可变参数模板等)会单独成篇,敬请期待。
如果你是刚接触模板的小白,跟着本文一步一步走,保证能看懂、能上手。


一、先说两句:为什么要有模板?

很多小伙伴刚学完函数重载,会有一个疑问:我写一个Swap交换函数,int要写一遍,double要写一遍,string又要写一遍……这也太累了,代码全是重复的,看着都头疼。

有没有一种办法,让我只写一遍,就能通吃所有类型呢?

有!这就是泛型编程。

泛型编程:编写与类型无关的通用代码,让代码在使用时再绑定具体类型,从而实现代码复用。

而模板(Template)就是泛型编程的基础。你把它想象成一个"模具"就行:模具本身不是产品,但往里一浇,就能浇出对应类型的产品。

简单来说:模板写一次,类型随便换。


二、函数模板

2.1 概念

函数模板代表的是一个"函数家族",它本身与类型无关,在使用时会被"参数化"——编译器会根据你传入的实参类型,自动生成一个特定类型的函数版本。

2.2 格式

函数模板的格式长这样:

template<typenameT1,typenameT2,...,typenameTn>返回值类型 函数名(参数列表){...}

一个经典的交换函数模板:

template<typenameT>voidSwap(T&left,T&right){T temp=left;left=right;right=temp;}

这里有几个易错点,先给你打个预防针:

  • typename是用来定义模板参数的关键字,也可以换成class(两者在模板参数这里等价)。
  • ⚠️千万不能用struct代替class,会编译报错,这个坑不少人踩过。

2.3 一句话搞懂原理:模板不是函数

很多人会问:模板到底是不是函数?

记住:函数模板只是一个"蓝图",它本身并不是函数。

在编译阶段,当你真正调用模板时,编译器会根据你传入的实参类型去"推演"(deduce)模板参数T,然后现场生成一个对应类型的真实函数。

举个例子:当你用double类型调用这个模板时,编译器把T推演成double,然后生成一份专门处理double的代码,再去调用它。

所以一句话概括:

编译阶段根据传入模板的类型,编译器现场生成一个函数;模板本身不是函数,只是一个生产函数的"模板机"。


三、函数模板的实例化

用不同类型的参数去使用同一个函数模板,这个过程就叫做模板实例化。

实例化分为两种:隐式实例化和显式实例化。

3.1 隐式实例化

看个加法模板:

template<classT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){inta1=10,a2=20;doubled1=10.0,d2=20.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(){inta=10;doubleb=20.0;Add<int>(a,b);// 显式规定 T 是 intreturn0;}

这里把T明确指定成了int。如果传入的类型不匹配,编译器会尝试做一次隐式类型转换;要是转换不了,就直接报错。

简单记:能推导就隐式,推不了就显式。

3.3 模板参数的匹配原则(重要)

当一个非模板函数和同名函数模板同时存在时,编译器会遵循下面这几条原则:

  1. 一个非模板函数可以和同名函数模板同时存在,而且模板还能被实例化成这个非模板函数。
  2. 如果其他条件都相同,调用时会优先调用非模板函数,不会从模板再生成一个实例。
  3. 如果模板能产生一个匹配更好的版本,那么就选模板。

看个例子就明白了:

// 专门处理 int 的加法函数(非模板)intAdd(intleft,intright){returnleft+right;}// 通用加法模板template<classT1,classT2>T1Add(T1 left,T2 right){returnleft+right;}voidTest(){Add(1,2);// 和非模板函数完全匹配 → 调用非模板函数Add(1,2.0);// 模板能生成更匹配的版本 → 选择模板生成新函数}
  • Add(1, 2):两个参数都是int,和现成的非模板函数完全匹配,直接用现成的,没必要再实例化模板。
  • Add(1, 2.0):一个int一个double,非模板函数对不上,但模板可以现场生成一个更合适的版本,于是选模板。

这一条"完全匹配优先用非模板,模板更合适才用模板"的规则,很多面试题会考,务必记牢。


四、类模板(初阶)

函数有模板,类当然也有模板。比如我们要写一个通用的Stack(栈),不想为int、double各写一遍,就可以用类模板。

4.1 定义格式

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

一个栈的类模板示例:

template<typenameT>classStack{public:Stack(size_t capacity=4):_array(newT[capacity]),_capacity(capacity),_size(0){}voidPush(constT&data);private:T*_array;size_t _capacity;size_t _size;};

4.2 关于声明和定义分离,这里有个大坑!

⚠️ 注意:模板不建议把声明和定义分离到两个文件(.h和.cpp)里,否则会出现链接错误。

因为模板只有在被实例化时才生成代码,而.cpp文件里如果没有具体实例化,编译器就不知道要为它生成啥,链接时自然找不到定义。

正确姿势:声明和定义写在同一个文件里。哪怕要分离,也只在同一文件内分离,并且每个成员函数定义时都要单独带模板参数:

template<classT>voidStack<T>::Push(constT&data){// 这里一般会有扩容逻辑,先省略_array[_size]=data;++_size;}

注意定义处Stack<T>这个写法——成员函数属于"类模板Stack<T>",而不是Stack。

4.3 类模板的实例化

类模板的实例化和函数模板不太一样:函数模板很多时候靠推导(隐式),而类模板必须在名字后面跟<>,把具体类型放进去,手动指定。

intmain(){Stack<int>st1;// 存放 int 的栈Stack<double>st2;// 存放 double 的栈return0;}

这里再强调一个很关键的概念,很多人一开始会懵:

Stack只是类模板的名字,它本身不是一个真正的类;Stack<int>才是真正的类型。

所以定义对象时,一定要写Stack<int>这种带尖括号的完整形式,而不是只写Stack。


五、小结与进阶预告

来,把今天的内容串一遍:

知识点一句话记忆
泛型编程写一份与类型无关的通用代码,实现代码复用
模板泛型编程的基础,是"蓝图/模具",本身不是函数也不是类
函数模板编译时根据实参类型推演T,现场生成对应函数
隐式实例化编译器根据实参自动推导类型
显式实例化在<>里手动指定类型,如Add<int>(...)
匹配原则完全匹配优先非模板,模板更合适才用模板
类模板Stack<int>才是真正的类型,Stack只是名字

本文属于模板系列初阶篇,我们把最核心的地基打好了。

但是!模板的世界远不止这些。后面的进阶篇还会带你搞定:

  • 非类型模板参数(模板参数不只是类型)
  • 模板的特化(全特化 & 偏特化)
  • 模板的分离编译(extern template那些事)
  • 可变参数模板(...的魔法)
  • 模板与 STL 的关系

如果觉得这篇对你有帮助,欢迎点赞、收藏、关注,进阶篇正在路上,我们不见不散!🚀

参考:本人 C++ 模板初阶学习笔记整理。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 4:05:16

openclaw/hermes 智能体为何依赖 WSL2?TaoToken 配置骨架带你揭秘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 4:03:25

LVGL lv_menu实战:构建动态层级菜单与交互界面

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华