C++泛型编程(一):函数模板


模板:建立通用的模具,可提高复用性
特点
(1)模板是一个框架不可直接使用(需确定具体类型);
(2)模板的通用性不是万能的。

C++中的泛型编程思想 ,通过模板实现,包括函数模板类模板等2种模板机制。


1 函数模板

作用:将类型参数化(将固定类型抽象为通用类型),提高代码复用性。
建立一个通用函数声明与定义函数时不具体指定返回值类型形参类型,使用虚拟类型表示;调用函数时再确定其具体类型。

语法

template<typename T>
函数声明或定义

解释
template:关键字,声明创建模板
typename:关键字,类型名称,表明其后符号是通用数据类型,可使用class代替。
T通用数据类型/虚拟类型(通常为大写字母),标识符名称可替换。

注:声明函数模板,可使用template<typename T>template<class T>
声明类模板,可使用template<class T>
思路1:函数模板使用typename,类模板使用class,用作区分。
思路2:函数模板和类模板统一使用class。

使用方式
(1)自动类型推导直接调用模板函数,编译器根据实参类型自动推导。
(2)显式指定类型:调用模板函数时,显式指定参数的类型,如func<指定泛型类型>(...);

示例

#include <iostream>
using namespace std;

//两整型变量交换
void swapInt(int& a, int& b) {
    
    
	int temp = a;
	a = b;
	b = temp;
}

//两字符型变量交换
void swapChar(char& a, char& b) {
    
    
	char temp = a;
	a = b;
	b = temp;
}

/* 函数模板 */
template<typename T>	//声明函数模板
void mySwap(T& a, T& b) {
    
    
	T temp = a;
	a = b;
	b = temp;
}

int main() {
    
    
	//交换整型变量
	int a = 1, b = 2;
	swapInt(a, b);
	cout << "a = " << a << endl;	//2
	cout << "b = " << b << endl;	//1

	//交换字符型变量
	char m = 'x', n = 'y';
	swapChar(m, n);
	cout << "m = " << m << endl;	//y
	cout << "n = " << n << endl;	//x

	/* 函数模板的使用 */
	//1.自动类型推导
	double p1 = 1.1;
	double q1 = 2.2;
	mySwap(p1, q1);
	cout << "p1 = " << p1 << endl;	//2.2
	cout << "q1 = " << q1 << endl;	//1.1

	//2.显式指定类型
	double p2 = 1.1;
	double q2 = 2.2;
	mySwap<double>(p2, q2);
	cout << "p2 = " << p2 << endl;	//2.2
	cout << "q2 = " << q2 << endl;	//1.1

	return 0;
}

2 函数模板注意事项

注意事项
(1)自动类型推导时,必须推导出一致的数据类型T,方可使用,否则编译器报错:没有与参数列表匹配的函数模板“func”实例
(2)函数模板调用时,必须明确通用数据类型T具体数据类型,方可使用,否则编译器报错:没有与参数列表匹配的函数模板“func”实例

注:使用函数模板时,必须明确通用数据类型T具体数据类型,并且可推导出一致的数据类型

示例

#include <iostream>
using namespace std;

/* 函数模板 */
//template<typename T>
template<class T>	//声明函数模板(typename可替换为class)
void mySwap(T& a, T& b) {
    
    
	T temp = a;
	a = b;
	b = temp;
}

template<class T>
void func() {
    
    
	cout << "func()调用..." << endl;
}

int main() {
    
    
	//1.自动类型推导时,必须推导出【一致的数据类型】T,方可使用
	int a = 1;
	int b = 2;
	char c = 'z';
	mySwap(a, b);		//正确
	//没有与参数列表匹配的函数模板“mySwap”实例。参数类型为(int , char)
	//mySwap(a, c);		//错误

	//2.函数模板调用时,必须明确T的【具体数据类型】,方可使用
	//没有与参数列表匹配的函数模板“func”实例
	//func();		//错误
	func<int>();	//正确。将T的类型显式指定为int类型

	return 0;
}

3 函数模板练习:数组排序

练习:利用函数模板,实现数组的降序排序。
示例

#include <iostream>
using namespace std;

/*
	数组排序:选择排序 + 降序排序
*/

//元素交换的函数模板
template<typename T>
void mySwap(T& a, T& b) {
    
    
	T temp = a;
	a = b;
	b = temp;
}

//数组排序的函数模板
template<typename T>
void sortArray(T arr[], int length) {
    
    
	for (int i = 0; i < length; i++) {
    
    
		//降序排序
		int maxIndex = i;		//设定最大值的索引
		for (int j = i + 1; j < length; j++) {
    
    
			if (arr[maxIndex] < arr[j]) {
    
    
				maxIndex = j;	//实际最大值的索引
			}
		}

		//设定最大值的索引与实际最大值的索引不相等:交换两元素
		if (maxIndex != i) {
    
    
			//t temp = arr[i];
			//arr[i] = arr[maxindex];
			//arr[maxindex] = arr[i];

			//函数模板实现元素交换
			mySwap(arr[i], arr[maxIndex]);
		}
	}
}

//遍历数组的函数模板
template<typename T>
void printArray(T arr[], int length) {
    
    
	for (int i = 0; i < length; i++) {
    
    
		cout << arr[i] << " ";
	}
	cout << endl;
}

int main() {
    
    
	//字符数组
	char charArr[] = "bedafc";
	int len1 = sizeof(charArr) / sizeof(charArr[0]);
	sortArray(charArr, len1);
	printArray(charArr, len1);	//f e d c b a

	//整型数组
	int intArr[] = {
    
     4, 2, 5, 3, 6, 1 };
	int len2 = sizeof(intArr) / sizeof(intArr[0]);
	sortArray(intArr, len2);
	printArray(intArr, len2);	//6 5 4 3 2 1

	return 0;
}

