11-x 第十一章 总结与测验
干得漂亮。函数模板看似相当复杂,但它们是让代码处理不同类型对象的强大手段。后续章节将深入探讨模板机制,请做好准备迎接挑战。
章节回顾
函数重载function overloading允许创建同名函数,前提是每个同名函数的形参类型集不同(或通过其他方式区分)。此类函数称为重载函数overloaded function(简称重载overload)。返回类型不作为区分依据。
解析重载函数时,若未找到精确匹配项,编译器将优先选择可通过数值提升匹配的重载函数,而非需要数值转换的函数。当调用重载函数时,编译器会根据函数调用中的实参尝试匹配最合适的重载版本,此过程称为重载解析overload resolution。
当编译器发现两个或多个函数可匹配重载函数调用,却无法确定最佳匹配时,就会发生模糊匹配ambiguous match。
默认实参default argument是为函数形参提供的默认值。具有默认实参的形参必须始终位于最右侧,且在解析重载函数时不会用于区分函数。
函数模板function templates允许我们创建类似函数的定义,作为创建相关函数的模板。在函数模板中,我们使用类型模板形参type template parameters作为占位符,用于稍后指定具体类型。告知编译器正在定义模板并声明模板类型的语法称为模板形参声明template parameter declaration。
从函数模板(含模板类型)生成具体类型函数的过程称为函数模板实例化function template instantiation(简称实例化instantiation)。当该过程因函数调用触发时,称为隐式实例化implicit instantiation。实例化的函数称为函数实例function instance(或简称实例instance,有时也称模板函数template function)。
模板实参推导template argument deduction允许编译器根据函数调用的实参推断出实例化函数应使用的实际类型。模板实参推导不进行类型转换。
模板类型有时被称为泛型类型generic types,使用模板进行编程有时被称为泛型编程generic programming。
在C++20中,当普通函数使用auto关键字作为形参类型时,编译器会自动将该函数转换为函数模板,每个auto形参都成为独立的模板类型形参。这种创建函数模板的方法称为简写函数模板abbreviated function template。
非类型模板形参non-type template parameter是一种具有固定类型的模板形参,用于作为传递给模板实参的constexpr值的占位符。
测验时间
问题 #1
1a) 此程序的输出结果是什么?为什么?
#include <iostream>
void print(int x)
{
std::cout << "int " << x << '\n';
}
void print(double x)
{
std::cout << "double " << x << '\n';
}
int main()
{
short s { 5 };
print(s);
return 0;
}
显示答案
输出为整型5(int 5)。将短整型转换为整型属于数值提升,而将短整型转换为双精度浮点型则属于数值转换。编译器会优先选择数值提升而非数值转换的转换方式。
1b) 为什么以下代码无法编译?
#include <iostream>
void print()
{
std::cout << "void\n";
}
void print(int x=0)
{
std::cout << "int " << x << '\n';
}
void print(double x)
{
std::cout << "double " << x << '\n';
}
int main()
{
print(5.0f);
print();
return 0;
}

显示答案
由于带默认实参的形参在重载函数解析时不予计数,编译器无法判断对 print() 的调用应解析为 print() 还是 print(int x=0)。
1c) 为什么以下代码无法编译?
#include <iostream>
void print(long x)
{
std::cout << "long " << x << '\n';
}
void print(double x)
{
std::cout << "double " << x << '\n';
}
int main()
{
print(5);
return 0;
}
显示答案
字面量 5 是整型(int)。将整型转换为长整型(long)或双精度浮点型(double)属于数值转换,编译器无法确定哪种函数更匹配。
问题 #2
步骤 #1
编写名为 add() 的函数模板,允许用户对两个相同类型的值进行加法运算。以下程序应能运行:
#include <iostream>
// write your add function template here
int main()
{
std::cout << add(2, 3) << '\n';
std::cout << add(1.2, 3.4) << '\n';
return 0;
}
并产生以下输出:
5
4.6

