Cpp学习14 第 11 章 函数重载与函数模板

铁名_IronName Lv5

11.1 — 函数重载简介

function overloading
函数重载允许我们创建多个同名函数,只要每个同名函数具有不同的参数类型(或者可以通过其他方式区分这些函数)。在同一作用域内共享名称的每个函数都称为 重载函数 overloaded function(有时简称重载  overload )。

当调用一个已被重载的函数时,编译器会尝试根据函数调用中使用的参数,将该函数调用与相应的重载版本进行匹配。这称为重载解析 。

11.2 — 函数重载区分

只要每个重载函数具有不同数量的参数,它们就可互相区分。
只要每个重载函数的参数类型列表各不相同,该函数就可以被区分。

区分重载函数时,不考虑函数的返回类型。

函数的类型签名 (通常称为签名 )是指函数头中用于区分函数的各个部分。在 C++ 中,这包括函数名、参数数量、参数类型和函数级限定符。值得注意的是,它 包括返回类型。

11.3 — 函数重载解析和歧义匹配

找到了多个匹配的函数。编译器将发出歧义匹配编译错误。

略。一般不会这样写程序。

11.4 — 删除函数

如果我们明确不希望某个函数被调用,可以使用 = delete 说明符将其标记为已删除。如果编译器将对已删除函数的调用匹配到一起,编译将中止并报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

void printInt(int x)
{
std::cout << x << '\n';
}

void printInt(char) = delete; // calls to this function will halt compilation
void printInt(bool) = delete; // calls to this function will halt compilation

int main()
{
printInt(97); // okay

printInt('a'); // compile error: function deleted
printInt(true); // compile error: function deleted

printInt(5.0); // compile error: ambiguous match

return 0;
}

11.5 — 默认参数

1
2
3
4
5
void print(int x, int y=10) // 10 is the default argument
{
std::cout << "x: " << x << '\n';
std::cout << "y: " << y << '\n';
}

请注意,必须使用等号来指定默认参数。使用圆括号或花括号进行初始化是行不通的

在函数调用中,任何显式提供的参数都必须是最左边的参数(不能跳过带有默认值的参数)。
如果某个参数被赋予了默认参数,那么所有后续参数(在其右侧)也必须被赋予默认参数。

最佳实践

如果函数有前向声明(尤其是在头文件中),则将默认参数放在前向声明中。否则,将默认参数放在函数定义中。

带有默认参数的函数可以被重载。

默认参数可能导致匹配结果不明确。

默认参数对通过函数指针调用的函数不起作用

11.6 — 函数模板

在 C++ 中,模板系统旨在简化创建能够处理不同数据类型的函数(或类)的过程。

函数模板是一种类似函数的定义,用于生成一个或多个重载函数,每个重载函数都具有不同的实际类型集。这使得我们能够创建可以处理多种不同类型的函数。用于生成其他函数的初始函数模板称为主模板 ,而从主模板生成的函数称为实例化函数 。

创建主函数模板时,我们会使用占位符类型 (技术上称为类型模板参数 ,非正式地称为模板类型 )来表示函数体中使用的任何参数类型、返回类型或我们希望稍后由模板用户指定的类型。

我们要告诉编译器这是一个模板,并且 T 是一个类型模板参数,它可以作为任何类型的占位符。这两点都通过模板参数声明来实现,该声明定义了后续将要使用的所有模板参数。模板参数声明的作用域严格限于其后的函数模板(或类模板)。因此,每个函数模板或类模板都需要自己的模板参数声明。

1
2
3
4
5
template <typename T> // this is the template parameter declaration defining T as a type template parameter
T max(T x, T y) // this is the function template definition for max<T>
{
return (x < y) ? y : x;
}

如果类型模板参数的用法不明显或有必须满足的特定要求,则此类名称有两种常见的约定:

  • 以大写字母开头(例如 Allocator )。标准库采用这种命名约定。
  • 类型名称以字母 T 开头,然后以大写字母开头(例如 TAllocator )。这样更容易看出该类型是类型模板参数。

