C++模板是泛型编程的基础,在C++中模板包括:
函数模板
定义一个函数的模板,编译器可根据传入参数的类型生成相应的函数,实现了写一个函数可适配不同输入参数。
模板对类型进行了参数化,提高了代码的通用型。
定义
template <typenamne T>
函数声明和定义
- template — 声明创建模板
- typename — 表明其后的符合是一种数据类型,和class等同
- T — 通用数据类型的名称
使用
// 实现一个可交换两个参数的函数
template <typename T> // 声明一个模板
void myswap(T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
函数模板的调用方式:
- 自动类型推导
- 显示类型指定
自动类型推导
T的类型由编译器根据传入的参数类型推导出,编译器根据推导出的类型在模板基础上来实现相应的函数。
void main()
{
int a = 10;
int b = 20;
myswap(a, b); //自动类型推导
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
显式类型指定
T的类型在调用时通过<类型>显式指定,编译器根据指定的类型在模板的基础上来实现相应函数
void main()
{
int a = 10;
int b = 20;
myswap<int>(a, b); //显式类型指定
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
使用注意事项
- 自动类型推导必须推导出一致的数据类型T,才可以使用;
- 模板必须确定T的数据类型,才可以使用;
- 使用显式类型指定来调用模板是一个好的习惯 。
示例:
void main()
{
int a = 10;
int b = 20;
char c = 'f';
//myswap(a, c); //自动类型推导,不能使用
// a、c类型不一致,对编译器来说有二义性
// 编译器不能确定是根据a的类型还是根据b的类型来实现函数
myswap<int>(a, c); //显示类型指定,可以使用
// 这时编译器根据int类型实现函数,并对c进行隐式类型转换
}
示例
template <typename T>
void fun()
{
cout << "test" << endl;
}
void main()
{
//fun(); //不能使用
// 还是一样的原因,编译器无法确定T的类型,不知道如何去实现相应的函数
fun<int>(); // 可以使用
}
案例
- 实现一个从大到小的排序的目标函数,实现对不同数据类型的数据进行排序
- 使用选择排序来实现
template <typename T> // 声明一个模板
void myswap(T & a, T & b)
{
T temp;
temp = a;
a = b;
b = temp;
}
// 选择排序模板函数
template <typename T>
void selectionSort(T array[], int len)
{
int max;
for (int i = 0; i < len; i++)
{
max = i; // 假设未排序元素第一个位置为最大元素
for (int j = i; j < len; j++)
{
if (array[j] > array[max])
max = j;
}
if (max != i) // 最大元素未在正确位置
{
myswap<T>(array[i], array[max]);
}
}
}
// 打印数组模板模板函数
template <typename T>
void printArray(T array[],int len)
{
for (int i = 0; i < len; i++)
{
cout << array[i] << " ";
}
cout << endl;
}
void main()
{
// 测试函数模板排序
int intArray[] = { 4, 5, 6, 3, 6, 10, 75 };
selectionSort<int>(intArray, 7);
printArray<int>(intArray, 7);
char charArray[] = {"dfeegegd"};
selectionSort<char>(charArray, 7);
printArray<char>(charArray, 7);
}
函数模板和普通函数的区别
主要是隐式类型转换上
- 普通函数在调用时是可以进行隐式类型转换的;
- 函数模板,使用自动推导类型不可以进行隐式类型转换(根本原因还是编译器不知道如何实现函数,对照使用注意事项内容);
- 函数模板,使用显式类型指定,可以进行隐式类型转换。
结论:在使用函数模板时,建议使用显式类型指定来调用,即可以避免一些问题,也增加了程序的可读性。
函数模板和普通函数调用规则
- 如果调用时,普通函数和函数模板都可以匹配,则调用普通函数
- 可以通过空模板参数强制调用普通函数
- 函数模板也可以进行重载
- 如果函数模板比普通函数更加匹配,则调用函数模板
实例:
//普通函数
void myPrint(int a)
{
cout << "调用普通函数" << endl;
}
//模板函数
template <typename T>
void myPrint(T a)
{
cout << "调用模板函数" << endl;
}
//模板函数重载
template <typename T>
void myPrint(T a, T b)
{
cout << "调用重载模板函数" << endl;
}
void mian()
{
int a = 10;
int b = 20;
char c = 'c';
myPrint(a); //普通函数和模板函数都匹配,调用普通函数
myPrint<>(a); // 通过空模板参数强制调用模板函数
myPrint(a,b); // 调用重载模板函数
myPrint(c); // 函数模板更匹配,调用函数模板
}
建议:定义了函数模板,就不用再去定义相应的普通函数了,容易使代码可读性变差。
函数模板的局限性
- 自定义类型如果没有定义相应运算符操作,则程序不知道如何对自动义类型进相应操作,程序报错
实例:
// 定义一个person类
class person {
public:
person(string name, int age) {
this->mName = name;
this->mAge = age;
}
string mName;
int mAge;
};
//定义一个比较函数模板
template <class T>
bool myCompare(T a, T b)
{
if (a == b)
return true;
else
return false;
}
//函数模板具体化
template <>
bool myCompare(person a, person b)
{
if ((a.mName == b.mName) && (a.mAge == b.mAge))
return true;
else
return false;
}
void mian()
{
person p1("aimi", 10);
person p2("tomi", 10);
bool res = myCompare(p1,p2); // 不可以直接使用,编译时报错,实现模板具体化后则可常使用
//解法方法
// 1、对自定义类型实现操作符重载
// 2、对类模板实现具体化,程序优先调用具体化的模板
if(res)
cout << "p1 == p2" << endl;
else
cout << "p1 != p2" << endl;
}