news 2026/9/14 11:41:58

c++——模版

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
c++——模版

目录

引子

模版是什么

模版格式

函数模版

函数模版的隐式实例化

函数模版的显式实例化

原理

一些使用的例子(帮助理解)

模版函数的匹配原则

类模板

简单使用

类模板中的成员函数函数放在类外进行定义

原理

非类型模版参数

typename指定类型

模版特化

函数模板特化

函数模板特化的坑

类模板特化之全特化

类模板特化之偏特化

调用优先级

为什么模板函数声明和定义不要分离 ?


引子

我们经常用到Swap函数交换两个值。由于需要交换的数据的类型不同,我们就需要写不同参数列表的同名函数,也就是函数重载:

void Swap(int &a,int &b) { int temp = a; a = b; b = temp; } void Swap(float& a, float& b) { int temp = a; a = b; b = temp; } void Swap(char& a, char& b) { int temp = a; a = b; b = temp; }

同样的,下面的A类和B类的逻辑完全一样,但由于我们需要的是能处理float和int两个类型的类,所以我们写了两个类。

class A { public: A() {} void Set(int x) { a = x; } private: int a; }; class B { public: B() {} void Set(float x) { b = x; } private: float b; };

然而这三个函数的逻辑是一样的,这两个类的逻辑也是一样的,他们只是类型不一样,写这么多有些多此一举,通过泛型编程-模版就能减少相同逻辑代码的书写,提高开发效率。


模版是什么

模版属于泛型编程,是一种代码复用的手段,每个模版都代表着一个函数/类家族,根据实参类型或者直接指定类型来实例化出特定的版本。


模版格式

template<typename T1,typename T2,......typename Tn>
  • typename是关键字,也可以用class代替,用来定义一个模版类型。
  • T1-Tn是函数/类的模版类型名(T1-Tn都是类型名)
  • 该语句放到函数/类的上方

函数模版

注意:

  • 使用template定义了函数模版类型之后,在函数的实现中就可以使用相应的模版类型,定义形参,变量,返回值等,总之,就是把模版类型当作实际类型使用
  • 函数模版类型的实际类型由调用函数时传入的实参类型推导而来(此时你必须在函数形参的定义中使用模版类型),或是在调用时直接指定(也就是下面的“模版参数的显示实例化”),二者必居其一,否则编译器无法推导

函数模版的隐式实例化

template<class T> T add(T x,T y) { return x + y; } int main() { cout << add(1, 2) << endl; cout << add(2.0,5.3) << endl; }

函数模版的显式实例化

template<class T> T add(T x,T y) { return x + y; } int main() { cout << add(1, 2.5) << endl; }

编译器不确定T是int还是double类型:

为了解决这个问题你可以这样:

template<class T,class S> T add(T x,S y) { return x + y; } int main() { cout << add(1, 2.5) << endl; }

但是你也可以这样:

template<class T> T add(T x,T y) { return x + y; } int main() { cout << add<int>(1, 2.5) << endl; }

这样直接指定了T类型而不用编译器推演,叫做函数模板的显式实例化(这种情况下,b会转换为int类型)

原理

检测你传入的参数类型,隐式的帮你生成这一类型的函数重载(这个过程叫模版的隐式实例化),或者根据指定的类型,显示的帮你生成这一类型的函数重载(这个过程叫模版的显式实例化)。所以实际上仍然生成了多个函数,调用的时候也不是调用同一个函数,而是调用对应的函数重载,只不过编译器替你写了这些函数,不用自己写了,提高了开发效率。

一些使用的例子(帮助理解)

在这种情况下,模版函数中只有返回值是模版类型,所以根据传入的实参类型无法推导出模版类型的实际类型,所以需要显示实例化,直接指定模版类型的实际类型:

template<class T> T add(int x,double y) { return x + y; } int main() { cout << add<int>(1, 2.5) << endl; }

在这种情况下,可以通过传入的实参类型推导出所有模版类型的实际类型,也可以直接显示实例化:

