前言
模板是一个通用框架,是C++泛型编程思想的主要体现。C++提供了函数模板和类模板两种模板机制,本文介绍的是函数模板相关的知识。
一、函数模板的作用及语法
作用:用一个虚拟的类型来代表函数的返回值类型和形参的类型,在调用函数时根据实际需求传不同类型的参数以达到函数通用的目的,增加代码的复用性。
语法:
template <typename T>
返回值 函数名(参数){}
解释:
template 声明创建模板
typename 也可以用class,后面接的符号代表一种数据类型
T 是一个通用的数据类型,名称自定义,通常为大写字母
示例: 交换两个数
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;
}
void Swaptest()
{
int a = 10;
int b = 20;
swapInt(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 30;
double d = 40;
swapDouble(c, d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
int main()
{
Swaptest();
return 0;
}
上面这种方式每一种类型都要分别写一个对应的交换函数但是swapInt函数和swapDouble函数代码大部分都是一样的,只是函数名和参数类型有所不同,对于此我们可以使用函数模板的方式来提高代码复用率,减少代码量。具体代码如下所示:
//函数模板
template<typename T> //声明一个模板,告诉编译器后面代码中紧跟的T不要报错,T是一个通用数据类型
void MySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
void Swaptest()
{
int a = 10;
int b = 20;
//利用函数模板交换
//调用函数模板有隐式类型推导和显式类型推导两种方式
// 1、隐式类型推导
MySwap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 30.0;
double d = 40.0;
// 2、显式类型推导
MySwap<double>(c, d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
int main()
{
Swaptest();
return 0;
}
函数模板调用方式有两种:显式类型推导和隐式类型推导
需要注意的是:
使用显式类型推导,参数和推导的类型必须一致。
如果有普通函数和模板函数,在调用时可以用显式调用,省略类型的方式 MySwap<>(1,2)
二、函数模板案例
案例描述:
- 利用函数模板封装一个排序函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
案例:
#include <iostream>
#include <string>
using namespace std;
template<class T>
void mySwap(T &a, T &b) //交换函数 模板
{
T temp = a;
a = b;
b = temp;
}
template<typename T>
void mySort(T arr[], int len) //排序算法
{
for (int i = 0; i < len; i++)
{
int max = i; //认定最大值的下标
for (int j = i; j < len; j++)
{
if (arr[max] < arr[j])
{
max = j; //更新最大值下标
}
}
mySwap(arr[max], arr[i]); //交换max和i元素
}
}
template<typename T>
void printArray(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void Mytest()
{
char charArr[] = "badcfe"; //测试char数组
int num = sizeof(charArr) / sizeof(char);
mySort(charArr, num);
printArray(charArr, num);
}
int main()
{
Mytest();
return 0;
}
三、普通函数与函数模板的区别
普通函数与函数模板的区别:
- 普通函数只可以有一种数据类型相匹配。函数模板有多种类型
- 隐式推导优先使用普通函数,只有普通函数不匹配才使用函数模板
- 函数模板只有在调用时,才会构建函数,而普通函数是在编译时
- 普通函数调用时候可以发生自动类型转换,而函数模板不行
示例:
int myAdd1(int a, int b) //普通函数
{
return a + b;
}
template<class T>
int myAdd2(T a, T b) //函数模板
{
return a + b;
}
void Mytest()
{
int a = 10;
int b = 20;
char c = 'c';
cout << myAdd1(a, c) << endl;
//隐式类型推导
cout << myAdd2(a, b) << endl;
//cout << myAdd2(a, c) << endl;会报错,T的类型不一致
//显示指定类型
myAdd2<int>(a, c); //不会报错,会发生隐式类型转换
}
int main()
{
Mytest();
return 0;
}
四、普通函数与函数模板的调用规则
调用规则如下:
-
如果函数模板和普通函数都可以实现,优先调用普通函数
void myPrint(int a, int b)
{
cout << "调用的普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
void Mytest()
{
int a = 10;
int b = 20;
myPrint(a, b);
}
int main()
{
Mytest();
return 0;
}
运行结果:
-
可以通过空模板参数列表来强制调用函数模板
void myPrint(int a, int b)
{
cout << "调用的普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
void Mytest()
{
int a = 10;
int b = 20;
myPrint<>(a, b); //通过空模板参数列表来强制调用函数模板
}
int main()
{
Mytest();
return 0;
}
运行结果:
-
函数模板也可以发生重载
template<typename T>
void myPrint(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
template<typename T>
void myPrint(T a, T b,T c)
{
cout << "调用的是重载的函数模板" << endl;
}
void Mytest()
{
int a = 10;
int b = 20;
myPrint(a, b, 100);
}
int main()
{
Mytest();
return 0;
}
运行结果:
-
如果函数模板可以产生更好的匹配,优先调用函数模板
void myPrint(int a, int b)
{
cout << "调用的普通函数" << endl;
}
template<typename T>
void myPrint(T a, T b)
{
cout << "调用的是函数模板" << endl;
}
void Mytest()
{
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2);
}
int main()
{
Mytest();
return 0;
}
运行结果:
-
模板的重载与嵌套使用
#include <iostream>
using namespace std;
template <class T>
T Max(T a,T b)
{
return((a > b)?a : b);
}
template <class T>
T Max(T a,T b,T c)
{
return Max(Max(a,b),c); //嵌套
}
template <class T>
T Max(T a,T b,T c,T d)
{
return Max(Max(a,b,c),d); //嵌套
}
int main(int argc, char *argv[])
{
cout << Max(8,11) << endl;
cout << Max(4,8,9) << endl;
cout << Max(4.2,2.8,5.9) << endl;
cout << Max<float>(5.4,3.8,2.9) << endl;
cout << Max<float>(5.4,3.8,2.9,8.1) << endl;
return 0;
}
运行结果:
-
函数模板的特化
定义:为了解决函数模板的局限性,在定义函数模板时候,直接确定好T的类型。也就是特定的类型模板
格式: template<class T> 返回值 函数名(类名& 对象){ }
匹配:如果传入的是自定义类型,并且和特化版本匹配,会优先使用特化版本
注意:如果特化后,类型确定才可以使用自定义类型中的成员变量和方法。
class Person
{
public:
Person(string name, int age)
{
this->m_Age = age;
this->m_Name = name;
}
int m_Age;
string m_Name;
};
//对比两个数据是否相等
template<typename T>
bool myCompare(T &a, T &b)
{
if (a == b)
{
return true;
}
else
{
return false;
}
}
//利用具体化Person的版本实现代码,具体化优点调用
template<> bool myCompare(Person &p1, Person &p2)
{
if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name)
{
return true;
}
else
{
return false;
}
}
void Mytest()
{
Person a("danny", 20);
Person b("danny", 20);
bool ret = myCompare(a, b);
if (ret)
{
cout << "a == b" << endl;
}
else
{
cout << "a != b" << endl;
}
}
int main()
{
Mytest();
return 0;
}
如果不写具体化的版本,代码就会报错。原因是出现自定义类型的比较时,编译器无法识别。可以在Person
类中重载==
运算符的形式来解决问题,也可以利用具体化的模板解决自定义类型的通用化。
注:本文主要为在长沙华清远见 上C++课程时整理的笔记及测试程序,部分内容参考B站黑马程序员C++课程。