如何编写一个通用的函数?

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨

🐻推荐专栏1: 🍔🍟🌯C语言初阶

🐻推荐专栏2: 🍔🍟🌯C语言进阶

🔑个人信条: 🌵知行合一
金句分享:
✨你要狠下心来去努力,努力变成一个很厉害的人.✨

前言

本文主要讲解如何使用简单的模板,了解模板的原理以及基本知识.

目录

一、函数模板

模板的作用:

C++模板的作用是支持泛型编程。==泛型编程=是一种编程范式,它只考虑算法或数据结构的抽象,而不考虑具体的数据类型。通过使用模板,可以编写一种通用的算法或数据结构,而不需要为每种数据类型都编写一遍相关代码。模板可以用于函数、类、结构体等地方,以实现通用的算法和数据结构。使用模板可以提高代码的复用性和可读性,减少代码的重复编写。

示例:实现一个交换函数.

使用模板之前:

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

void test1()
{
	//交换整形
	int a = 2, b = 3;
	cout << "a=" << a << "  " << "b=" << b << endl;
	swap(a, b);
	cout << "a=" << a << "  " << "b=" << b << endl;

	//交换字符型
	char c1 = 'd', c2 = 'f';
	cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
	swap(c1, c2);
	cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
	//交换...
}

上述实现过程中使用函数重载实现.但是函数重载会有一些不合适的问题.

  1. 函数重载只是重载的函数类型不同,代码复用率比较低,对于一个新的类型又要增加新的函数.
  2. 由于功能基本一样,只是类型不同,导致代码的可维护性比较低,一个出错可能所有的重载均出错,均要修改.

这时,函数模板就派上用场了.

(1)函数模板的格式

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

其中,typename 可以使用class代替,不能使用struct代替.

示例:使用模板后的通用交换函数.

cpp 复制代码
template <class T>//模板
void swap(T& a, T& b)//T会根据传参的对象进行推导为相应的类型
{
	T tmp = a;
	a = b;
	b = tmp;
}
void test1()
{
	//交换整形
	int a = 2, b = 3;
	cout << "a=" << a << "  " << "b=" << b << endl;
	swap(a, b);
	cout << "a=" << a << "  " << "b=" << b << endl;

	//交换字符型
	char c1 = 'd', c2 = 'f';
	cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
	swap(c1, c2);
	cout << "c1=" << c1 << "  " << "c2=" << c2 << endl;
	//交换...
}

(2)函数模板的原理(重点)

函数模板类似于一个模具,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做.

这就类似于古代的印刷术,如果每本书都需要手写,那效率是否太低了,还有各种情况可能会出错.但是印刷术的使用,就可以使用模具生成.

函数模板的原理是通过将类型参数化,使函数能够在编译时根据实际参数的类型推断生成具体的函数实例。编译器会根据调用函数时的参数类型,实例化出适合该类型的函数版本。

比如:

当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码.当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码.如上图所示.

(3)模板参数的显示实例化

上面我们实现的交换函数,模板根据传参时不同的参数,自动推演出函数参数的实际类型.我们称这类通过编译器进行自动推导的实例化模板参数称为模板参数的隐式实例化.

那什么是显示实例化呢?

cpp 复制代码
template <typename T>
T add(const T& a, const T& b)
{
	return a + b;
}
void test1()
{
	int a = 2, b = 3;
	double d1 = 2.5, d2 = 4.1;
	cout << add(a, b) << endl;
	cout << add(d1, d2) << endl;
	//下面这句会报错,因为一个模板参数无法在一个函数中实例化为2个不同类型的参数,一个int,一个double
	//cout << add(a, d2) << endl;
}

一个函数模板参数在同一个函数中,无法被识别为不同的两个实例类型参数,当编译器推导出aint时,又推出d2double类型,则编译器陷入两难.

就好比:
int:妈妈说今天不许出去玩!
double:爸爸说今天可以出去玩!
编译器:我听谁的.

解决方案:

直接将参数先强转为一样的,当模板函数接收到参数时,就只有一样的结果了.

