从 C 向 C++ 进阶系列导航
1. 函数模版
C++ 中提出了函数模板的概念。函数模板可以看作是具备类型检查的宏,并可适配任意的数据类型。这也是泛型编程的思想,即不考虑具体数据类型的编程方式,具体的数据类型由调用者决定。
函数模板定义方式如下:
template <typename T>
Type Funname(T x)
{
...
}
其中,template 告诉编译器以下为模板的定义, 告诉编译器 T 为一个泛指类型。
- 实验:
template <typename T>
T add(T num_A, T num_B)
{
return num_A + num_B;
}
int main()
{
int iSum = add<int>(1, 2);
cout << "iSum = " << iSum << endl; // iSum = 3
float fSum = add<float>(0.3, 0.5);
cout << "fSum = " << fSum << endl; // fSum = 0.8
string strSum = add<string>("123", "456");
cout << "strSum = " << "\"" << strSum << "\"" << endl; // strSum = "123456"
return 0;
}
函数模版是支持类型自动推导的,如上 float fSum = add(0.3, 0.5);
中虽没有显式指明函数模版类型为 float,但编译器能够根据传参自动推导出函数模版类型为 float。
需要注意的是,当函数模板使用类型自动推导时,类型必须进行严格匹配,如果两传参类型不一致但函数模版的传参类型又一致时,会发生编译错误;显示类型指定时,能够进行隐式类型转换。
2. 函数模版本质
函数模板的本质为数据类型的替换。对于函数模板,编译器会进行二次编译。第一次为函数模板本身的编译,第二次为函数调用时进行数据类型替换后的函数编译。因此,每次的函数调用都会产生一个新的函数副本,这无疑会降低程序的编译效率,但使用模板技术可以大大地提高编程效率,所以牺牲较低的编译效率换取较高的编程效率是值得的。、
- 实验:
typedef int(iFun)(int,int);
typedef float(fFun)(float,float);
template <typename T>
T sub(T num_A, T num_B)
{
return num_A - num_B;
}
int main()
{
int iDif = sub<int>(1, 2);
cout << "iDif = " << iDif << endl; // iDif = -1
iFun* pi = sub<int>;
cout << "pi = " << reinterpret_cast<void*>(pi) << endl; // pi = 0x400a80
float fDif = sub<float>(0.3, 0.5);
cout << "fDif = " << fDif << endl; // fDif = -0.2
fFun* pf = sub<float>;
cout << "pf = " << reinterpret_cast<void*>(pf) << endl; // pf = 0x400a92
// string strDif = sub<string>("123", "456"); // error: no match for ‘operator-’ (operand types are ‘std::__cxx11::basic_string<char>’ and ‘std::__cxx11::basic_string<char>’)
return 0;
}
以上实验中,唯独使用 string 类型的模板时编译出错,这是因为在模板函数中使用了减法而 string 类型并没有减法相关的功能实现,所以在二次编译时会发生编译错误。而其余数据类型的函数模板并没有发生编译错误,且所调用的函数地址各不相同,这也证明了模板会进行二次编译。
3. 多参数函数模版
函数模板支持多个泛型参数,为从左向右部分指定类型参数。如果没有指定数据类型时,则以实参的数据类型为准。由于无法自动推导函数的返回类型,在工程中是将返回类型作为第一个类型参数。
- 实验:
template <typename T1, typename T2, typename T3>
T1 add(T2 num_A, T3 num_B)
{
return static_cast<T1>(num_A + num_B);
}
int main()
{
int iSum = add<int, float, float>(1.1, 2.2);
cout << "iSum = " << iSum << endl; // iSum = 3
float fSum = add<float, int>(1, 2.2); // 等价于 add<float, int, float>(1, 2.2)
cout << "fSum = " << fSum << endl; // fSum = 3.2
iSum = add<int>(1.1, 2.2); // 等价于 add<int, float, float>(1.1, 2.2)
cout << "iSum = " << iSum << endl; // iSum = 3
return 0;
}
4. 函数重载与函数模版
由于函数模板可以进行自动的类型推导,因此函数调用时有可能出现同时满足函数重载与函数模板的情况,此时编译器会优先调用重载后的函数。如果想指定调用函数模板,可以在函数调用处使用 “<>” 表示限定调用函数模板。
- 实验:
template <typename T>
void Print(T num)
{
cout << "void Print(T num): " << num << endl;
}
void Print(int num)
{
cout << "void Print(int num): " << num << endl;
}
int main()
{
Print(100); // void Print(int num): 100
Print<>(100); // void Print(T num): 100
Print(3.14); // void Print(T num): 3.14
return 0;
}