11.7 — 函数模板实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

template <typename T>
T max(T x, T y)
{
return (x < y) ? y : x;
}

int main()
{
std::cout << max<int>(1, 2) << '\n'; // instantiates and calls function max<int>(int, int)

return 0;
}

从函数模板(具有模板类型)创建函数(具有特定类型)的过程称为函数模板实例化  function template instantiation(简称实例化 )。当函数因函数调用而被实例化时,称为隐式实例化 。从模板实例化的函数在技术上称为特化 ,但在日常用语中通常称为函数实例 。用于生成特化的模板称为主模板 。函数实例在各方面都与普通函数相同。

模板参数推导

如果参数类型与我们想要的实际类型匹配,则无需指定实际类型——相反,我们可以使用模板参数推导 ,让编译器从函数调用中的参数类型推导出应该使用的实际类型。

1
2
std::cout << max<>(1, 2) << '\n';
std::cout << max(1, 2) << '\n';
最佳实践

当调用从函数模板实例化的函数时,应优先使用普通的函数调用语法(除非您需要优先使用函数模板版本而不是匹配的非模板函数)。

带有非模板参数的函数模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// T is a type template parameter
// double is a non-template parameter
// We don't need to provide names for these parameters since they aren't used
template <typename T>
int someFcn(T, double)
{
return 5;
}

int main()
{
someFcn(1, 3.4); // matches someFcn(int, double)
someFcn(1, 3.4f); // matches someFcn(int, double) -- the float is promoted to a double
someFcn(1.2, 3.4); // matches someFcn(double, double)
someFcn(1.2f, 3.4); // matches someFcn(float, double)
someFcn(1.2f, 3.4f); // matches someFcn(float, double) -- the float is promoted to a double

return 0;
}

实例化的函数可能无法编译通过。
只要语法正确,编译器就会实例化并编译语义上不合理的函数模板。您有责任确保调用此类函数模板时使用的参数合理。

我们可以告诉编译器,禁止使用某些特定参数实例化函数模板。这可以通过函数模板特化来实现,它允许我们为特定的模板参数集重载函数模板,并配合 = delete 符,告诉编译器任何对该函数的使用都应该引发编译错误。

1
2
3
4
5
6
7
8
9
10
template <typename T>
T addOne(T x)
{
return x + 1;
}

// Use function template specialization to tell the compiler that addOne(const char*) should emit a compilation error
// const char* will match a string literal
template <>
const char* addOne(const char* x) = delete;

默认参数

与普通函数一样,函数模板也可以为非模板参数设置默认值。从该模板实例化的每个函数都将使用相同的默认参数。

当在函数模板中使用静态局部变量时,从该模板实例化的每个函数都会拥有该静态局部变量的一个独立版本。

由于模板类型可以替换为任何实际类型,因此模板类型有时也被称为泛型类型 。又因为模板可以与特定类型无关地编写,所以使用模板进行编程有时也被称为泛型编程 。

最佳实践

使用函数模板可以编写通用代码,以便在需要时处理各种类型的数据。

11.8 — 具有多种模板类型的函数模板

