C++ 函数模板

什么叫模板?

C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。


为什么要使用函数模板?

为了使代码更加简明,提高开发效率!

例:
需要判断整型,字符型,浮点类型的大小,那么就必须写三个这样的判断函数,这样不仅在代码上,而且还在内存上造成一定的浪费,所以最好的解决办法就是用模板

代码示例:

#include 
#include 

using namespace std;

// 模板说明
template <typename T>
// 函数实现
T Max(T a, T b) {
	return a > b ? a : b;
}

int main(void) {
	cout << "整型和整型比较" << endl;
	cout << Max(10, 20) << endl;
	// 相当于调用了:
	/*int Max(int a, int b) {
		return a > b ? a : b;
	}*/

	cout << endl << "字符型和字符型比较" << endl;
	cout << Max('a', 'b') << endl;
	// 相当于调用了:
	/*char Max(char a, char b) {
		return a > b ? a : b;
	}*/

	cout << endl << "浮点型和浮点型比较" << endl;
	cout << Max(10.5, 20.5) << endl;
	// 相当于调用了:
	/*float Max(float a, float b) {
		return a > b ? a : b;
	}*/

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第1张图片

可以看出使用函数模板和自己定义三个普通函数的效果是一模一样的!
使用函数模板,只需要一个函数就可以搞定了!


函数模板的语法

所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。


函数模板的定义

1.模板说明

template < 类型形式参数表 >

类型形式参数表:
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
(注:typename 和 class 的效果完全等同), 建议使用 typename 关键字。

例:
template

2.函数定义

类型 函数名 (形式参数表) {
// to do…
}
注意:模板说明的类属于参数,必须在函数定义之前出现一次;
函数参数表中可以使用类,属于类型参数,也可以使用一般类型参数。

例:

T function(T a, T b, Tc) {
	// T是模板说明中的类型
}
3.函数模板调用

1.function(10, 20, 30); // 显式类型调用
2.function(10, 20, 30); // 自动数据类型推导

调用后,函数模板自动声明Tint类型!

4.模板函数

C++ 函数模板_第2张图片

自己在代码中说明的叫 函数模板
编译器自动生成的叫模板函数


函数模板的多种形参

在上面 为什么要使用函数模板? 中,我们说明函数模板只是说明一种类型,那么可以说明多种吗?
答案是可以的。

例:
使用函数模板,比较整型和浮点型的大小,并以浮点型作为返回值类型。

代码示例:

#include 
#include 

using namespace std;

// 模板说明
template <typename T, typename Y>
// 函数实现
Y Max(T a, Y b) {
	return a > b ? a : b;
}

int main(void) {
	int n = 10;
	float f = 10.5;

	cout << "比较结果:" << Max(n, f) << endl;
	// 实际上调用了:
	/*float Max(int a, float b) {
		return a > b ? a : b;
	}*/

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第3张图片

定义再多的形成都是可以的!


函数模板可以比较类吗?

答案是可以的!

例:

  1. 我们先定义一个类, 再定义私有数据成员 k ;
  2. 定义成员函数返回 k 的值;
  3. 重载 > 运算符(因为不重载没法比较)
  4. mian函数实现

代码实例:

#include 
#include 

using namespace std;

class demo {
public:
	demo(int k = 0) { this->k = k; }

	int getK() const { return k; }

	bool operator>(demo& de) {
		if (this->k > de.k) return true;
		return false;
	}
private:
	int k;
};

template <typename T>
T Max(T a, T b) {
	return a > b ? a : b;
}
// 也可以写成这样******************
//template 
//Y Max(T a, Y b) {
//	return a > b ? a : b;
//}


int main(void) {

	cout << "类与类的比较:" << endl;
	demo d1(10);
	demo d2(20);
	cout << Max(d1, d2).getK() << endl;
	// 实际上调用了:
	/*demo Max(demo a, demo b) {
		return a > b ? a : b;
	}*/

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第4张图片


函数模板与函数重载

到了这里,我们有两个问题:

  1. 函数模板定义的函数 和 普通定义的函数名字相同(即重载)可以并存吗?
  2. 函数模板可以自动类型转化吗?

带着这个问题,我们开始上代码:

问题一:

  1. 函数模板定义的函数 和 普通定义的函数名字相同(即重载)可以并存吗?

例:

定义一个函数模板用于交换字符型和整型,再定义一个普通函数也是用于交换字符型和整型(其中,两个函数的名字一样)

代码示例:

#include 
#include 

using namespace std;

// 函数模板函数
template <typename T, typename Y>
void swop(T& a, Y& b) {
	T c;

	c = a;
	a = b;
	b = c;

	cout << "函数模板被调用了" << endl;
}

// 普通函数
void swop(char& a, int& b) {
	int c;

	c = a;
	a = b;
	b = c;

	cout << "普通函数被调用了" << endl;
}

int main(void) {
	int n = 65;
	char c = 'a';

	// 函数模板和普通函数并存,参数类型和普通函数重载更匹配
	// 调用普通函数
	swop(c, n);

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第5张图片
由运行结果我们可以知道:两者允许并存
但为什么是调用普通函数而不是调用模板函数呢?
这个问题会在下面 函数模板和普通函数的调用顺序 有详细解析!

问题二:

  1. 函数模板可以自动类型转化吗?

例:

接着上面的代码,我们把普通函数去掉会怎么?

代码示例:

#include 
#include 

using namespace std;

template <typename T, typename Y>
void swop(T& a, Y& b) {
	T c;

	c = a;
	a = b;
	b = c;

	cout << "函数模板被调用了" << endl;
}

//void swop(char& a, int& b) {
//	int c;
//
//	c = a;
//	a = b;
//	b = c;
//
//	cout << "普通函数被调用了" << endl;
//}

int main(void) {
	int n = 65;
	char c = 'a';

	// 函数模板和普通函数并存,参数类型和普通函数重载更匹配
	// 调用普通函数
	//swop(c, n);

	// 可以运行,没有报错
	swop(c, n);

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第6张图片

我们可以发现,没有报错,且可以运行!

所以,第二个问题答案是:
函数模板允许自动类型转化?

No,No, 还有另外一种情况:

上面我们定义函数模板的是使用两个类型:

template <typename T, typename Y>	// 两种类型
void swop(T& a, Y& b) {
	T c;

	c = a;
	a = b;
	b = c;

	cout << "函数模板被调用了" << endl;
}

那么,如果我们使用一种类型会怎么样呢?

template <typename T>		// 一种类型
void swop(T& a, T& b) {
	T c;

	c = a;
	a = b;
	b = c;

	cout << "函数模板被调用了" << endl;
}

我们不敢胡乱推测,下面我们用编译器说话:

修改后的代码:

#include 
#include 

using namespace std;

template <typename T>
void swop(T& a, T& b) {
	T c;

	c = a;
	a = b;
	b = c;

	cout << "函数模板被调用了" << endl;
}

//void swop(char& a, int& b) {
//	int c;
//
//	c = a;
//	a = b;
//	b = c;
//
//	cout << "普通函数被调用了" << endl;
//}

int main(void) {
	int n = 65;
	char c = 'a';

	// 函数模板和普通函数并存,参数类型和普通函数重载更匹配
	// 调用普通函数
	//swop(c, n);

	// 如果把普通的函数注释掉,那么,将会报错
	// 函数模板不提供隐式的数据类型转换,必须是严格的匹配
	swop(c, n);

	system("pause");
	return 0;
}

没想到,居然出错了…

出错截图:

C++ 函数模板_第7张图片
现在还敢不敢妄下推论了?

我们从这个例子中,可以得到:函数模板不允许自动类型转化

所以,有两个结论,到底该选哪个好?

其实各有春秋,这得看定义了什么样的模板函数,就会有与其对应的结论。

  1. 如果是两个参数的模板函数,那么结论是:函数模板允许自动类型转化
  2. 如果是一个参数的模板函数,那么结论是:函数模板不允许自动类型转化

函数模板和普通函数的调用顺序

经过以上的示例,我们已经学会了函数模板的基本使用,那么,函数模板和普通函数的调用顺序是怎么样的呢?

我们也通过编译器推出答案。

例:

  1. 定义函数模板,用于比较两个整数的大小(用一个参数)
  2. 定义普通函数,用于比较两个整数的大小(和函数模板重名,即重载)
  3. main 函数测试

代码示例:

#include 
#include 

using namespace std;

// 当普通函数比函数模板更加精确时,会优先调用普通函数
template <typename T>
T compare(T a, T b) {
	cout << "调用了模板函数:T compare(T a, T b)" << endl;
	return a > b ? a : b;
}

int compare(int a, int b) {
	cout << "调用了普通函数:int compare(int a, int b)" << endl;
	return a > b ? a : b;
}


int main(void) {
	int a = 10;
	int b = 20;

	compare(a, b);



	// 可以指定调用函数模板
	cout << endl << endl << "---此处是验证是否可以指定使用哪个函数---(不计入测试结果)" << endl;
	compare<int>(a, b);

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第8张图片

由运行结果我们可以知道:即使函数模板也符合调用,但是,编译器会选择更加精确的普通函数来调用!

因为调用函数模板还存在一个类型转换的过程!

==

还有一个问题,如果把普通函数定义为不精确,那么编译器会调用哪个函数呢?

我们还是以编译器来推论:

还是上面的代码,我们把模板函数定义为两个参数,且在mian函数中,测试一个整型和一个浮点型的比较,那么,普通函数就变成不精确了。

代码示例:

#include 
#include 

using namespace std;

 // 当函数模板比普通函数更精确时,调用函数模板
template <typename T, typename Y>
T compare(T a, Y b) {
	cout << "调用了模板函数:T compare(T a, Y b)" << endl;
	return a > b ? a : b;
}

int compare(int a, int b) {
	cout << "调用了普通函数:int compare(int a, float b)" << endl;
	return a > b ? a : b;
}


int main(void) {

	int a = 10;
	float b = 20.5;

	compare(a, b);




	// 可以指定调用函数模板
	cout << endl << endl << "---此处是验证是否可以指定使用哪个函数---(不计入测试结果)" << endl;
	compare<int, float>(a, b);

	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第9张图片

由运行截图我们可以知道:当函数模板比普通函数更精确时,则调用函数模板


函数模板也可以像普通函数一样叠加调用

代码示例:

#include 
#include 

using namespace std;

template <typename T, typename Y>
T Max(T a, Y b) {
	cout << "调用了模板函数:T Max(T a, Y b)" << endl;
	return a > b ? a : b;
}

template <typename T>
T Max(T a, T b, T c) {
	cout << "调用 T Max(T a, T b, T c)" << endl;
	return Max(Max(a, b), c);
}
	


int main(void) {

	float a = 10.5;
	float b = 20.5;
	float c = 30.5;

	Max(a, b, c);


	system("pause");
	return 0;
}

运行截图:

C++ 函数模板_第10张图片


总结:
函数模板和普通函数在一起,调用规则:
1 函数模板可以像普通函数一样被重载
2 C++编译器优先考虑普通函数
3 如果函数模板可以产生一个更好的匹配,那么选择模板
4 可以通过空模板实参列表的语法限定编译器只通过模板匹配

结论:

  1. 编译器并不是把函数模板处理成能够处理任意类型的函数
  2. 编译器从函数模板通过具体类型产生不同的函数

你可能感兴趣的:(c++,c++)