Function declaration
函数声明引入函数名称及其类型。函数定义将函数名称/类型与函数体相关联。
目录 |
函数声明
函数声明可以出现在任何作用域中。在类作用域中的函数声明会引入类成员函数(除非使用了 friend 说明符),详情请参阅 成员函数 和 友元函数 。
noptr-declarator
(
parameter-list
)
cv
(可选)
ref
(可选)
except
(可选)
attr
(可选)
|
(1) | ||||||||
noptr-declarator
(
parameter-list
)
cv
(可选)
ref
(可选)
except
(可选)
attr
(可选)
->
trailing
|
(2) | (自 C++11 起) | |||||||
(其他形式的 声明符 语法请参见 声明 )
| noptr-declarator | - |
任意有效的
declarator
,但如果以
*
、
&
或
&&
开头,则必须用括号括起来。
|
||||||
| parameter-list | - | 可能为空的函数参数列表(以逗号分隔)(详见下文) | ||||||
| attr | - | (C++11 起) 属性 列表。这些属性应用于函数类型,而非函数本身。函数的属性出现在声明符内标识符之后,并与声明开头出现的属性(如果有)合并。 | ||||||
| cv | - | const/volatile 限定符,仅允许在非静态成员函数声明中使用 | ||||||
| ref | - | (C++11 起) 引用限定符,仅允许在非静态成员函数声明中使用 | ||||||
| except | - |
|
||||||
| trailing | - | 尾随返回类型,当返回类型依赖于参数名(例如 template < class T, class U > auto add ( T t, U u ) - > decltype ( t + u ) ; )或类型复杂时(例如 auto fpif ( int ) - > int ( * ) ( int ) )特别有用 |
|
如 声明 中所述,声明符后可跟随 requires 子句 ,用于声明函数的关联 约束 ,这些约束必须被满足才能通过 重载决议 选中该函数。(示例: void f1 ( int a ) requires true ; )注意关联约束是函数签名的组成部分,但不是函数类型的组成部分。 |
(since C++20) |
函数声明符可以与其他声明符混合使用,只要 声明说明符序列 允许:
// 声明一个int、一个int指针、一个函数和一个函数指针 int a = 1, *p = NULL, f(), (*pf)(double); // 声明说明符序列是int // 声明符f()声明(但不定义) // 一个不接受参数且返回int的函数 struct S { virtual int f(char) const, g(int) &&; // 声明两个非静态成员函数 virtual int f(char), x; // 编译时错误:virtual(在声明说明符序列中) // 仅允许在非静态成员函数的声明中使用 // };
|
将具有 volatile 限定符的对象类型用作参数类型或返回类型已被弃用。 |
(since C++20) |
函数的返回类型不能是函数类型或数组类型(但可以是指向这些类型的指针或引用)。
|
与任何声明一样,出现在声明之前和声明符内标识符之后的属性都适用于被声明或定义的实体(在此情况下为函数): [[noreturn]] void f [[noreturn]] (); // OK: 两个属性都应用于函数 f 然而,出现在声明符之后(上述语法中)的属性适用于函数的类型,而非函数本身: void f() [[noreturn]]; // Error: 此属性对函数本身无影响 |
(since C++11) |
返回类型推导若函数声明的 声明说明符序列 包含关键字 auto ,则可省略尾随返回类型,编译器将从 非舍弃 的 return 语句中所用操作数的类型推导出返回类型。若返回类型未使用 decltype ( auto ) ,推导遵循 模板实参推导 规则: int x = 1; auto f() { return x; } // 返回类型为 int const auto& f() { return x; } // 返回类型为 const int&
若返回类型为
decltype
(
auto
)
,则返回类型如同将 return 语句中使用的操作数包装在
int x = 1; decltype(auto) f() { return x; } // 返回类型为 int,同 decltype(x) decltype(auto) f() { return(x); } // 返回类型为 int&,同 decltype((x)) (注意:“ const decltype ( auto ) & ” 是错误的, decltype ( auto ) 必须单独使用) 若存在多个 return 语句,它们必须全部推导为相同类型: auto f(bool val) { if (val) return 123; // 推导返回类型 int else return 3.14f; // Error: 推导返回类型 float } 若无 return 语句,或 return 语句的操作数是 void 表达式(包括无操作数的 return 语句),则声明的返回类型必须是 decltype ( auto ) (此时推导的返回类型为 void ),或(可能带 cv 限定的) auto (此时推导的返回类型为(相同 cv 限定的) void ): auto f() {} // 返回 void auto g() { return f(); } // 返回 void auto* x() {} // Error: 无法从 void 推导 auto* 一旦在函数中遇到 return 语句,从该语句推导出的返回类型可用于函数的其余部分,包括其他 return 语句: auto sum(int i) { if (i == 1) return i; // sum 的返回类型为 int else return sum(i - 1) + i; // OK: sum 的返回类型已已知 } 若 return 语句使用 花括号初始化列表 ,则不允许推导: auto func() { return {1, 2, 3}; } // Error 虚函数 和 协程 (since C++20) 不能使用返回类型推导: struct F { virtual auto f() { return 2; } // Error }; 函数模板 (除 用户定义转换函数 外)可使用返回类型推导。推导在实例化时进行,即使 return 语句中的表达式不 依赖 于模板参数。此实例化不在 SFINAE 的立即上下文中。 template<class T> auto f(T t) { return t; } typedef decltype(f(1)) fint_t; // 实例化 f<int> 以推导返回类型 template<class T> auto f(T* t) { return *t; } void g() { int (*p)(int*) = &f; } // 实例化两个 f 以确定返回类型, // 选择第二个模板重载 使用返回类型推导的函数或函数模板的重声明或特化必须使用相同的返回类型占位符: auto f(int num) { return num; } // int f(int num); // Error: 无占位符返回类型 // decltype(auto) f(int num); // Error: 不同的占位符 template<typename T> auto g(T t<span class="br0 |
参数列表
参数列表决定了函数被调用时可以指定的实参。它是一个由 参数声明 组成的逗号分隔列表,每个参数声明具有以下语法:
| attr (可选) decl-specifier-seq declarator | (1) | ||||||||
|
attr
(可选)
|
(2) | (C++23 起) | |||||||
attr
(可选)
decl-specifier-seq
declarator
=
initializer
|
(3) | ||||||||
| attr (可选) decl-specifier-seq abstract-declarator (可选) | (4) | ||||||||
|
attr
(可选)
|
(5) | (C++23 起) | |||||||
attr
(可选)
decl-specifier-seq
abstract-declarator
(可选)
=
initializer
|
(6) | ||||||||
void
|
(7) | ||||||||
| 错误用法 | 示例 |
|---|---|
| 存在多个参数 | int f1 ( void , int ) ; |
| void 参数被命名 | inf f2 ( void param ) ; |
| void 带有cv限定符 | int f3 ( const void ) ; |
| void 是 依赖类型 |
int
f4
(
T
)
;
(其中
T
为
void
)
|
| void 参数作为 显式对象参数 (C++23 起) | int f5 ( this void ) ; |
|
尽管 decl-specifier-seq 暗示可能存在类型说明符之外的 说明符 ,但唯一允许的其他说明符是 register 以及 auto (直至 C++11) ,且它没有实际效果。 |
(直至 C++17) |
|
如果任何函数参数使用了 占位符 ( auto 或 概念类型 ),则该函数声明实际上是 简写函数模板 声明: void f1(auto); // same as template<class T> void f1(T) void f2(C1 auto); // same as template<C1 T> void f2(T), if C1 is a concept |
(自 C++20 起) |
|
使用说明符 this 的参数声明(语法 ( 2 ) / ( 5 ) )声明了一个 显式对象参数 。 显式对象参数不能是 函数参数包 ,且仅能出现在以下声明的参数列表的首个参数位置: 具有显式对象参数的成员函数存在以下限制: struct C { void f(this C& self); // OK template<typename Self> void g(this Self&& self); // also OK for templates void p(this C) const; // Error: “const” not allowed here static void q(this C); // Error: “static” not allowed here void r(int, this C); // Error: an explicit object parameter // can only be the first parameter }; // void func(this C& self); // Error: non-member functions cannot have // an explicit object parameter |
(自 C++23 起) |
函数声明中声明的参数名通常仅用于自文档化目的。它们在函数定义中会被使用(但仍然是可选的)。
当类型名称嵌套在圆括号中时 (包括 lambda表达式 ) (C++11 起) ,参数列表会出现歧义。这种情况下,需要在两种声明之间作出选择:声明一个函数指针类型的参数,或是声明一个带有冗余括号的 声明符 标识符的参数。解决方法是将该类型名称视为 简单类型说明符 (即函数指针类型):
class C {}; void f(int(C)) {} // void f(int(*fp)(C param)) {} // 而非 void f(int C) {} void g(int *(C[10])); // void g(int *(*fp)(C param[10])); // 而非 void g(int *C[10]);
参数类型不能包含对未知边界数组的引用或指针类型,包括多级指针/此类类型的数组,或指向参数为此类类型的函数指针。
使用省略号
参数列表中的最后一个参数可以是省略号( ... );这声明了一个 可变参数函数 。省略号前的逗号可以省略 (C++26 中已弃用) :
int printf(const char* fmt, ...); // 可变参数函数 int printf(const char* fmt...); // 同上,但自 C++26 起已弃用 template<typename... Args> void f(Args..., ...); // 带有参数包的可变参数函数模板 template<typename... Args> void f(Args... ...); // 同上,但自 C++26 起已弃用 template<typename... Args> void f(Args......); // 同上,但自 C++26 起已弃用
函数类型
参数类型列表
函数的 参数类型列表 按以下方式确定:
- 每个参数的类型 (包括函数 参数包 ) (C++11 起) 由其自身的 参数声明 确定。
-
确定各参数类型后,任何“
T的数组”类型或函数类型T的参数会被调整为“指向T的指针”。 - 生成参数类型列表后,构成函数类型时会删除修饰参数类型的任何顶层 cv 限定符 。
- 最终转换后的参数类型列表以及 省略号 或函数 参数包 (C++11 起) 的存在与否即构成函数的参数类型列表。
void f(char*); // #1 void f(char[]) {} // 定义 #1 void f(const char*) {} // 正确:另一个重载 void f(char* const) {} // 错误:重定义 #1 void g(char(*)[2]); // #2 void g(char[3][2]) {} // 定义 #2 void g(char[3][3]) {} // 正确:另一个重载 void h(int x(const int)); // #3 void h(int (*)(int)) {} // 定义 #3
确定函数类型
在语法
(1)
中,假设
noptr-declarator
作为独立声明,给定
noptr-declarator
中的
qualified-id
或
unqualified-id
类型为“派生声明符类型列表
T
”:
|
(自 C++17 起) |
-
该
(C++17 前)
否则,该
(C++17 起)
函数声明的类型为
“派生声明符类型列表 函数,接受
参数类型列表 cv (可选) ref (可选) (C++11 起) ,返回T”。
|
在语法
(2)
中,假设
noptr-declarator
作为独立声明,给定
noptr-declarator
中
qualified-id
或
unqualified-id
的类型为“派生声明符类型列表
|
(since C++11) |
|
(since C++17) |
若存在 attr ,则其应用于函数类型。 |
(since C++11) |
// “f1” 的类型是 // “带有 [[noreturn]] 属性的返回 void 的 int 类型函数” void f1(int a) [[noreturn]]; // “f2” 的类型是 // “constexpr noexcept 修饰的返回 int 的 int 指针类型函数” constexpr auto f2(int[] b) noexcept -> int; struct X { // “f3” 的类型是 // “const 修饰的无参函数返回 const int” const int f3() const; };
后置限定符
带有
cv
或
ref
(C++11 起)
的函数类型(包括由
typedef
命名的类型)仅能作为以下形式出现:
- 用于 非静态成员函数 的函数类型,
- 成员指针所指向的函数类型,
- 函数 typedef 声明 或 类型别名声明 (C++11 起) 的顶层函数类型,
- 模板类型参数 的默认参数中的 类型标识 ,或
- 模板类型参数的模板实参的类型标识。
typedef int FIC(int) const; FIC f; // 错误:未声明成员函数 struct S { FIC f; // 正确 }; FIC S::*pm = &S::f; // 正确
函数签名
每个函数都有一个签名。
函数的签名由其名称和 参数类型列表 组成。其签名还包含所在的 命名空间 ,但存在以下例外情况:
- 若该函数为 成员函数 ,其签名将包含该函数所属的类(而非外围命名空间)。若存在以下组件,其签名亦将包含:
-
- cv
|
(C++11 起) |
|
(C++20 起) |
except 和 attr (C++11 起) 不涉及函数签名 ,尽管 noexcept 规范 会影响函数类型 (C++17 起) 。
函数定义
非成员函数定义只能出现在命名空间作用域(不存在嵌套函数)。 成员函数 定义也可以出现在 类定义 的主体中。它们具有以下语法:
|
attr
(可选)
decl-specifier-seq
(可选)
declarator
virt-specs (可选) contract-specs (可选) function-body |
(1) | ||||||||
|
attr
(可选)
decl-specifier-seq
(可选)
declarator
requires-clause contract-specs (可选) function-body |
(2) | (自 C++20 起) | |||||||
| attr | - | (since C++11) 一个 属性 列表。这些属性会与 declarator 中标识符后的属性(如有)合并(参见本页顶部)。 |
| decl-specifier-seq | - | 带说明符的返回类型,遵循 声明语法 |
| declarator | - | 函数声明符,与前述函数声明语法相同(可带括号) |
| virt-specs | - |
(since C++11)
override
、
final
或其任意顺序的组合
|
| requires-clause | - | 一个 requires 子句 |
| contract-specs | - | (since C++26) 函数契约说明符 列表 |
| function-body | - | 函数体(见下文) |
function-body
是以下之一:
| ctor-initializer (可选) compound-statement | (1) | ||||||||
| function-try-block | (2) | ||||||||
=
default
;
|
(3) | (C++11 起) | |||||||
=
delete
;
|
(4) | (C++11 起) | |||||||
=
delete
(
string-literal
);
|
(5) | (C++26 起) | |||||||
| ctor-initializer | - | 成员初始化列表 ,仅允许在构造函数中使用 |
| compound-statement | - | 构成函数体的花括号包围的 语句序列 |
| function-try-block | - | 函数 try 块 |
| string-literal | - | 可用于说明函数被删除原因的 未求值字符串字面量 |
int max(int a, int b, int c) { int m = (a > b) ? a : b; return (m > c) ? m : c; } // 声明说明符序列是“int” // 声明符是“max(int a, int b, int c)” // 函数体是 { ... }
函数体是一个 复合语句 (由一对花括号包围的零个或多个语句序列),在函数调用时执行。此外, 构造函数 的函数体还包含以下内容:
- 对于构造函数 成员初始化列表 中未列出的所有非静态数据成员,将使用 默认成员初始化器 或 (since C++11) 默认初始化 来初始化对应的成员 子对象 。
- 对于构造函数成员初始化列表中未列出的所有基类,将使用默认初始化来初始化对应的基类子对象。
|
若函数定义包含 virt-specs ,则必须定义 成员函数 。 |
(C++11 起) |
|
若函数定义包含 requires-clause ,则必须定义 模板化函数 。 |
(C++20 起) |
void f() override {} // 错误:非成员函数 void g() requires (sizeof(int) == 4) {} // 错误:非模板函数
函数定义的参数类型以及返回类型不能是(可能带有 cv 限定符的) 不完整 的 类类型 ,除非该函数被定义为删除函数 (C++11 起) 。完整性检查仅在函数体内进行,这使得 成员函数 可以返回其所属的类(或其外围类),即使在定义点时该类尚未完整(在函数体内该类已是完整的)。
函数定义中 声明符 内声明的参数在其函数体 作用域 内有效。若参数在函数体中未被使用,则无需为其命名(使用抽象声明符即可):
void print(int a, int) // 第二个参数未被使用 { std::printf("a = %d\n", a); }
尽管在函数声明中会丢弃参数的顶层 cv限定符 ,但它们会修改在函数体内可见的参数类型:
void f(const int n) // 声明类型为 void(int) 的函数 { // 但在函数体内,“n”的类型为 const int }
默认函数如果函数定义采用语法 ( 3 ) ,则该函数被定义为 显式默认 。 显式默认的函数必须是 特殊成员函数 或 比较运算符函数 (C++20 起) ,且不能有 默认参数 。
显式默认的特殊成员函数
如果
在其首次声明时显式默认的函数隐式是 内联 的,并且如果它可以成为 constexpr 函数 ,则隐式是 constexpr。 struct S { S(int a = 0) = default; // 错误:默认参数 void operator=(const S&) = default; // 错误:返回类型不匹配 ~S() noexcept(false) = default; // 正确:异常规范不同 private: int i; S(S&); // 正确:私有复制构造函数 }; S::S(S&) = default; // 正确:定义复制构造函数 显式默认函数和隐式声明函数统称为 默认 函数。它们的实际定义将由编译器隐式提供,详见各自页面。 删除函数如果函数定义采用语法 ( 4 ) 或 ( 5 ) (C++26 起) ,则该函数被定义为 显式删除 。 任何对删除函数的使用都是非良构的(程序将无法编译)。这包括显式调用(使用函数调用运算符)和隐式调用(调用已删除的重载运算符、特殊成员函数、分配函数等),构造指向删除函数的指针或成员指针,甚至在不 潜在求值 的表达式中使用删除函数。 非纯虚成员函数可以被定义为删除,即使它被隐式 ODR 使用 。删除函数只能被删除函数覆盖,非删除函数只能被非删除函数覆盖。
如果函数被重载,首先进行 重载决议 ,仅当选中删除函数时程序才非良构: struct T { void* operator new(std::size_t) = delete; void* operator new[](std::size_t) = delete("new[] is deleted"); // C++26 起 }; T* p = new T; // 错误:尝试调用已删除的 T::operator new T* p = new T[5]; // 错误:尝试调用已删除的 T::operator new[], // 发出诊断消息“new[] is deleted” 函数的删除定义必须是翻译单元中的首次声明:先前声明的函数不能重新声明为删除: struct T { T(); }; T::T() = delete; // 错误:必须在首次声明时删除 用户提供函数如果函数是用户声明且在其首次声明时未显式默认或删除,则该函数是 用户提供 的。用户提供的显式默认函数(即在首次声明后显式默认)在显式默认处定义;如果此类函数被隐式定义为删除,则程序非良构。在首次声明后将函数声明为默认可以在提供高效执行和简洁定义的同时,为不断演进的代码库提供稳定的二进制接口。 // “trivial”的所有特殊成员函数 // 分别在首次声明时默认, // 它们不是用户提供的 struct trivial { trivial() = default; trivial(const trivial&) = default; trivial(trivial&&) = default; trivial& operator=(const trivial&) = default; trivial& operator=(trivial&&) = default; ~trivial() = default; }; struct nontrivial { nontrivial(); // 首次声明 }; // 未在首次声明时默认, // 它是用户提供的并在此处定义 nontrivial::nontrivial() = default; 歧义解析
当函数体与以
using T = void(); // 函数类型 using U = int; // 非函数类型 T a{}; // 定义空函数 U b{}; // 值初始化 |
注释
当使用直接初始化语法的变量声明与函数声明存在歧义时,编译器始终选择函数声明;详见 直接初始化 。
| 功能测试宏 | 值 | 标准 | 功能特性 |
|---|---|---|---|
__cpp_decltype_auto
|
201304L
|
(C++14) |
decltype(auto)
|
__cpp_return_type_deduction
|
201304L
|
(C++14) | 普通函数的返回类型推导 |
__cpp_explicit_this_parameter
|
202110L
|
(C++23) | 显式对象参数 ( 推导 this ) |
__cpp_deleted_function
|
202403L
|
(C++26) | 带原因的删除函数 |
关键词
示例
#include <iostream> #include <string> // 带默认参数的简单函数,无返回值 void f0(const std::string& arg = "world!") { std::cout << "Hello, " << arg << '\n'; } // 声明位于命名空间(文件)作用域 //(定义将在后续提供) int f1(); // 返回指向f0的函数指针,C++11前风格 void (*fp03())(const std::string&) { return f0; } // 返回指向f0的函数指针,使用C++11后置返回类型 auto fp11() -> void(*)(const std::string&) { return f0; } int main() { f0(); fp03()("test!"); fp11()("again!"); int f2(std::string) noexcept; // 函数作用域内的声明 std::cout << "f2(\"bad\"): " << f2("bad") << '\n'; std::cout << "f2(\"42\"): " << f2("42") << '\n'; } // 返回int的简单非成员函数 int f1() { return 007; } // 带有异常规范和函数try块的函数 int f2(std::string str) noexcept try { return std::stoi(str); } catch (const std::exception& e) { std::cerr << "stoi() failed!\n"; return 0; } // 已删除函数,尝试调用会导致编译错误 void bar() = delete # if __cpp_deleted_function ("reason") # endif ;
可能的输出:
stoi() failed!
Hello, world!
Hello, test!
Hello, again!
f2("bad"): 0
f2("42"): 42
缺陷报告
以下行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用版本 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 135 | C++98 |
类内定义的成员函数
不能以自身类作为参数或返回类型 因为此时类类型不完整 |
允许 |
| CWG 332 | C++98 | 参数可以具有cv限定的 void 类型 | 禁止 |
| CWG 393 | C++98 |
包含指向未知边界数组的指针/引用的类型
不能作为参数类型 |
允许此类类型 |
| CWG 452 | C++98 | 成员初始化列表不是函数体的一部分 | 属于函数体 |
| CWG 577 | C++98 |
依赖类型
void
可用于
声明无参函数 |
仅允许非依赖
void |
| CWG 1327 | C++11 |
默认或删除函数不能
与 override 或 final 同时指定 |
允许 |
| CWG 1355 | C++11 | 仅特殊成员函数可以是用户提供的 | 扩展至所有函数 |
| CWG 1394 | C++11 |
删除函数不能有任何不完整类型的参数
或返回不完整类型 |
允许不完整类型 |
| CWG 1824 | C++98 |
对函数定义参数类型和返回类型的
完整性检查可在函数定义上下文之外进行 |
仅在函数定义
上下文中 进行检查 |
| CWG 1877 | C++14 | 返回类型推导将 return ; 视为 return void ( ) ; |
此情况下直接推导
返回类型为 void |
| CWG 2015 | C++11 | 对已删除虚函数的隐式odr使用会导致程序非法 |
此类odr使用豁免
于使用禁令 |
| CWG 2044 | C++14 |
返回
void
的函数进行返回类型推导会失败
如果声明的返回类型是 decltype ( auto ) |
更新推导规则
以处理此情况 |
| CWG 2081 | C++14 |
函数重声明可以使用返回类型
推导即使初始声明未使用 |
不允许 |
| CWG 2144 | C++11 | { } 在同一位置既可以是函数体也可以是初始化器 |
通过声明符标识符的
类型进行区分 |
| CWG 2145 | C++98 | 函数定义中的 声明符 不能带括号 | 允许 |
| CWG 2259 | C++11 |
关于带括号类型名的歧义解析规则
未涵盖lambda表达式 |
已涵盖 |
| CWG 2430 | C++98 |
在类定义内的成员函数定义中,
由于 CWG 1824 的解决方案,该类类型 不能作为返回类型或参数类型 |
仅在函数体内
进行检查 |
| CWG 2760 | C++98 |
构造函数的函数体不包含
未在构造函数常规函数体中指定的初始化 |
同时包含这些
初始化 |
| CWG 2831 | C++20 |
带有
requires子句
的函数定义
可以定义非模板函数 |
禁止 |
| CWG 2846 | C++23 | 显式对象成员函数不能有类外定义 | 允许 |
| CWG 2915 | C++23 | 未命名的显式对象参数可以具有 void 类型 | 禁止 |
参见
|
C 文档
关于
函数声明
|