4 普通函数与函数模板的区别

区别
普通函数调用时,可发生自动类型转换隐式类型转换
函数模板调用时:
①若使用自动类型推导的方式,不可发生隐式类型转换(必须推导出一致的数据类型T);
②若使用显式指定类型的方式,可发生隐式类型转换

注:调用函数模板时,建议使用显式指定类型的方式,可自行确定通用类型T的具体类型。

示例

#include <iostream>
using namespace std;

//普通函数
int myAdd(int num1, int num2) {
    
    
	return num1 + num2;
}

//函数模板
template<typename T>
T sum(T num1, T num2) {
    
    
	return num1 + num2;
}

int main() {
    
    
	/* 普通函数调用:可发生隐式类型转换 */
	int a = 1;
	int b = 2;
	char ch = 'c';

	cout << myAdd(a, b) << endl;	//3
	//隐式类型转换,char型隐式转换为int型,即a + (int)ch
	cout << (int)ch << endl;		//99
	cout << myAdd(a, ch) << endl;	//100

	/* 函数模板调用 */
	//1.自动类型推导时,不可发生隐式类型转换
	//cout << sum(a, ch) << endl;	//报错:没有与参数列表匹配的函数模板“sum”实例,参数类型为(int,char)

	//2.显式指定类型时,可发生隐式类型转换
	cout << sum<int>(a, ch) << endl;	//100 正确

	return 0;
}

5 普通函数与函数模板同名时的调用规则

调用规则
(1)若普通函数和函数模板均可调用,则优先调用普通函数

注:若普通函数有声明、无定义,且存在同名函数模板,调用同名函数时,编译器报错:无法解析的外部命令

(2)通过空模板参数列表,可强制调用函数模板,即func<>(...);
(3)函数模板可发生函数重载
(4)若函数模板的匹配度更高,则优先调用函数模板

注:提供函数模板时,建议不要提供同名普通函数,否则容易产生二义性

示例:普通函数与函数模板的调用规则

#include <iostream>
using namespace std;

/* 普通函数 */
void print(int a, int b) {
    
    
	cout << "普通函数调用" << endl;
}

/* 函数模板 */
template<typename T>
void print(T a, T b) {
    
    
	cout << "函数模板调用" << endl;
}

/* 函数模板重载 */
//3.函数模板可发生重载
template<typename T>
void print(T a, T b, T c) {
    
    	//函数模板重载
	cout << "重载函数模板调用" << endl;
}

//1.若函数模板和普通函数均可调用,则优先调用普通函数
void func1() {
    
    
	int a = 1;
	int b = 2;

	print(1, 2);	//普通函数调用
}

//2.通过 空模板参数列表,可强制调用函数模板,即func<>(...);
void func2() {
    
    
	int a = 1;
	int b = 2;

	//空模板参数列表
	print<>(1, 2);	//函数模板调用
}

//3.函数模板可发生重载
void func3() {
    
    
	int a = 1;
	int b = 2;
	int c = 3;

	//空模板参数列表
	print<>(1, 2, 3);	//重载函数模板调用
}

//4.若函数模板的匹配度更高,则优先调用函数模板
void func4() {
    
    
	char a = 'a';
	char b = 'b';

	//普通函数:print(int a, int b); //char型需强转为int型
	//函数模板:print(T a, T b);		//可推导出一致数据类型(字符型),匹配度更高
	print(a, b);	//函数模板调用
}

int main() {
    
    
	//func1();
	//func2();		
	//func3();		
	func4();		

	return 0;
}

6 模板的局限性

局限:模板的通用性不是万能的,如函数模板不能用于数组名赋值、自定义数据类型的比较。

不适用场景1:数组名之间不能直接赋值

template<class T>
void func(T a, T b)
{
    
     
	a = b;	//不能用于数组名赋值
}

不适用场景2:自定义数据类型不能比较

template<class T>
void func(T a, T b)
{
    
     
    if(a > b) {
    
     ... }	//不能用于自定义数据类型的比较
 }

针对模板的局限性,C++提供函数模板的重载,针对特定数据类型提供具体化的模板,具体化的模板优先于常规模板。
使用template<>表明重载的函数模板,是针对特定数据类型的具体化版本。
语法template<> 返回类型 函数模板名(自定义数据类型 &a , 自定义数据类型 &b, ...){...}
例:template<> void func(Object &a , Object &b){...}

注1:利用具体化的函数模板,通过对自定义数据类型的特殊处理,可实现自定义类型的通用化。
注2:学习模板的主要目的是在STL中运用系统自带模板,而非自行写模板。

示例:函数模板重载,针对自定义数据类型的具体化模板

#include <iostream>
using namespace std;
#include <string>

class Person {
    
    
public:
	string name;
	int age;

	Person(string name, int age) {
    
    
		this->name = name;
		this->age = age;
	}
};

template<typename T>
bool myCompare(T& a, T& b) {
    
    
	return a == b ? true : false;
}

//使用template<> 表明重载的函数模板,是针对特定数据类型的具体化版本
template<> bool myCompare(Person& a, Person& b) {
    
    
	return a.name == b.name && a.age == b.age;
}

int main() {
    
    
	Person p1("Tom", 18);
	Person p2("Tom", 18);

	int flag = myCompare(p1, p2);
	if (flag) {
    
    
		cout << "p1 == p2" << endl;
	}
	else {
    
    
		cout << "p1 != p2" << endl;
	}

	return 0;
}

猜你喜欢

转载自blog.csdn.net/newson92/article/details/113825601