17. C++模板(template)1(泛型编程,函数模板,类模板)

发布于:2024-11-28 ⋅ 阅读:(13) ⋅ 点赞:(0)

⭐本篇重点:泛型编程,函数模板,类模板

⭐本篇代码:c++学习/07.函数模板 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)

目录

一. 泛型编程

二. 函数模板

2.1 函数模板的格式

2.2 函数模板的简单使用 

 2.3 函数模板的原理

2.4 函数模板的实例化

2.5 模板参数的匹配原则

三. 类模板

3.1 类模板的格式

3.2 类模板的简单使用

3.3 类模板的实例化


一. 泛型编程

        如何实现一个交换函数,这个函数可以交换任意两个相同类型的变量?我们可以使用函数重载来完成这个功能。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

void swap(int& a, int& b)
{
	int t = a;
	a = b;
	b = t;
}

void swap(double& a, double& b)
{
	double t = a;
	a = b;
	b = t;
}

void swap(char& a, char& b)
{
	char t = a;
	a = b;
	b = t;
}

int main()
{
	int a = 1, b = 2;
	double c = 3.14, d = 1.44;
	char c1 = 'a', c2 = 'b';
	printf("交换前\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);


	swap(a, b);
	swap(c, d);
	swap(c1, c2);
	printf("\n\n交换后\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);
	return 0;
}

运行结果如下:

 

函数重载虽然可以完成这个功能,但是有两个缺点:

1 如果有一个重载的函数出错,可能导致全部函数出问题

2 代码的复用性较低,每增加一个新的类型,都要新增一个重载函数 

        其实,在C++中也存在一种模具,根据填充的材料,获取不同的铸件。就是泛型编程

泛型编程:编写与类型无关的代码,而模板是泛型编程的基础

二. 函数模板

2.1 函数模板的格式

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

注意:typename是用来定义模板参数的关键字,可以使用class替代(不可使用struct替代)

2.2 函数模板的简单使用 

        将swap修改为函数模板后的代码如下:

//使用函数模板
template<class T>
void myswap(T& a, T& b)
{
	T t = a;
	a = b;
	b = t;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

//使用函数模板
template<class T>
void myswap(T& a, T& b)
{
	T t = a;
	a = b;
	b = t;
}

int main()
{
	int a = 1, b = 2;
	double c = 3.14, d = 1.44;
	char c1 = 'a', c2 = 'b';
	printf("交换前\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);


	myswap(a, b);
	myswap(c, d);
	myswap(c1, c2);
	printf("\n\n交换后\na:%d b:%d\nc:%f d:%f\nc1:%c c2:%c\n", a, b, c, d, c1, c2);
	return 0;
}

测试结果如下:

 

 2.3 函数模板的原理

        函数模板在编译器编译阶段,编译器根据传入的参数去推导生成对应参数类型的函数用来调用。比如我们传入int型参数,编译器通过推演,将T确定为int型,然后生成一份专门为int型调用的代码。

下图可以帮助我们理解函数模板

2.4 函数模板的实例化

        用不同的参数去使用函数模板的时候,称为函数模板的实例化。函数模板的实例化有显示实例化和隐式实例化。

隐式实例化:让编译器根据实参去自动推演参数类型

显示实例化: 在调用函数时候,在函数名后面加上<>,在<>中指定参数的类型       

 上面的交换函数swao就是隐士实例化的例子

显示实例化:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

//使用函数模板
template<class T>
T add(const T& a, const T& b)
{
	return a + b;
}

int main()
{
	int a = 1, b = 2;
	double c = 3.14, d = 1.44;
	cout << "a + b = " << add<int>(a, b) << endl;
	cout << "c + d = " << add<double>(c, d) << endl;

	int ret = add<int>(a, c); //如果参数类型和指定类型不一样,编译器会转化
	cout << "ret:" << ret << endl;
	return 0;
}

 如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

2.5 模板参数的匹配原则

        一个非模板函数可以和一个同名模板函数同时存在,并且这个模板函数可以被实例化为这个非模板函数

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

//非模板函数
int add(const int& a, const int& b)
{
	return a + b;
}

//使用函数模板
template<class T>
T add(const T& a, const T& b)
{
	return a + b;
}

int main()
{
	int a = 1, b = 2;
	double c = 3.14, d = 1.44;
	int ret1 = add(a, b);		//调用非模板函数
	int ret2 = add<int>(a, b);	//调用显示实例化的add函数
	double ret3 = add(c, d);	//调用double类型的add函数

	cout << ret1 << endl << ret2 << endl << ret3;
	return 0;
}

运行结果如下:

        对于非模板函数和同名模板函数 ,在调用条件相同的情况下,编译器会优先去调用非模板函数。如果模板函数可以产生一个更匹配的函数,则会调用模板函数

        模板函数不允许自动类型转化,普通函数支持自动类型转化

三. 类模板

        与函数模板一样,我们也可以使用类模板。

3.1 类模板的格式

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

3.2 类模板的简单使用

这里使用一个模板数组(支持传入的类型)来举例:

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

template<class T>
class arr
{
public:
	arr()
		:_a(nullptr)
		, _size(0)
		, _capacity(0)
	{};

	~arr()
	{
		//销毁开辟的空间
		delete[] _a;
		_size = 0;
		_capacity = 0;
	}

	//插入函数
	void PushBack(const T& x)
	{
		if (_size == _capacity)
		{
			size_t newcapacity = (_capacity == 0) ? 3 : _capacity * 2;
			T* tmp = new T[newcapacity];
			if (_a != nullptr)
			{
				memcpy(tmp, _a, sizeof(T) * _size);
				delete[] _a;    //注意要销毁旧的空间,防止内存泄漏!
				_a = nullptr;
			}
			_a = tmp;
			_capacity = newcapacity;
		}
		_a[_size] = x;
		_size++;
	}

	void print()
	{
		for (int i = 0; i < _size; i++)
		{
			cout << _a[i] << " ";
		}
		cout << endl;
	}
private:
	T* _a;
	size_t _size;
	size_t _capacity;
};

int main()
{
	//定义一个int类型的数组
	arr<int> nums;
	for (int i = 0; i < 10; i++)
	{
		nums.PushBack(i);
	}
	nums.print();

	//定义一个char类型的数组
	arr<char> str;
	for (int i = 0; i < 10; i++)
	{
		str.PushBack('a' + i);
	}
	str.print();
	return 0;
}

运行结果如下:

3.3 类模板的实例化

        类模板的实例化与函数模板的实例化不同,类模板的实例化需要在模板名后面加上<>,并且在<>指明这个类型。

        就像上面的代码:arr是模板名字,arr<int>和arr<char>才是类名字