【C++】泛型编程、函数模板、类模板

目录

一、泛型编程

二、函数模板

2.1 函数模板格式

2.2 函数模板的实例化

2.2.1 隐式实例化

2.2.2显式实例化

三、类模板

3.1 类模板格式

3.2 类模板的实例化 

四、模板实现的原理

4.1 模板的原理

4.2 模板参数的匹配原则


一、泛型编程

泛型编程是一种基于模板机制的编程风格,它允许对数据类型进行抽象描述,并在编译时生成类型相关的代码。

想要实现一个通用的交换函数swap()

//int型
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

//double型
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}

//char型
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

//...

可以看到,不同类型的swap函数结构相同,只有函数类型不同,虽然函数重载能够完成工作,但是仍有一些不太好的地方。

  1. 重复的写这样的函数会造成代码冗余,也会使代码编写者重复工作。
  2. 有几个类型就要写几次,当增加新类型时,用户还要相应的再写一个或几个函数。
  3. 代码容错率低,一个出错可能造成所有的重载都出错。

于是C++引入了基于模板机制的泛型编程,即给编译器一个模板,让编译器根据不同的类型利用该模板生成相应的代码。 

泛型编程的核心思想是将算法和数据结构与具体的数据类型分离,即在实现算法和数据结构时不指定具体的数据类型,而是用类型参数来描述数据类型,在使用时通过具体的数据类型实例化模板,从而生成类型相关的代码。

总之,C++泛型编程是一种强大的编程技术,可以通过模板机制实现代码重用、拥有更高的灵活性和可扩展性,同时也是C++语言的重要特性之一。

C++中的泛型编程主要通过两种方式实现:函数模板和类模板。


二、函数模板

函数模板是一种通用的函数形式,允许定义一个通用的函数,在使用时可以根据参数类型自动推导出模板参数类型,并生成相应的函数实例。

2.1 函数模板格式

template <typename T>
返回类型 函数名(参数列表) {
  // 函数体
}

其中,

  • template 是定义模板的关键字
  • typename 是定义模板参数的关键字,告诉编译器后面的标识符是一个类型参数,typename 也可以写成class,不可以写成struct
  • T 是类型参数的名称,可以根据需要进行自定义。
  • 返回类型 是函数的返回类型,可以是任意合法的C++类型。
  • 函数名 是函数的名称,自定义命名。
  • 参数列表 是函数的参数列表,可以包含任意数量和类型的参数。
  • 模板参数可以有多个。例如:template <typename T1, typename T2>
template <typename T>
//template <class T>
void Swap(T& left, T& right)
{
    T temp = left;
    left = right;
    right = tmp;
}

2.2 函数模板的实例化

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

2.2.1 隐式实例化

隐式实例化:编译器根据实参的类型推演模板参数的类型,并生成相应的函数实例。

void Test1()
{
	int a = 10;
	int b = 20;
	double d1 = 30.3;
	double d2 = 40.4;
	char c1 = 'x';
	char c2 = 'y';

	Swap(a, b);
	cout << "a = " << a  << ", b = " << b << endl;

	Swap(d1, d2);
	cout << "d1 = " << d1 << ", d2 = " << d2 << endl;
	
	Swap(c1, c2);
	cout << "c1 = " << c1 << ", c2 = " << c2 << endl;

    //Swap(a, d1);
}

注意:

Swap(a, d1);该语句不能通过编译。

因为在编译期间,当编译器看到该实例化时,需要推演其实参类型。通过实参a将T推演为int类型,通过实参d1将T推演为double类型,但模板参数列表中只有一个T 编译器无法确定此处到底该将T确定为int 或者 double类型而报错

在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅

此时有两种处理方式:1. 用户自己来强制类型转换(部分函数适用) 2. 使用显式实例化 

强制类型转换(部分函数适用):

Swap(a, (int)d1);

此处不能编译通过,因为转换后是临时变量,具有常性,形参要用const修饰,但是修饰后又不能更改对应。

下面这个函数模板就可以使用强制类型转换:

template <typename T>
T Add(const T& left, const T& right)
{
    return left + right;
}
void Test2()
{
    int a = 10;
    double b = 11.0;
    cout << Add(a, (int)b) << endl;//强制类型转换
    cout << Add<int>(a, b) << endl;//显式实例化
}

结果都是21。

2.2.2显式实例化

显式实例化:在函数名后的<>中指定模板参数的实际类型

例如上面Test2中的 cout << Add<int>(a, b) << endl; 就是显式实例化

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


三、类模板

类模板是一种通用的类形式,可以根据实参的数据类型生成对应的类实例。 

3.1 类模板格式

template <typename T>
class 类名 {
private:
  // 私有成员

public:
  // 公有成员
};

其中,

  • template 是定义模板的关键字
  • typename 是定义模板参数的关键字,告诉编译器后面的标识符是一个类型参数,typename 也可以写成class,不可以写成struct
  • T 是类型参数的名称,可以根据需要进行自定义。
  • 类名 是类的名称,自定义命名。
  • 在类模板中,可以定义私有成员和公有成员,具体内容根据需要自由定义。
  • 模板参数可以有多个。例如:template <typename T1, typename T2>

需要注意的是,函数模板和类模板的参数可以是类型参数、非类型参数或模板类型参数,根据实际需求进行选择。

template <typename T>
class Stack
{
public:
	Stack(size_t cp = 4)
		: _arr(new T[(cp == 0 ? 4 : cp)])
		, _top(0)
		, _capacity(cp == 0 ? 4 : cp)
	{}

	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;
		_top = 0;
		_capacity = 0;
	}
	
	void Inspect()
	{
		size_t cp = _capacity * 2;
		if (_top == _capacity)
		{
			T* temp = new T[cp];
			for (size_t i = 0; i < _top; i++)
			{
				temp[i] = _arr[i];
			}
			delete _arr;
			_arr = temp;
			_capacity = cp;
		}
	}

	void Push(T val)
	{
		Inspect();
		_arr[_top] = val;
		_top++;
	}

private:
	T* _arr;
	size_t _top;
	size_t _capacity;
};

3.2 类模板的实例化 

在实例化类模板时,将会根据模板参数推断出具体的类型,并生成相应的类实例。实例化后的类可以使用类模板中定义的成员函数和成员变量。

void Test3()
{
	Stack<int> s1;//类模板的实例化
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	s1.Push(5);
}

四、模板实现的原理

4.1 模板的原理

模板的原理是基于编译期间的代码生成,在编译时进行模板的解析和实例化,将模板参数替换成实际的类型或值,并生成对应的函数或类。

编译器遇到模板定义时并不生成实际的函数或类代码,而是在调用或实例化时才会根据需要生成对应的代码

编译器会根据模板的使用情况,在编译时进行两个阶段的处理:

  1. 模板定义实例化阶段:编译器会根据模板定义和使用情况,生成对应的函数或类模板实例的代码,这些代码在编译时并不会立即执行,而是生成待用的模板实例。
  2. 模板实例化调用阶段:在实际调用或实例化模板时,编译器根据具体的模板参数替换模板中的参数,并将模板实例代码插入到调用或实例化的位置,生成最终的可执行代码。

 通过函数调用的地址可以看到,三个函数对应三个地址,说明模板已经被实例化。

4.2 模板参数的匹配原则

模板参数的匹配原则:

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值