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

一篇文章带你了解C++模板编程详解

泛型编程

在计算机程序设计领域,为了避免因数据类型的不同,而被迫重复编写大量相同业务逻辑的代码,人们发展的泛型及泛型编程技术。什么是泛型呢?实质上就是不使用具体数据类型(例如 int、double、float 等),而是使用一种通用类型来进行程序设计的方法,该方法可以大规模的减少程序代码的编写量,让程序员可以集中精力用于业务逻辑的实现。泛型也是一种数据类型,只不过它是一种用来代替所有类型的“通用类型”

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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;

}

......

Swap函数能实现各种类型的变量交换,但是只要类型不同就需要重新写一个

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  • 重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数
  • 代码的可维护性比较低,一个出错可能所有的重载均出错,那能否告诉编译器一个模版,让编译器根据不同的类型利用该模版来生成代码呢?

可以的,C++语法中有了模板:

函数模板

函数模板概念

所谓函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。 这个通用函数就称为 函数模板(Function Template) 。函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

函数模板格式

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

1

2

3

4

5

6

7

8

template<typenameT>

//或者 template<class T>

voidSwap(T& x1, T& x2)

{

T temp = left;

left = right;

right = temp;

}

T1,T2等等是什么类型现在也不确定,一会用的时候才能确定

注意:

typename是用来定义模板参数关键字,也可以使用class

函数模板的原理

函数模板本身并不是函数,是编译器根据调用的参数类型产生特定具体类型函数的模具,所以其实模板就是将本来应该我们做的重复的事情交给了编译器,我们看下面的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

template<classT>

voidSwap(T& x, T& y)

{

T temp = x;

x = y;

y = temp;

}

intmain()

{

inta = 1;

intb = 2;

Swap(a, b);

charA ='a';

charB ='b';

Swap(A,B);

return0;

}

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然
后产生一份专门处理int类型的代码,对于字符类型也是如此。

然而当我们在写了函数时,不会进入模板函数里,没有写具体的函数时,就会进入模板函数里,我们看下面的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

voidSwap(int& x,int& y)

{

inttemp = x;

x = y;

y = temp;

}

template<classT>

voidSwap(T& x, T& y)

{

T temp = x;

x = y;

y = temp;

}

intmain()

{

inta = 1;

intb = 2;

Swap(a, b);

charA ='a';

charB ='b';

Swap(A,B);

return0;

}

我们进行调式:

我们可以看到int类型的交换函数我们写了,调用时调用的是我们写的,而char类型的我们没写,就用了模板。

那么这里调用的是模板函数吗?

不是的,实际上这里会有两个过程

1、模板推演,推演T的具体类型是什么

2、推演出T的具体类型后实例化生成具体的函数

上面的代码实例化生成了下面的函数:

1

2

3

4

5

6

voidSwap(char& x,char& y)

{

chartemp = x;

x = y;

y = temp;

}

真正调用的还是两个函数,但是其中的一个函数不是我们自己写的,而是我们给了编译器一个模板,然后编译器进行推演在编译之前实例化生成三个对应的函数,模板是给编译器用的,编译器充当了写函数的工具:

可以看到这里是调用了Swap<char>函数

在C++当中,其实内置类型也可以像自定义类型那样这样初始化:

1

2

inta(1);

int(2);//匿名

1

2

3

4

5

6

voidSwap(T& x1, T& x2)

{

T temp(x1);

x1 = x2;

x2 = x1;

}

所以模板还可以这样写,可以使内置类型和自定义类型兼容:

1

2

3

4

5

6

voidSwap(T& x1, T& x2)

{

T temp(x1);

x1 = x2;

x2 = x1;

}

我们来具体看一看函数模板的实例化:

函数模板的实例化

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

隐式实例化:让编译器根据实参推演模板参数的实际类型

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

template<classT>

T Add(constT& left,constT& right)

{

returnleft + right;

}

intmain()

{

inta1 = 10, a2 = 20;

doubled1 = 10.0, d2 = 20.0;

Add(a1, a2);

Add(d1, d2);

// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化

Add(a1, d2);

return0;

}

该语句是不能够通过编译的,因为在编译期间,当编译器看到该实例化时,用a1去推T是int,而用d2去推是double,但是模板参数列表里只有一个T,编译器不能明确该T是int还是double,T是不明确的,所以编译器会报错

那么怎么处理呢?

解决方式:

1、调用者自己强制转换

1

2

3

//实参去推演形参的类型

Add(a1, (int)d2);

