【C++】模板初阶

目录

个人主页<---请点击
C++专栏<---请点击

一、泛型编程

如何实现一个通用的交换函数Swap呢?我们可以通过函数重载来完成。

c 复制代码
void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void Swap(double& a, double& b)
{
	double tmp = a;
	a = b;
	b = tmp;
}
void Swap(char& a, char& b)
{
	char tmp = a;
	a = b;
	b = tmp;
}

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

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子 ,让编译器根据不同的类型 利用该模子 来生成代码呢?就像浇筑一样:

C++中存在这样的方法,就是模板C++中的模板(Templates)泛型编程 的核心实现方式,泛型编程 (Generic Programming)是一种编程范式 ,旨在通过将数据类型参数化 ,使代码(如类、方法、算法等)能够在不针对特定数据类型的前提下实现复用 ,同时保证类型安全。其核心思想是将 类型作为参数传递给代码实体,从而让同一逻辑可以适用于多种不同的数据类型,避免为每种类型重复编写相似代码

二、函数模板

1、函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关 ,在使用时被参数化,根据实参类型产生函数的特定类型版本

2、函数模板格式

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

此时我们就可以使用函数模板来实现Swap函数:

c 复制代码
template<typename T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

执行的代码段:

c 复制代码
int i = 1, j = 2;
Swap(i, j);
double x = 1.1, y = 2.2;
Swap(x, y);

可以看到它们成功被交换了。

注意typename可以被替换成class,但不能替换成struct,其中的T不是固定格式,你可以替换成你想替换的字母或单词,一般习惯用大写。

3、函数模板原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具 。所以其实模板就是将本来应该我们做的重复的事情交给了编译器,相当于你现在是包工头而不是工人了。

4、函数模板的实例化

上面2中讲的这种是隐式实例化,也就是实参类型推演模板参数,当你像下面这样写时编译器就推演不出参数了:

c 复制代码
int i = 0;
double x = 1.1;
Swap(i, x);

一个是int一个是double,编译器此时就懵了,会报出以下错误:

为了让讲的例子更合理,我们再写出一个模板Add来讲解:

c 复制代码
template<typename T>
T Add(const T& a, const T& b)
{
	return a + b;
}

执行的代码段:

c 复制代码
int i = 0;
double x = 1.1;
cout << Add(i, x) << endl;

报错:

处理方案1:强转
隐式实例化:让编译器根据实参推演模板参数的实际类型

c 复制代码
// 隐式实例化(实参类型推演模板参数)
cout << Add(i, (int)x) << endl;
cout << Add((double)i, x) << endl;


注意 :由于Add中的参数是引用,强转时产生的临时变量具有常性 ,所以必须使用const引用,或者你不用引用,那就不需要加const

处理方案2:显示实例化
显式实例化 :在函数名后的<>中指定模板参数的实际类型

c 复制代码
//显示实例化(显示指定模板参数)
cout << Add<int>(i, x) << endl;
cout << Add<double>(i, x) << endl;


处理方案3:多加一个typename

c 复制代码
template<typename T,typename t>
T Add(T& a, t& b)
{
	return a + b;
}
c 复制代码
int i = 0;
double x = 1.1;

5、模板参数的匹配原则

  1. 一个非模板函数 可以和一个同名的函数模板 同时存在,而且该函数模板还可以被实例化为这个非模板函数
c 复制代码
// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
void Test()
{
	// 与非模板函数匹配,编译器不需要特化
	Add(1, 2);
	// 虽然有匹配的但可以调用模板生成的
	//指定调用编译器特化的Add版本
	Add<int>(1, 2);
}
  1. 对于非模板函数同名函数模板 ,如果其他条件都相同,在调动时会优先调用非模板函数 ,而不会从该模板产生出一个实例。如果模板可以产生一个更加匹配的函数, 那么将选择模板。
c 复制代码
// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}
void Test()
{
	// 与非函数模板类型完全匹配,
	// 不需要函数模板实例化
	Add(1, 2); 
	// 模板函数可以生成更加匹配的版本,
	// 编译器根据实参生成更加匹配的Add函数
	Add(1, 2.0); 
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

模板函数的类型推导机制

模板函数在调用时需要通过实参自动推导模板参数编译器要求实参类型与模板参数类型严格匹配,不进行自动类型转换,除非:

  • 模板参数被显式指定(如 func<int>(3.14))。
  • 使用了const引用指针类型 (如 T& 允许int到const int的转换)。

原因 :模板的设计目标是实现完全泛型 的代码,类型推导必须精确 。如果允许自动转换,可能导致意外行为(如int与double相加时类型不一致)

普通函数的重载解析与类型转换

普通函数通过重载决议选择最佳匹配函数,并允许隐式类型转换:

  • 编译器会根据实参与形参的匹配程度排序候选函数。
  • 若存在多个可行函数,会选择转换代价最小的函数

标准转换的优先级

在同一等级的标准转换中(如int→double和double→int)C++进一步区分:

  • 类型提升 (如int→long)优先于类型转换 (如int→double)
  • 值保留转换 (如int→double)优先于可能损失值的转换 (如double→int)

原因 :普通函数的重载机制 允许为不同类型提供专门实现,而隐式转换是C++语言的基础特性(如int到double的转换)。

6、类模板

6.1 类模板的定义格式

c 复制代码
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};

我们以Stack为例写类模板:

c 复制代码
template<class T>
class Stack
{
public:
	Stack(int n = 4)
		:_a(new T[n])
		,_top(0)
		,_capacity(n)
	{ }

	void push(T x);

	~Stack()
	{
		delete[] _a;
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	T* _a;
	int _top = 0;
	int _capacity = 0;
};

这就是类模板,假设我们要在类外面 实现里面声明的函数push时,我们需要这样实现:

c 复制代码
template<class T>
void Stack<T>::push(T x)
{
	//...
	_a[_top++] = x;
}

也需要在函数上面加上template<class T>,并且要声明在Stack<T>类域中。

6.2 类模板的实例化

类模板实例化函数模板实例化 不同,类模板实例化必须显示实例化 ,需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

c 复制代码
Stack<int> s1;
s1.push(1);
s1.push(2);
s1.push(3);
Stack<double> s2;
s2.push(1.1);
s2.push(2.2);
s2.push(3.3);

Stack类名Stack<int>才是类型

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

相关推荐
@Turbo@1 分钟前
【QT】在QT6中读取文件的方法
开发语言·数据库·qt
_extraordinary_13 分钟前
Java 异常
java·开发语言
moz与京15 分钟前
【数据结构】字符串操作整理(C++)
开发语言·数据结构·c++
招财进宝。。17 分钟前
c# 获取电脑 分辨率 及 DPI 设置
开发语言·c#·电脑
无处不在的海贼20 分钟前
小明的Java面试奇遇之:支付平台高并发交易系统设计与优化[特殊字符]
java·开发语言·面试
居居飒25 分钟前
深入理解 JDK、JRE 和 JVM 的区别
java·开发语言·jvm
几道之旅32 分钟前
python-pptx去除形状默认的阴影
开发语言·javascript·python
1560820721942 分钟前
在QT环境下部署FFT库
开发语言·qt
漫步者TZ1 小时前
【Netty系列】Reactor 模式 1
java·开发语言·github
linff9111 小时前
Reactor和Proactor
c++·网络编程’