C++ 泛型编程(一):模板基础:函数模板,类模板,模板原理,模板匹配规则

泛型编程

泛型编程,泛型即是指具有在多种数据类型上皆可操作的含义,其实就是能够帮助开发者编写完全一般化并可重复使用的算法,同样的工作不需要做多次,同样的算法针对不同的类型也不应该写多次,所以需要通过某种途径,来使一个容器或者算法,能够兼容所有的类型,这就是泛型编程。

void Swap(int& left, int& right) {
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right) {
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right) {
	char temp = left;
	left = right;
	right = temp;
}

就比如非常常用的swap函数,虽然c++支持重载,但是如果对于这种函数我们每一个都要自己去实现,是一件很麻烦并且多余的工作,因为我们将重复逻辑的东西多次实现。

class Stack
{

private:
	int capacity;
	int size;
	int* arr;
};

又比如一个数据结构,如果我们实现了一个栈,如果他此时要存储其他的数据类型,那我们就必须还要重新修改一个,这无疑是多余的。

所以,c++基于重载这一项机制,实现了模板编程,模板作为类或者函数的蓝图,可以针对不同类型,来实现对应操作。


函数模板

函数模板

函数模板其实代表的是一个函数家族,,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定
类型版本。
而模板函数则时通过函数模板来实例化的具体函数

用法

template<typename T1, typename T2,......,typename Tn>
//typename即类型名,也可以用class替代,这里的class指的是类型,不能用struct替换

只需要在对应的函数前面加上这一句即可,然后将所有需要替换类型的参数改为上面的T1,T2即可

template<class	T>
void Swap(T& left, T& right) {
	T temp = left;
	left = right;
	right = temp;
}

int main()
{
	int i = 3, j = 4;
	double a = 3.4, b = 5.6;
	char x = 'x', y = 'y';

	Swap(i, j);
	Swap(a, b);
	Swap(x, y);

	cout << i << ' ' << j << endl;
	cout << a << ' ' << b << endl;
	cout << x << ' ' << y << endl;
}

在这里插入图片描述
可以看到,这样就可以适用于多重类型。

函数模板的原理

下面来讨论一下,当我们调用这个模板函数的时候,是通过这个模板函数来实现对应的功能?还是它可能会重载成其他函数,再调用其他的函数呢?
这是只需要看看汇编代码就可以了
在这里插入图片描述
可以看到,它调用的是三个不同的函数,这时候就可以推论出来。

模板函数本身并不是一个函数,而是一个蓝图,通过识别我们传入的参数,然后在底层生成一个该类型的模板函数,并调用该函数。

并且这个阶段是在预处理的时候就进行了,因为我们仔细思考一下,如果它是在编译阶段才进行函数的生成,那肯定是无法通过语法的检查的,所以这一阶段只能在预处理进行。

函数模板的实例化

模板的实例化有两种方法,一种是显式实例化,一种是隐式实例化

隐式实例化
template<class T> T Add(const T& left, const T& right) 
{
	return left + right;
}

int main()
{
	int i = 3, j = 4;
	double a = 3.4, b = 5.6;
	Add(i, j);
	Add(a, b);

	/*
	这时编译器就会报错,因为需要通过参数推演出对应类型,但是此时就无法
	推演出到底是将T推演成double还是将T推演成int
	Add(i, a);
	*/

	// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
	Add(i, (int)a);
	return 0;
}

显式实例化

int main() {
 int i = 3;
 double x = 3.4;
 
 // 显式实例化
 Add<int>(i, x);
 
 return 0; 
 }

这种则是用尖括号来显式的声明,STL中的容器等就是采用这种显式的实例化来明确参数的类型。

函数模板的匹配规则(引用)

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数,这时如果使用隐式实例化,则会调用非模板函数,如果使用显式实例化,则调用模板函数
//非模板函数
int Add(int left, int right) 
{
	return left + right;
}

template<class T> 
T Add(T left, T right) 
{
	return left + right;
}

int main()
{
	Add(1, 2); // 调用非模板函数
	Add<int>(1, 2); // 调用模板函数
}
  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 专门处理int的加法函数
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函 数
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

类模板

用法

类模板的用法和函数模板其实一样

template<class T>
class Stack
{

private:
	int capacity;
	int size;
	T* arr;
};

这里的Stack不是一个具体的类,他是类模板,也就是一个蓝图,通过这个模板来识别参数的类型并生成对应的模板类。所以可以理解为类模板是一个类家族,模板类是通过类模板实例化的具体类

如果类中的成员函数需要在类外定义的话,需要每一个函数前都要声明一次类模板的参数列表

template<class T>
class Stack
{
public:
	void push();
	void pop();
private:
	int capacity;
	int size;
	T* arr;
};

template<class T>
void Stack<T>::push()
{

}

template<class T>
void Stack<T>::pop()
{

}

还有一个需要注意的地方就是,类模板的所有成员函数都是模板函数

类模板的实例化

类模板只能够通过显式实例化
如STL中的几个容器都是通过类模板实现的

	stack<int> s1;
	stack<double> s2;

stack<int>并不是类名,而是类型,stack才是类名


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章