让你的代码原地起飞——C++模板

让你的代码原地起飞——C++模板_第1张图片

W...Y的主页  

代码仓库分享 


前言:

针对C语言的不足,C++创建了许多功能用来补齐C语言的短板问题。比如:引用、函数重载……这也是我们学习到的C++内容。但是现在对于C语言还是有一些短板与不足:一些函数的复用解决不了。我们学习了模板之后就可以对功能相同的函数进行复用,话不多说,直接开始今天的学习。

目录

泛型编程 

函数模板

函数模板概念

函数模板格式

函数模板的原理

函数模板的实例化

模板参数的匹配原则

类模板

类模板的定义格式


泛型编程 

当我们想对两个数据进行交换时,我们可以创建一个swap函数进行交换。但是一种函数只能实现一种类型的数据交换。怎么样才能实现通用的交换函数呢?

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函数的交换可以使用函数重载写完,但是有一下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错

这是C++就引出了一个模板的做法,给编译器一个函数的模板,编译器就可以根据需求自动生成一种类型的函数进行使用。

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

让你的代码原地起飞——C++模板_第2张图片

函数模板

函数模板概念

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

函数模板格式

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

现在我们可以将前面的所以内容进行修改,舍弃掉函数重载:

template
//template
void swap(t& x, t& y)
{
	t tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 1, b = 2;
	swap(a, b);

	double c = 1.1, d = 2.22;
	//swap(a, b);
	swap(c, d);

	return 0;
}

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

函数模板的原理

我们对上述代码进行F5调试,点击F11后我们可以看到无论是调用int类型的参数还是double类型的参数全部都是从我们的函数模板上通过的,这就说明无论类型是怎样都是调用同一个函数吗?

答案是不是的,我们可以从代码的反汇编中看出调用两个函数的地址不相同,所以不是同一个函数

让你的代码原地起飞——C++模板_第3张图片

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

让你的代码原地起飞——C++模板_第4张图片

我们把这些事情交给编译器去干,让编译器进行对象实例化,在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。 

函数模板的实例化

我们给予编译器函数模板,编译器就会通过参数进行推演将类型匹配的函数进行实例化。下述是一个求和的模板:

template
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.11, d2 = 20.22;
	// 实参传递给形参,自动推演模板类型
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;
	
    return 0;
}

 当参数类型相同时,编译器很容易就推出实例化函数进行调用,但如果时不同类型参数呢?编译器会报错,因为当我们参数不同时会出现矛盾,编译器不知道应该去用哪一种类型进行实例化,

这时我们第一种做法就是可以进行强制类型转换:‘

cout << Add(a1, (int)d1) << endl;
cout << Add((double)a1, d1) << endl;

第二种做法就是显示实例化:

cout << Add(a1, d1) << endl;
cout << Add(a1, d1) << endl;

我们在函数名与参数直接+<类型名>即可,这样我们的编译器不用识别参数类型就转换成提供的类型函数。

模板参数的匹配原则

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

/ 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
/ 通用加法函数
template
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add(1, 2); // 调用编译器特化的Add版本
}

当我们已经拥有一个可以处理int加法函数时,还有一个函数模板,当调用参数为int的加法函数时编译器会直接选择已经有的加法函数,并不会去使用函数模板进行隐式实例化(编译器也会偷懒),当我们还想要使用函数模板调用时我们可以使用显示实例化进行。

对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板 

// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函
数
}

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

总结:C++编译器非常“聪明”,它总是会选择数据最为匹配的那一个!!!

类模板

为什么会有类模板呢?我们的typedef不是也可以解决关于类型的问题吗?typedef只能解决部分问题:

typedef double STDataType;
class Stack
{
private:
	STDataType* _a;
	size_t _top;
	size_t _capacity;
};

int main()
{
	Stack st1; // int
	Stack st2; // double


	return 0;
}

这是我们声明的一个栈,我们使用typedef让私有成员_a的类型为double,我们想储存int时只需将typedef的地方进行修改即可。但是当我们想在同一个地方即定义一个st1与st2,st1用来存放int类型的值,st2用来存放double类型的值时就出现了问题,在同一声明下只能出现一个类型。

如果我们没有学过模板,我们就要与函数一样声明定义多个数据类型不同的类。但是我们学了模板就非常easy了。

类模板的定义格式

template
class stack
{
public:
	stack(int capaicty = 4)
	{
		_a = new t[capaicty];
		_top = 0;
		_capacity = capaicty;
	}

	~stack()
	{
		delete[] _a;
		_capacity = _top = 0;
	}

private:
	t* _a;
	size_t _top;
	size_t _capacity;
};

int main()
{
	stack st1; // int
	stack st2; // double


	return 0;
}

上述就是stack的模板,与函数模板大同小异。但是我们发现在main函数中我们进行定义时并没有进行参数传递,所以编译器不知道我们需要的类型不能自动生成所需要的类,所以这里我们必须要进行显示实例化操作才可以。

 

你可能感兴趣的:(C++,c++,开发语言)