template<class T,class S> T add(T x,S y) { return x + y; } int main() { cout << add(1, 2.5) << endl; cout << add<int,double>(1, 2.5) << endl; }

模版函数的匹配原则

  • 一个非模板函数可以和一个同名的函数模板同时存在
  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
  • 模板函数如果隐式实例化(传参推导),则不允许自动类型转换(因为有歧义),但普通函数可以进行自动类型转换
举个例子吧:

类模板

注意:

  • 使用template定义了类模版类型之后,在类的实现中就可以使用相应的模版类型,定义函数(可以认为类的模版类型就是每个成员函数的模版类型),成员变量等,总之,就是把模版类型当作实际类型使用
  • 类模版类型的实际类型只能通过模版的显示实例化来实现,也就是在创建对象时指定模版类型
  • 一定要注意的是,使用了模版类型的类的类型也要带上模版类型的实际类型,比如下面“简单使用”的例子中,A类的类型就变成了A<int>。这比较好理解,因为模版类会根据指定的类型实例化出不同的类,如果这些类都叫做A的话,该怎么区分呢?

简单使用

template<class T> class A { public: static T Set(T x) { _x = x; } private: T _x; }; int main() { A<int> x; x.Set(5); }

类模板中的成员函数函数放在类外进行定义

template<class T> class A { public: T Set(T x); private: T _x; }; template<class T> T A<T>::Set(T x) { _x = x; } int main() { A<int> x; x.Set(5); }
  • 必须加上模板参数列表(例如:template<class T>)
  • 类型也要写成A<T>

原理

根据指定的类型,隐式的帮你生成这一类型的类(这一过程叫做模版的显示实例化)。所以实际上仍然生成了多个类,只不过编译器替你写了这些类,不用自己写了,提高了开发效率


非类型模版参数

函数和类模板参数除了可以是类型,还可以是常量,这个常量的值在显示实例化中指定

举个例子:

使用时需要注意的几点:

  • 在c++20标准之前,n的类型只能是整形。在c++20及之后,n可以是所有内置类型。但是哪个标准中n都不可以是自定义类型
  • n是右值,也就是说它不是变量而是常量,所以你不能再类中修改它

在标准库中的 arry 数组类的设计就引用了非类型模版参数,用于指定数组所开空间的大小,如下:

栈上开辟了10个int大小的空间。


typename指定类型

先看个代码:

这样的代码会报错,原因是,模版定义的时候还不知道vector<T>::iterator是什么,这取决于vector是否对某类型有特化,有可能取出来的是迭代器类型,也有可能取出来的是可访问变量,编译器默认把他当做变量,这时只要在前面加上typename 说明这是一个类型名即可:


模版特化

特化就是针对特殊类型使模版实例化之后的逻辑特殊化,特化有两种:函数模板特化和类模板特化

函数模板特化

这是一个比较类的模板函数,看起来不错,但如果我想要这样:即使给compare传地址,也能正常比较数字大小,原来的逻辑显然不行,因为地址需要解引用,然后比较地址指向的数据:

我们可以像下面这样。这时下面的compare就是上面的compare模版函数的特化,只要给compare传入的参数类型是int*,就会走下面的compare:

从上面这个例子我们也可以看到函数模板特化的形式:

  • 必须要先有一个基础的函数模板
  • 关键字template后面接一对空的尖括号<>
  • 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 函数形参表: 必须要和模板函数的参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

函数模板特化的坑

为什么会报错呢?原函数模板中,const修饰的是形参,而特化出来的函数中const修饰的是形参指向的内容,所以可以认为两者参数不一致,那么下面的compare也就不算上面的compare的特化了。解决方法:

这样编译器就认为参数一致了,const修饰的都是形参本身。

实际上函数模版特化一般不会使用,因为其实可以直接编写非模版函数,由于非模版函数的优先性,调用时会优先调用非模版函数,效果一样,还更简单

类模板特化之全特化

先看一下代码:

