C++学习笔记21-函数模板

本阶段主要针对C++泛型编程和STL技术做详细讲解 , 探讨C++更深层的使用。

21.0 模板的概念

模板就是建立通用的模具,大大提高复用性。

例如生活中的模板:

一寸照片模板,PPT模板,word模板

模板的特点:

  • 模板不可以直接使用,它只是一个框架。
  • 模板的通用并不是万能的。

C++中另一种编程思想称为泛型编程,主要利用的技术就是模板。
C++提供两种模板机制:函数模板和类模板


21.1 函数模板语法

函数模板作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来表示。

语法:

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

//其中<>为函数模板的参数列表。

解释:

  1. template – 声明创建模板
  2. typename – 表面其后面跟着的符号是一种数据类型,可以用class代替
  3. T – 通用的数据类型,名称可以替换,通常为大写字母。

模板的使用语法:

1.自动类型推导 函数(参数)  
2.显示指定类型 函数<数据类型>(参数)

示例:

#include<iostream>
using namespace std;

//函数模板
//交换两个整型函数
void swapInt(int& a, int& b)
{
    
    
	int temp = a;
	a = b;
	b = temp;
}

//交换两个浮点型函数
void swapDouble(double& a, double& b)
{
    
    
	double temp = a;
	a = b;
	b = temp;
}

//函数模板
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
void mySwap(T& a, T& b)
{
    
    
	T temp = a;
	a = b;
	b = temp;
}

//1.自动类型推导 mySwap(a,b)
//2.显示指定类型 mySwap<数据类型>()

void test1_01()
{
    
    
	int a = 10, b = 20;
	cout <<"a = " << a <<" b = "<< b << endl;
	swapInt(a, b);
	cout << "a = " << a << " b = " << b << endl;
	mySwap<int>(a, b);
	cout << "a = " << a << " b = " << b << endl;

	double c = 5.25, d = 10.25;
	cout << "c = " << c << " d = " << d << endl;
	swapDouble(c, d);
	cout << "c = " << c << " d = " << d << endl;
	mySwap<double>(c, d);
	cout << "c = " << c << " d = " << d << endl;

	float e = 1.2, f = 3.5;
	cout << "e = " << e << " f = " << f << endl;
	mySwap(e, f);
	cout << "e = " << e << " f = " << f << endl;
}

int main()
{
    
    
	test1_01();
	system("pause");
	return 0;

}

21.2 函数模板注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用。
  • 模板必须要确定出T的数据类型,才可以使用。

示例:

#include<iostream>
using namespace std;

template<class T> //typename和class是相同的
void mySwap2(T& a, T& b)
{
    
    
	T temp = a;
	a = b;
	b = temp;
}
void test2_01()
{
    
    
	int a = 10, b = 20;
	cout << "a = " << a << " b = " << b << endl;
	mySwap2(a, b);
	cout << "a = " << a << " b = " << b << endl;
	double c = 20.0;
	//mySwap2(a, c); 会报错,因为传入数据类型不一致.
}

template<class T>
void func2()
{
    
    
	cout << "func的调用" << endl;
}
void test2_02()
{
    
    
	//func2();  会报错,因为在这里编译器不能推导数据类型是什么
	func2<int>();
}

int main()
{
    
    
	test2_01();
	system("pause");
	return 0;
}

总结:
  使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型。


21.3 函数模板案例-数组排序

案例描述:

  • 利用函数模板封装一个排序的函数, 可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试
#include<iostream>
using namespace std;
template<class T>
int CoutArr(T& arr)
{
    
    
	return sizeof(arr)/sizeof(arr[0]);
}