显示答案
#include <iostream>
template <typename T>
T add(T x, T y)
{
return x + y;
}
int main()
{
std::cout << add(2, 3) << '\n';
std::cout << add(1.2, 3.4) << '\n';
return 0;
}
步骤 #2
编写名为 mult() 的函数模板,允许用户对任意类型的值(第一个形参)与整数(第二个形参)进行乘法运算。第二个形参不应是模板类型。函数应返回与第一个形参相同的类型。以下程序应能运行:
#include <iostream>
// write your mult function template here
int main()
{
std::cout << mult(2, 3) << '\n';
std::cout << mult(1.2, 3) << '\n';
return 0;
}
并产生以下输出:
6
3.6

显示答案
#include <iostream>
template <typename T>
T mult(T x, int y)
{
return x * y;
}
int main()
{
std::cout << mult(2, 3) << '\n';
std::cout << mult(1.2, 3) << '\n';
return 0;
}
步骤 #3
编写一个名为 sub() 的函数模板,允许用户减去两个不同类型的值。以下程序应能运行:
#include <iostream>
// write your sub function template here
int main()
{
std::cout << sub(3, 2) << '\n';
std::cout << sub(3.5, 2) << '\n';
std::cout << sub(4, 1.5) << '\n';
return 0;
}
并产生以下输出:
1
1.5
2.5

显示答案
#include <iostream>
template <typename T, typename U>
auto sub(T x, U y)
{
return x - y;
}
/*
//If C++20 capable, you can use an abbreviated function template instead
auto sub(auto x, auto y)
{
return x - y;
}
*/
int main()
{
std::cout << sub(3, 2) << '\n';
std::cout << sub(3.5, 2) << '\n';
std::cout << sub(4, 1.5) << '\n';
return 0;
}
问题 #3
该程序的输出结果是什么?为什么?
#include <iostream>
template <typename T>
int count(T) // This is the same as int count(T x), except we're not giving the parameter a name since we don't use the parameter
{
static int c { 0 };
return ++c;
}
int main()
{
std::cout << count(1) << '\n';
std::cout << count(1) << '\n';
std::cout << count(2.3) << '\n';
std::cout << count<double>(1) << '\n';
return 0;
}

显示方案
1 2 1 2当调用 count(1) 时,编译器将实例化函数 count
(int) 并调用它。这将返回值 1。
再次调用 count(1) 时,编译器会发现 count(int) 已存在,并再次调用它。这将返回值 2。
当调用 count(2.3) 时,编译器将实例化原型为 count(double) 的函数并调用它。这是具有独立静态 c 变量的新函数,因此返回值为 1。
当调用 count(1) 时,编译器会识别到我们明确请求双精度版本的 count()。由于先前声明,该函数已存在,因此将调用 count (double),并将 int 参数隐式转换为 double。此函数将返回值 2。
问题 #4
该程序的输出是什么?
#include <iostream>
int foo(int n)
{
return n + 10;
}
template <typename T>
int foo(T n)
{
return n;
}
int main()
{
std::cout << foo(1) << '\n'; // #1
short s { 2 };
std::cout << foo(s) << '\n'; // #2
std::cout << foo<int>(4) << '\n'; // #3
std::cout << foo<int>(s) << '\n'; // #4
std::cout << foo<>(6) << '\n'; // #5
return 0;
}

显示答案
11 2 4 2 6在情况1中,foo(1)与foo(int)完全匹配,因此调用非模板函数foo(int)。
在情况2中,foo(s)与非模板函数foo(int)不完全匹配,但实参s可转换为int类型,故foo(int)成为候选函数。然而编译器会优先使用函数模板 foo(T) 来匹配精确的 foo (short),因此实际调用的是 foo (short)。
在情况 3 中,foo(4) 是对 foo 的显式调用,因此 foo(int) 不被考虑。编译器匹配出 foo (int) 并调用它。
情况4同样属于对foo的显式调用。编译器将实参s提升为int类型以匹配函数形参。
情况5中,此语法仅匹配函数模板,因此不会考虑foo(int)。最终调用foo(int)。

浙公网安备 33010602011771号