泛型编程基石:C++ 模板从入门到熟练

欢迎来到 s a y − f a l l 的文章 欢迎来到say-fall的文章 欢迎来到say−fall的文章

🌈 say-fall:个人主页 🚀 专栏:《手把手教你学会C++》 | 《C语言从零开始到精通》 | 《数据结构与算法》 | 《小游戏与项目》 💪 格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。


前言:

C++ 相较于 C 语言最核心的突破,在于引入了类和对象的面向对象编程范式,但这并非全部 ------ 为了让代码更通用、更高效,C++ 还设计了模板这一核心特性。模板作为泛型编程的基石,能让我们摆脱 "为每种数据类型重复写相同逻辑" 的低效模式,只用一份代码就能适配多种类型,堪称 C++ 提升开发效率、实现代码复用的 "利器"。


文章目录


正文:

一、泛型编程

简单来说,泛型编程就是让代码 "通用化",写一份代码就能处理多种不同类型的数据,而不用为每种类型都写一遍重复代码。

下面来看不使用模板的代码:

cpp 复制代码
//这是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;
}

//...

我们很明显的能观察到这很麻烦,每有一种类型的交换,我们都得写一种函数来处理,甚至我们很难处理两种类型不同的对象进行交换,而c++中的模板就能很好的处理这一情况。

模板有两种:一种是函数模板,另一种是类模板:

二、函数模板

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

  • 函数模板的使用格式:

cpp 复制代码
template <typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
  • 例:
cpp 复制代码
template <class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

这里的classtypename一样,具有相同的作用,但是切记这里不能struct

那么如何理解模板函数是什么东西呢?

可以认为模板就是一个函数的模具,将你想要的类型输入,他就能生成对应的函数:

以上是函数模板的格式,那么真实的函数要如何使用呢?

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

  • 显式实例化

在使用函数的时候,在函数名的后面加<类型>

cpp 复制代码
//以交换函数为例:
template <class T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 1, b = 2; 
	cout<< a << " " << b << endl;
	Swap<int>(a,b);
	cout<< a << " " << b << endl;

	return 0;
}
  • 隐式实例化

与显式实例化不同,隐式实例化并不直接传递类型,而是让编译器自动识别:

cpp 复制代码
template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}
int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.0, d2 = 20.0;
    Add(a1, a2);//①
    
    Add(a1, d2);//②
    return 0;
}

在上方代码中,①表达式编译器是可以识别的,②则不可以,因为编译器识别到了两个不同的类型,无法判断。

有读者会想到:double不是可以隐式类型转换成int吗?但是实际上编译器在不知道用户想要的是哪种结果的情况下不会轻易的隐式类型转换,如果想要解决这种问题,就只能:① 用户自己显式转换;② 显式实例化

三、模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数:
cpp 复制代码
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}

void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2);
	// 调用编译器特化的Add版本
}
  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
cpp 复制代码
// 专门处理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(1, 2.0);
	// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

四、类模板

  • 类模板的定义格式
cpp 复制代码
template<class T1, class T2, ..., class Tn> 
class 类模板名
{
 // 类内成员定义
};
  • 例:
cpp 复制代码
// 类模版
template<typename T>
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = new T[capacity];
		_capacity = capacity;
		_size = 0;
	}
	void Push(const T& data);
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};

// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误
template<class T>
void Stack<T>::Push(const T& data)
{
	// 扩容
	_array[_size] = data;
	++_size;
}
int main()
{
	Stack<int> st1;
	// int
	Stack<double> st2;
	// double
	return 0;
}
  • 类模板的实例化

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

cpp 复制代码
// Stack是类名,Stack<int>才是类型
Stack<int> st1;    // int
Stack<double> st2; // double

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻: 腾讯云开发者社区


  • 本节完...
相关推荐
黯叶2 小时前
基于 Docker+Docker-Compose 的 SpringBoot 项目标准化部署(外置 application-prod.yml 配置方案)
java·spring boot·redis·docker
代码笔耕2 小时前
写了几年 Java,我发现很多人其实一直在用“高级 C 语言”写代码
java·后端·架构
txinyu的博客2 小时前
结合游戏场景解析UDP可靠性问题
java·开发语言·c++·网络协议·游戏·udp
一路向北North2 小时前
springboot基础(85): validator验证器
java·spring boot·后端
djimon2 小时前
06年老电脑复活Ubuntu14.04配置Python网站爬自动化
开发语言·python·自动化
郝学胜-神的一滴2 小时前
深入解析Mipmap层级判定原理:从理论到实践
c++·unity·godot·游戏程序·图形渲染·unreal engine
雾岛听蓝2 小时前
探索C++继承机制
开发语言·c++
你我一见如故2 小时前
Linux基础(4)Linux中的开发工具(1)--yum和vim
linux·服务器·编辑器·vim
1.14(java)2 小时前
掌握数据库约束:确保数据精准可靠
java·数据库·mysql·数据库约束