cpp 复制代码
	//解决方法1:传参时将其中不同的参数强转,使参数们相同
	cout << add(a, (int)d2) << endl;
	cout << add((double)a, d2) << endl;

模板参数的显示实例化:

让爸妈先商量好听谁的.

cpp 复制代码
	//解决方法2:显示指定模板的参数
	cout << add<int>(a, d2) << endl;	//听妈妈的
	cout << add<double>(a, d2) << endl;	//听爸爸的

我们应当是考虑如何在调用时采取不同的调用方式去满足我们的需求,千万不要想着去修改模板函数的返回值,参数使他们固定生成,那模板就不通用了,而且不是什么时候我们都可以去修改模板的.

错误示例:

cpp 复制代码
template <typename T>
T add(const T& a, const int& b)//直接修改参数,进行固定
{
	return a + b;
}

(4)模板匹配

对于函数名相同的非模板函数和模板函数同时存在时,编译器会优先选择非模板函数.除非模板可以产生更好的匹配函数,才会选择模板.

编译器:有现成的为啥不用.

cpp 复制代码
void swap(double& a, double& b)
{
	double tmp = a;
	a = b;
	b = tmp;
}
//template <class  T>
template <typename  T>//函数模板
void swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
void test1()
{
	//交换double
	double d1 = 2.5, d2 = 4.5;
	//非模板函数和模板函数同时存在时,编译器优先选择非模板函数,有现成的为啥不用?非得自己去再模具刻一个?
	swap(d1, d2);
	cout << "d1=" << d1 << "  " << "d2=" << d2 << endl;
	
	//交换整形
	int a = 2, b = 3;
	//没有现成的非模板函数,则编译器调用模板函数去实例化一份
	swap(a, b);
	cout << "a=" << a << "  " << "b=" << b << endl;
}

交换double型数据时,会调用void swap(double& a, double& b)函数,因为有现成的可以调用.

交换int整形时,则会调用模板函数void swap(T& a, T& b),实例化生成int型的函数.

小知识:
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换.

因为模板函数的参数是通过参数类型进行推导的.

二、类模板

类模板的格式

cpp 复制代码
template <typename T>
class A
{
	//成员
}

类模板在后续学习STL时候会具体介绍,目前了解一下即可,使用方法与函数模板类似,这里就不过多介绍了.

cpp 复制代码
template <typename T>
class A
{
public:
	A(size_t capacity = 10)
		: _data(new T[capacity])
		, _size(0)
		, _capacity(capacity)
	{}

	void push_back() {
		//...
	}
	~A()
	{
		delete _data;
		_size = 0;
		_capacity = 0;
	}

private:
	T* _data;
	size_t _size;
	size_t _capacity;
};


void test3()
{
	A<int> a1;			//实例化为存储int数据的类
	A<double> a2;		//实例化为存储double数据的类
}

本文只是对模板的初步了解,后续会遇到更加复杂的模板,比如多参数的模板等,知识一点点的学,不求速成,坚持一点点的积累,一起加油吧!

今天就讲到这里了,拜拜.

相关推荐
2402_85758349几秒前
“协同过滤技术实战”:网上书城系统的设计与实现
java·开发语言·vue.js·科技·mfc
爱学习的白杨树7 分钟前
MyBatis的一级、二级缓存
java·开发语言·spring
OTWOL12 分钟前
两道数组有关的OJ练习题
c语言·开发语言·数据结构·c++·算法
问道飞鱼16 分钟前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js
拓端研究室16 分钟前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
Code成立17 分钟前
《Java核心技术I》Swing的网格包布局
java·开发语言·swing
Auc2422 分钟前
使用scrapy框架爬取微博热搜榜
开发语言·python
QQ同步助手29 分钟前
C++ 指针进阶:动态内存与复杂应用
开发语言·c++
凯子坚持 c35 分钟前
仓颉编程语言深入教程:基础概念和数据类型
开发语言·华为
小爬虫程序猿37 分钟前
利用Java爬虫速卖通按关键字搜索AliExpress商品
java·开发语言·爬虫