从这个例子也可以看出类模板全特化的形式:

  • 必须要先有一个基础的类模板
  • 关键字template后面接一对空的尖括号<>
  • 类名后跟一对尖括号,尖括号中指定需要特化的类型

类模板特化之偏特化

偏特化是指只特化(指明)类模版参数中所有模版类型的一部分。

从这个例子也可以看出类模板偏特化的形式:

  • 必须要先有一个基础的类模板。
  • 关键字template后面接一对空的尖括号<>,尖括号里面定义剩下的没有指明的模版类型。
  • 类名后跟一对尖括号,尖括号中指定需要特化的类型以及剩下的没有指明的模版类型。

上面只是偏特化的其中一种形式,下面是偏特化的另一种体现:

上面的特化不指明模板参数的具体类型,而是限制了:只要传入的两个模板参数都是指针类型(也可以限制成引用;甚至一个限制引用,另一个限制成指针),那么就走特化。

特别注意:在上面例子的特化中,T和S的类型不是指针类型,而是指针指向的数据类型。比如

A<int*, int*> x1;走的是特化,但是实例化后T和S是int类型,而不是int *

类模版之可变参数模版的部分特化

这一块我们直接看代码,C++新特性中的tuple就可以使用这种方式实现一个对象存储任意个不同类型的值:

// 主模板:任意数量的参数 template<class... Args> class tuple { }; // 部分特化:表示如果至少有一个参数就会走这里 template<class T, class... Args> class tuple<T, Args...> { };

关于tuple具体实现方式可以在我主页搜索tuple


调用优先级

非模版函数>特化函数>模版函数


为什么模板函数声明和定义不要分离 ?

先看看分离之后会怎么样:

显然,报错原因是找不到Add函数的定义。

让我们分析一下:

这是编译器生成可执行文件的整个过程

  1. 把ww.h中的代码放到ww.cpp中,生成ww.o
  2. 把ww.h中的代码放到test.cpp中,生成test.o
  3. 将ww.o和test.o链接起来生成可执行文件(在这个过程中通过函数名找到函数地址)

那我们进一步分析,链接之前是这样的:

在test.o中有声明无定义,而在ww.o中有定义.所以连接时,test.o通过Add这个函数名在ww.o中找到Add函数的定义,感觉没问题呀。

然而我们忽视了一个重要的问题,对于模板来说,没有调用是不会被编译出相应函数的

在ww.o中没有调用模板函数,所以模板不会被编译,所以实际上ww.o中没有Add函数的定义,因此,test.o调用了Add却找不到相应的函数定义而报错。

这样就解释了为什么模板函数声明和定义不要分离 。

除了把声明和定义写到一起外,还有别的解决方法吗?

有,可以在ww.cpp中显示实例化这个模板:

template int add<int>(int, int); template double add<double>(double, double);

这样的话ww.o就会编译出一个int类型和double类型的Add函数,ww.cpp中也就有了定义。

但是如果你传入的值是其他类型的话还是会找不到定义,这样还得写其他类型的显式实例化,等等等等,很多类型的实例化,这样模版还有什么意义。

总之,还是把模板的声明和定义写到一起为好!!!

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

遗传算法优化电力系统功率损耗的MATLAB实现

1. 项目背景与核心问题 在电力系统运行中&#xff0c;输电线路的功率损耗是影响电网经济性的关键因素。传统调度方法通常基于固定规则或简单优化算法&#xff0c;难以应对用电需求的动态变化。本项目采用遗传算法&#xff08;Genetic Algorithm, GA&#xff09;对发电站的用电需…

作者头像 李华
网站建设 2026/9/14 11:31:34

手机购物商城HTML源码实战:从静态页面到可交互demo

简介&#xff1a;手机购物商城网站HTML源码是一套面向移动端电商场景的前端页面集合&#xff0c;专为入门级前端学习者、移动端网页开发者以及需要完成课程设计或毕业设计的人群准备&#xff0c;可帮助快速搭建商城UI框架&#xff0c;也能作为项目二次开发的起点。资源包为RAR格…

作者头像 李华