Add((double)a1,d2);

这里可以将d2先强制类型转换,然后再进行推演;或者将a1先强制类型转换再进行推演

2、使用显式实例化

1

2

3

//实参不需要去推演形参的类型,显式实例化指定T的类型

Add<int>(a1, d2);

Add<double>(a1,d2);

这种方式是显式实例化指定T的类型

显式实例化在哪种场景可用呢?看下面的这种场景:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

classA

{

A(inta=0):_a(a)

{}

private:

int_a;

};

template<classT>

T func(intx)

{

T a(x);

returna;

}

intmain()

{

func<A>(1);

func<int>(2);

return0;

}

有些函数模板里面参数中没用模板参数,函数体内才有用到模板参数,此时就无法通过参数去推演T的类型,这时只能显示实例化

上面我们提了一点模板参数的匹配原则,下面我们具体看看模板参数的匹配原则:

模板参数的匹配原则

一个非模板函数可以和一个同名的函数模板同时存在,此时如果调用地方参数与非模板函数完全匹配,则会调用非模板函数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

intAdd(intleft,intright)

{

returnleft + right;

}

// 通用加法函数

template<classT>

T Add(T left, T right)

{

returnleft + right;

}

intmain()

{

Add(1,2);//调用自己的函数

return0;

}

Add(1,2)参数是int类型,而我们有现成的int参数的Add函数,所以有现成的就用现成的,编译器也会偷懒

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

相关文章:

  • 2026年苏州本地窗户漏水维修服务机构3家核心能力专业深度解析 专业防水公司排名推荐(2026年5月防水补漏最新TOP权威排名) - 鼎壹万修缮说
  • 5分钟搞定OBS RTSP直播:obs-rtspserver插件完整指南
  • 如何快速掌握BepInEx:面向游戏爱好者的终极插件框架指南
  • 2026年比话降AI率实测报告:知网论文AI率84.9%降到1.4%
  • 如何通过Raw Accel鼠标加速驱动优化游戏性能:7种曲线类型完全指南
  • 甘肃省嘉峪关CPPMSCMP官网报考入口,官方授权双证报考中心 - 众智商学院课程中心
  • Cadence OrCAD 16.6导出网表时,搞定那个烦人的“tmp_pstxnet.dat”写入错误
  • AI时代营销变革:从效率工具到人机共生的艺术
  • 从TLS 1.3到区块链:一文搞懂ECDSA和ECDH在现代安全协议里的核心作用
  • Harbor离线安装后,你的Docker客户端真的配好了吗?一份保姆级的证书配置与验证清单
  • 2026 年 5 月执业医师备考工具实测:破解刷题痛点的高效选择★★★★★ - 讲清楚了
  • DIY高性能触觉反馈鼠标:基于光标检测的30毫秒响应方案
  • Arduino土壤湿度监测仪DIY:从传感器原理到智能灌溉实践
  • React技术周刊 2026年第18周
  • 甘肃省临夏CPPMSCMP官网报考入口,官方授权双证报考中心 - 众智商学院课程中心
  • CoolProp:热物理计算领域的高性能开源架构深度解析
  • 零编程基础也能掌握的KH Coder:13种语言文本挖掘终极指南
  • 甘肃省平凉CPPMSCMP官网报考入口,官方授权双证报考中心 - 众智商学院课程中心
  • 长期使用 Taotoken 的 Token 计费模式让每笔支出都清晰可查
  • 在Dusun DSGW-210物联网网关部署Home Assistant全攻略
  • 2026重庆速洁家政:渝中口碑好的大学城家政公司 - LYL仔仔
  • Arduino LED测试仪制作:一键测量正向电压与限流电阻计算
  • 云学习笔记|基于Java+vue的云的学习笔记系统(源码+数据库+文档)
  • 告别卡顿:在MacBook Air上无缝运行Fedora 35的完整配置清单与性能调优建议
  • 揭开企业级集成平台的神秘面纱:iPaaS如何重塑数字化核心
  • 如何安全高效管理微信聊天记录:PyWxDump工具的终极指南
  • 别再只调PID了!用一阶ESO给你的Arduino小车做个“抗干扰外挂”
  • JoyCon-Driver终极指南:在Windows上免费解锁Switch手柄的全部潜力
  • TGA2624-SM、GaN工艺驱动9-10GHz射频信号的无损极速传输
  • 2026上海帕玛强尼手表回收怎么选?实测商家结果来了 - 合扬奢侈品交易中心