C++基础篇之模板

文章目录

  • 泛型编程
  • 函数模板
    • 函数模板的概念
    • 函数模板的格式
    • 函数模板的原理
    • 函数模板的实例化
    • 模板参数的匹配原则
  • 类模板
    • 类模板的格式
    • 类模板的实例化

泛型编程

之前学习函数重载的时候,我们写了一个交换函数Swap

void Swap(int& left, int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
void Swap(char& left, char& right)
{
	char tmp = left;
	left = right;
	right = tmp;
}
void Swap(double& left, double& right)
{
	double tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 0, b = 1;
	char c = 'A', d = 'B';
	double e = 1.1, f = 2.2;
	Swap(a, b);
	Swap(c, d);
	Swap(e, f);
	
	return 0;
}

虽然C++支持函数重载,让我们可以同时实现多个类型的变量进行交换,但是这仍存在不好的地方:

  • 重载的函数仅仅类型不同,代码复用率低,新的类型出现时,需要增加对应的函数
  • 代码的可维护性差,某一个出错可能需要修改所有的重载函数

C++为了解决以上问题,提出泛型编程的概念!!!

泛型编程是指编写与类型无关的通用代码,是代码复用的通用手段。模板是泛型编程的基础

其中模板分为函数模板类模板

函数模板

函数模板的概念

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

函数模板的格式

template
返回值类型 函数名(参数列表)
{
//…
}

// 以上面介绍的Swap函数为例
template <class T>			 //模板参数列表  ——参数类型
void Swap(T& left, T& right) //函数参数列表  ——参数对象
{
	T tmp = left;
	left = right;
	right = tmp;
}

注意:typename关键字可以替换class用于定义模板参数,但struct不能代替class。

函数模板的原理

函数模板并不是一个函数,是编译器用于产生特定类型函数的模具,所以模板将本应该我们进行的复杂操作交给了编译器执行。

思考一个小问题,主函数中的3个Swap函数是一样的吗?

template <class T>			
void Swap(T& left, T& right) 
{
	T tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 0, b = 1;
	char c = 'A', d = 'B';
	double e = 1.1, f = 2.2;
	Swap(a, b);
	Swap(c, d);
	Swap(e, f);
	return 0;
}

答案显然是不一样的,因为每个Swap函数参数类型都不相同,3个Swap函数会依据实参参数类型转换成不同的模板函数,如下图所示
C++基础篇之模板_第1张图片
同时我们通过汇编发现不同Swap函数Call的地址不同,也可以验证结果是正确的!
C++基础篇之模板_第2张图片
编译器编译阶段:编译器通过传入的实参类型来推演生成对应的模板函数

函数模板的实例化

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

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型
template <class T>			 		 //模板参数列表  ——参数类型
T Add(const T& left, const T& right) //函数参数列表  ——参数对象
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.10, d2 = 20.20;
	// 隐式实例化
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;
	return 0;
}

注意cout << Add(a1, d2) << endl; 是不对的,因为两个实参类型不同,编译器无法推演出T为int还是double。
此时有两种处理方式:
(1)用户自己强制转换

	cout << Add(a1, (int)d2) << endl;
	cout << Add((double)a1, d2) << endl;`

(2)使用显示实例化

  1. 显示实例化:在函数名后的<>中指定模板参数的实际类型
	cout << Add<int>(a1, a2) << endl;
	cout << Add<double>(d1, d2) << endl;
	cout << Add<int>(a1, d2) << endl;
	cout << Add<double>(a1, d2) << endl;

模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 非模板函数
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<int>(1, 2); // 调用编译器特化的Add版本
}

Add(1, 2);会直接与非模板函数匹配,因为编译器检测到存在专门处理int类型的Add函数,便不会用函数模板特化;
Add(1, 2);是显示实例化,编译器会根据函数模板特化出一个实例Add函数。

  1. 对于非模板函数和同名函数模板,如果其他条件都相同,会优先调用非模板函数;如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 非模板函数
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(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函}

Add(1, 2); 与非模板函数完全匹配,不需要模板实例化;
Add(1, 2.0);虽然可以隐式类型转换,但这里可以用函数模板特化出更加匹配的Add(int, double)函数。

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

类模板

类模板的格式

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

以栈为对象,编写一个栈类模板,代码如下:

template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0)
		, _capacity(capacity)
	{
		_a = new T[capacity];
	}
	~Stack()
	{
		delete[] _a;
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	T* _a;
	int _top;
	int _capacity;
};

PS:此时如果想把类的成员函数在类中声明,类外定义,需要加模板参数列表

template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0)
		, _capacity(capacity)
	{
		_a = new T[capacity];
	}
	// 在类中声明,类外定义
	~Stack();
	void Push(const T& x);
private:
	T* _a;
	int _top;
	int _capacity;
};
// 析构函数在类外定义
template <class T>
Stack<T>::~Stack()
{
	delete[] _a;
	_a = nullptr;
	_top = _capacity = 0;
}
// 插入函数在类外定义
template <class T>
void Stack<T>::Push(const T& x)
{
	//...
}

类模板的实例化

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

int main()
{
	Stack<int> s1;
	Stack<double> s2;
	Stack<char> s3;
	Stack<int*> s4;
	return 0;
}

注意:在之前学习类的时候,类名和类型是一样的。但在类模板中,以栈为例,Stack类名StackStackStackStack类型

你可能感兴趣的:(C++基础篇,c++,开发语言,后端)