解决此问题的最佳方法是重写函数模板,使参数可以解析为不同的类型。我们将不再使用单个模板类型参数 T ,而是使用两个( T 和 U ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

template <typename T, typename U> // We're using two template type parameters named T and U
T max(T x, U y) // x can resolve to type T, and y can resolve to type U
{
return (x < y) ? y : x; // uh oh, we have a narrowing conversion problem here
}

int main()
{
std::cout << max(2, 3.5) << '\n'; // resolves to max<int, double>

return 0;
}

在这种情况下,返回类型推断(通过 auto )会很有用——我们会让编译器从 return 语句中推断出返回类型应该是什么。

需要注意的是,具有 auto 返回类型的函数必须先完整定义才能使用(仅进行前向声明是不够的),因为编译器需要检查函数实现才能确定返回类型。

C++20 简写函数模板

C++20 引入了 auto 关键字的一种新用法:当 auto 关键字用作普通函数中的参数类型时,编译器会自动将该函数转换为函数模板,其中每个 auto 参数都成为一个独立的模板类型参数。这种创建函数模板的方法称为简写函数模板 。

例如:

1
2
3
4
auto max(auto x, auto y)
{
return (x < y) ? y : x;
}

是以下代码的简写:

1
2
3
4
5
template <typename T, typename U>
auto max(T x, U y)
{
return (x < y) ? y : x;
}

函数模板可以重载

就像函数可以重载一样,函数模板也可以重载。这种重载可以具有不同数量的模板类型和/或不同数量或类型的函数参数。
确定多个匹配函数模板中哪个更优的规则称为“函数模板偏序”。简而言之,限制性更强/更专门的函数模板将被优先选择。
如果多个函数模板可以匹配一个调用,而编译器无法确定哪个模板的限制性更强,则编译器会报错,提示匹配不明确。

11.9 — 非类型模板参数

非类型模板参数是具有固定类型的模板参数,它用作 作为模板参数传入的 constexpr 值 的占位符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

template <int N> // declare a non-type template parameter of type int named N
void print()
{
std::cout << N << '\n'; // use value of N here
}

int main()
{
print<5>(); // 5 is our non-type template argument

return 0;
}
最佳实践

使用 N 作为 int 非类型模板参数的名称。

非类型模板参数有什么用?

从 C++20 开始,函数参数不能是 constexpr 类型的。这对于普通函数、constexpr 函数(这很合理,因为它们必须能够在运行时执行)以及可能令人惊讶的是,甚至对于 consteval 函数也是如此。

非类型模板参数主要用于我们需要将 constexpr 值传递给函数(或类类型)的情况,以便在需要常量表达式的上下文中使用它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cmath> // for std::sqrt
#include <iostream>

template <double D> // requires C++20 for floating point non-type parameters
double getSqrt()
{
static_assert(D >= 0.0, "getSqrt(): D must be non-negative");

if constexpr (D >= 0) // ignore the constexpr here for this example
return std::sqrt(D); // strangely, std::sqrt isn't a constexpr function (until C++26)

return 0.0;
}

int main()
{
std::cout << getSqrt<5.0>() << '\n';
std::cout << getSqrt<-5.0>() << '\n';

return 0;
}

从 C++17 开始,非类型模板参数可以使用 auto ,让编译器从模板参数中推断出非类型模板参数。

11.10 — 在多个文件中使用函数模板

max.h:

1
2
3
4
5
6
7
8
9
10
#ifndef MAX_H
#define MAX_H

template <typename T>
T max(T x, T y)
{
return (x < y) ? y : x;
}

#endif

foo.cpp:

1
2
3
4
5
6
7
#include "max.h" // import template definition for max<T>(T, T)
#include <iostream>

void foo()
{
std::cout << max(3, 2) << '\n';
}

main.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
#include "max.h" // import template definition for max<T>(T, T)
#include <iostream>

void foo(); // forward declaration for function foo

int main()
{
std::cout << max(3, 5) << '\n';
foo();

return 0;
}
最佳实践

多个文件需要用到的模板应该定义在头文件中,然后在需要的地方使用 #include 指令包含它们。这样编译器就能看到完整的模板定义,并在需要时实例化模板。

11.x — 第 11 章总结和测验

passed.

  • 标题: Cpp学习14 第 11 章 函数重载与函数模板
  • 作者: 铁名_IronName
  • 创建于 : 2026-08-10 10:49:04
  • 更新于 : 2026-08-10 12:17:40
  • 链接: https://blog.ironname.top/2026/Cpp/Cpp学习14/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论