template<typename T>
void ArrSort(T arr[],int len)
{
    
    
	for (int i = 0; i < len; i++)
	{
    
    
		int max = i;  //假设当前的下标是最大的
		for (int j = i+1; j < len; j++)
		{
    
    
			if (arr[j] > arr[max])   
			{
    
    
				max = j;   //如果此元素比假设的元素大,就更新下标
			}
		}
		if (max != i)   //如果下标换了,就交换元素。
		{
    
    
			T temp = arr[i];
			arr[i] = arr[max];
			arr[max] = temp;
		}
	}
}
template<typename T>
void printArr(T arr[],int len)
{
    
    
	for (int i = 0; i < len; i++)
	{
    
    
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test3_01()
{
    
    
	char charArr[] = "bdafeg";
	printArr(charArr, CoutArr(charArr));
	ArrSort(charArr,CoutArr(charArr));
	printArr(charArr, CoutArr(charArr));
	
	cout << "--------------" << endl;
	
	int intArr[] = {
    
     2,4,3,1,7,0,3 };
	printArr(intArr, CoutArr(intArr));
	ArrSort(intArr,CoutArr(charArr));
	printArr(intArr, CoutArr(intArr));
}
int main()
{
    
    
	test3_01();
	system("pause");
	return 0;
}

参考 : 在函数中计算数组大小


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

  • 普通函数调用时可以发生自动类型转换(隐士类型转换)。
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。
  • 如果利用显式指定类型的方式,可以发生隐式类型转换。

示例:

#include<iostream>
using namespace std;

//普通函数可以发生隐式类型转换
//函数模板如果使用自动类型推导,则不可以发生隐式类型转换
//函数模板如果使用显示指定类型,则可以发生隐式类型转换

//普通函数
int myAdd01(int a, int b)
{
    
    
	return a + b;
}

template<class T>
T myAdd02(T a,T b)
{
    
    
	return a + b;
}

void test4_01()
{
    
    
	int a = 10, b = 20;
	cout << myAdd01(a, b) << endl;
	cout << myAdd02(a, b) << endl;
	char c = 'c';
	cout << myAdd01(a, c) << endl;  //c会转换会int型,就是隐式转换为ASCII码值。
	//cout << myAdd02(a, c) << endl;  //会报错,因为编译器不知道应该将c转化为int型还是把a转化为char型,无法确定
	cout << myAdd02<int>(a, c) << endl; 
	cout << myAdd02<char>(a, c) << endl; //显式指定类型后,则可以隐式类型转换。

}
int main()
{
    
    
	test4_01();
	system("pause");
	return 0;
}

建议使用的时候使用显示指定类型,减少出错。


21.5 重名普通函数与函数模板的调用规则

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数(即使只声明了普通函数而没有实现出现报错,也不会调用函数模板)。
  2. 可以通过空模板函数列表强制调用函数模板(语法:函数名<>(参数列表),只要有<>,无论<>中是否空,都会强制调用函数模板)。
  3. 函数模板也可以发生重载(与普通函数没区别)。
  4. 如果函数模板可以产生更好的匹配,优先调用函数模板(比如调用普通函数需要隐式类型转换,那么会优先调用函数模板)。

21.6 函数模板的局限性

局限性:
函数模板仅仅是使得传入函数的参数数据类型更加灵活,对于函数的重载等功能并没有直接实现的模板,需要特殊实现。

比如:

template<class T>
void f(T a, T b)
{
    
    
	a = b; 
}

在上述代码中提供的赋值操作,如果传入的a和b是数组,就无法实现了。

再比如:

template<class T>
void f(T a, T b)
{
    
    
if(a>b){
    
    ....}
}

在上述代码中,如果T的数据类型传入的是像Person这样的自定义数据类型,也无法正常运行

因此C++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
对于以上问题,解决方案有两个:

  1. 运算符重载
  2. 函数模板重载,并使程序优先调用函数模板,语法:template<> 函数定义可以使此函数模板优先于同名函数被调用,但是这里<>是空参数,所以要自己手动在函数列表中填入具体的参数数据类型。

示例:

#include<iostream>
using namespace std;

//对比两个数据是否相等
template<class T>
bool myCompare(T& a, T& b)
{
    
    
	if (a == b)
	{
    
    
		return true;
	}
	
	else
	{
    
    
		return false;
	}
}
void test6_01()
{
    
    
	int a = 10, b = 20; 
	if (myCompare(a, b))
	{
    
    
		cout << "a == b" << endl;
	}
	else 
	{
    
    
		cout << "a != b" << endl;
	}
}

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

//利用具体化Person的版本实现代码,具体化优先调用
class Person;
template<> bool myCompare(Person& p1, Person& p2)
{
    
    
	if (p1.name == p2.name && p1.age == p2.age)
	{
    
    
		return true;
	}
	else {
    
     return false; }
}

void test6_02()
{
    
    
	Person p1("Tom", 10);
	Person p2("Tom", 11);

	if (myCompare(p1, p2))
	{
    
    
		cout << "p1 == p2" << endl;
	}
	else
	{
    
    
		cout << "p1 != p2" << endl;
	}
}
int main()
{
    
    
	test6_01();
	test6_02();
	system("pause");
	return 0;
}

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化。
  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板。

21.7 函数模板的分文件编写

和类模板的分文件编写原理相同。

猜你喜欢

转载自blog.csdn.net/qq_49030008/article/details/123538422