函数声明引入函数名及其类型。函数定义将函数名/类型与函数体关联。

函数声明

函数声明可出现于任何作用域。在类作用域中的函数声明引入成员函数(除非使用 friend 说明符),细节见成员函数友元函数

被声明的函数类型由返回类型(return type)(由声明语法的 声明说明符序列 提供)和函数声明符(声明符)组成。

noptr-声明符 ( 形参列表 ) cv(可选) ref(可选) 异常说明(可选) attr(可选) (1)
noptr-声明符 ( 形参列表 ) cv(可选) ref(可选) 异常说明(可选) attr(可选) -> 尾随返回类型 (2) (C++11 起)

(声明符 语法的其他形式见声明

1) 常规函数声明语法

2) 尾随返回类型声明:尾随返回类型仅在最外层函数声明符中允许使用。此情况下的 声明说明符序列 必须包含关键词 auto

noptr-声明符 - 任何合法的 声明符,但若它以 、& 或 && 开始,则它必须为括号所环绕。
形参列表 - 可以为空,函数形参的逗号分隔列表(细节见下文)
attr(C++11) - 可选的属性列表。这些属性应用于函数类型,而非函数自身。出现于声明符中标识符之后的属性与出现于声明开端的属性合并,若它们存在。
cv - const/volatile 限定,只允许在非静态成员函数中使用
ref(C++11) - 引用限定,只允许在非静态成员函数中使用
异常说明 - 动态异常说明(C++17 前)或
noexcept 说明(C++11)之一。注意,异常说明不是函数类型的一部分 (C++17 前)
尾随返回类型(C++11) - 尾随返回类型,当返回类型取决于实参名时,例如 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;)注意,关联的制约是函数签名的一部分,但不是函数类型的一部分。
(C++20 起)

只要 声明说明符序列 允许,函数声明符就可与其他声明符混合:

  1. // 声明一个 int、一个 int*、一个函数,及一个函数指针
  2. int a = 1, *p = NULL, f(), (*pf)(double);
  3. // 声明说明符序列 是 int
  4. // 声明符 f() 声明(但不定义)
  5. // 一个不接受实参并返回 int 的函数
  6.  
  7. struct S
  8. {
  9. virtual int f(char) const, g(int) &&; // 声明两个非静态成员函数
  10. virtual int f(char), x; // 编译时错误:virtual(在 声明说明符序列 中)
  11. // 仅在非静态成员函数的声明中允许
  12. };

以 volatile 限定的对象类型为形参类型或返回类型是被弃用的。
(C++20 起)

函数的返回类型不能是函数类型或数组类型(但可以是到它们的指针或引用)。


与任何声明相同,出现于声明之前的属性和声明符中直接跟在标识符之后的属性,都应用于所声明或定义的实体(在此例中,应用到函数)




  1. [[noreturn]] void f [[noreturn]] (); // OK:两个属性都应用到函数 f




然而,(语法上)出现于声明符之后的属性,应用到函数类型,而非函数自身




  1. void f() [[noreturn]]; // 错误:此属性对效果无作用



(C++11 起)

与任何声明相同,ret func(params) 所声明的函数 func 的类型为 ret(params)(但适用下文所述的形参类型重编):参见 类型的命名



### 返回类型推导


若函数声明的 声明说明符序列 包含关键词 auto,则尾随返回类型可以省略,而编译器将从 return 语句中所用的表达式的类型推导出它。若返回类型使用的不是 decltype(auto),则推导遵循模板实参推导的规则进行。




  1. int x = 1;
    auto f() { return x; } // 返回类型是 int
    const auto& f() { return x; } // 返回类型是 const int&




若返回类型是 decltype(auto),则返回类型为将 return 语句中所用的表达式包裹于 decltype 中时所得到的类型。




  1. int x = 1;
    decltype(auto) f() { return x; } // 返回类型是 int,同 decltype(x)
    decltype(auto) f() { return(x); } // 返回类型是 int&,同 decltype((x))




(注意:“const decltype(auto)&”是错误的,decltype(auto) 必须独自使用)

若有多条返回语句,则它们必须推导出相同类型




  1. auto f(bool val)
    {
    if(val) return 123; // 推导返回类型 int
    else return 3.14f; // 错误:推导返回类型 float
    }




若无 return 语句或若 return 语句的实参是 void 表达式,则所声明的返回类型,必须要么是 decltype(auto),此情况中推导返回类型是 void,要么是(可有 cv 限定的)auto,此情况中推导的返回类型则是(同一 cv 限定的)void。




  1. auto f() {} // 返回 void
    auto g() { return f(); } // 返回 void
    auto x() {} // 错误: 不能从 void 推导 auto




一旦在函数中见到一条 return 语句,则从该语句推导的返回类型就可用于函数的剩余部分,包括其他 return 语句。




  1. auto sum(int i)
    {
    if(i == 1)
    return i; // sum 的返回类型是 int
    else
    return sum(i - 1) + i; // OK,sum 的返回类型已知
    }




若 return 语句使用花括号初始化器列表(brace-init-list),则不允许推导:




  1. auto func () { return {1, 2, 3}; } // 错误




虚函数不能使用返回类型推导。




  1. struct F
    {
    virtual auto f() { return 2; } // 错误
    };




若函数使用返回类型推导,则它不能用其推导的类型或其他种类的返回类型推导再声明,即使推导出相同类型也是如此




  1. auto f(); // 已声明,未定义
    auto f() { return 42; } // 已定义,返回类型是 int
    int f(); // 错误:不能使用推导的类型
    decltype(auto) f(); // 错误:不同种类的推导
    auto f(); // OK:再声明

    template<typename T>
    struct A { friend T frf(T); };
    auto frf(int i) { return i; } // 不是 A<int> 的友元




除了用户定义转换函数以外的函数模板可以使用返回类型推导。即使 return 语句中的表达式并非待决的,推导也在实例化时发生。这种实例化并不处于 SFINAE 的目的的立即语境中。




  1. 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 以确定返回类型,
    // 选择第二个模板重载




使用返回类型推导的函数模板特化必须使用同一返回类型占位符




  1. template<typename T> auto g(T t) { return t; } // #1
    template auto g(int); // OK,返回类型是 int
    //template char g(char); // 错误,无匹配的模板

    template<> auto g(double); // OK,用未知返回类型的前置声明
    template<typename T> T g(T t) { return t; } // OK,不等价于 #1
    template char g(char); // OK,现在有匹配的模板
    template auto g(float); // 仍然匹配 #1
    // void h() { return g(42); } // 错误,歧义




显式实例化声明自身并不实例化使用返回类型推导的函数模板




  1. template<typename T> auto f(T t) { return t; }
    extern template auto f(int); // 不实例化 f<int>
    int (*p)(int) = f; // 实例化 f<int> 以确定其返回类型,
    // 但仍需要在程序的别处出现显式实例化的定义



(C++14 起)

形参列表

形参列表决定调用函数时所能指定的实参。它是形参声明的逗号分隔列表,其中每一项拥有下列语法

attr(可选) 声明说明符序列 声明符 (1)
attr(可选) 声明说明符序列 声明符 = 初始化器 (2)
attr(可选) 声明说明符序列 抽象声明符(可选) (3)
attr(可选) 声明说明符序列 抽象声明符(可选) = 初始化器 (4)
void (5)

1) 声明具名(形式)参数。声明说明符序列 和 声明符 的含义见声明

  1. int f(int a, int *p, int (*(*x)(double))[3]);

2) 声明带有默认值的具名(形式)参数。

  1. int f(int a = 7, int *p = nullptr, int (*(*x)(double))[3] = nullptr);

3) 声明一个无名形参

  1. int f(int, int *, int (*(*)(double))[3]);

4) 声明具有默认值的无名形参

  1. int f(int = 7, int * = nullptr, int (*(*)(double))[3] = nullptr);

5) 指示函数不接受形参,它是空参数列表的确切同意词:int f(void); 和 int f(); 声明同一函数。注意类型 void(可以有 cv 限定)不能在其他情况下用于参数列表:int f(void, int); 和 int f(const void); 是错误的(但可以使用其衍生类型,如 void*)。在模板中,只能使用非待决的 void(当以 T = void 实例化时,采用单个 T 类型的形参的函数不会成为无形参函数) (C++11 起)

省略号 可出现于形参列表末尾;这声明一个变参函数(variadic function)

  1. int printf(const char* fmt ...);

为了与 C89 兼容,当形参列表含有至少一个形参时,省略号前可出现可选的逗号:

  1. int printf(const char* fmt, ...); // OK,同上

尽管 声明说明符序列 蕴含了可以存在类型说明符之外的说明符,但其他受允许的说明符仅有 register 和 auto (C++11 前),且它无任何效果。
(C++17 前)

若任何函数形参使用了占位符(placeholder)auto概念(concept)类型),则函数声明转变为简写函数模板声明:




  1. void f1(auto); // 同 template<class T> void f(T)
    void f2(C1 auto); // 同 template<C1 T> void f7(T),若 C1 是概念



(C++20 起)

声明于函数声明之中的形参名通常只用作以自身为文档的目的。它们在函数定义中被使用(但仍是可选的)。

形参列表中的每个函数形参的类型,根据下列规则确定:

1) 首先,以如同在任何声明中的方式,组合声明说明符序列和声明符以确定其类型。

2) 若类型是“T 的数组”或“T 的未知边界数组”,则它被替换成类型“T 的指针”

3) 若类型是函数类型 F,则它被替换成类型“F 的指针”

4) 从形参类型中丢弃顶层 cv 限定符(此调整只影响函数类型,但不改动形参的性质:int f(const int p, decltype(p)); 和 int f(int, const int); 声明同一函数)

因为这些规则,下列函数声明确切地声明同一函数:

  1. int f(char s[3]);
  2. int f(char[]);
  3. int f(char* s);
  4. int f(char* const);
  5. int f(char* volatile s);

下列声明也确切地声明同一函数:

  1. int f(int());
  2. int f(int (*g)());

形参类型不能是含有到未知边界数组的引用或指针的类型,含有这种类型的多级指针/数组,或含有指向以这些类型为形参的函数指针
(C++14 前)

指示可变实参的省略号前不能有逗号,即使它跟随指示形参包展开的省略号也是如此,故下列函数模板是确切相同的:




  1. template<typename Args> void f(Args…, …);
    template<typename Args> void f(Args …);
    template<typename Args> void f(Args……);




使用这种声明的例子之一是 std::is_function 的实现。
(C++11 起)

函数定义

非成员函数的定义只能出现在命名空间作用域中(不存在嵌套函数)。成员函数的定义亦可出现在类定义的体内。它们拥有下列语法:

attr(可选) 声明说明符序列(可选) 声明符 虚声明符序列(可选) 函数体

其中 函数体 是下列之一

构造函数初始化器(可选) 复合语句 (1)
函数-try-块 (2)
= delete ; (3) (C++11 起)
= default ; (4) (C++11 起)

1) 常规函数体

2) 函数 try 块(这是包装在 try/catch 块内的常规函数体)

3) 显式弃置的函数定义

4) 显式预置的函数定义,仅对特殊成员函数比较运算符函数 (C++20 起)允许

attr(C++11) - 可选的属性列表。这些属性与出现在 声明符 中标识符之后的属性结合(见本页顶部),若它们存在。
声明说明符序列 - 带有说明符的返回类型,与声明文法相同
声明符 - 函数声明符,与上述函数声明文法相同。和函数声明一样,它可后随 requires-子句 (C++20 起)
虚说明符序列(C++11) - overridefinal,或它们任意顺序的组合(仅对成员函数允许)
构造函数初始化器 - 成员初始化器列表,仅在构造函数中允许
复合语句 - 花括号环绕的语句序列,它们构成函数体
  1. int max(int a, int b, int c)
  2. {
  3. int m = (a > b)? a : b;
  4. return (m > c)? m : c;
  5. }
  6. // 声明说明符序列 是“int”
  7. // 声明符是“max(int a, int b, int c)”
  8. // 函数体是 { ... }

函数体是一条复合语句(为一对花括号所环绕的零或多条语句),它们在函数调用时被执行。

函数的各个形参类型和返回类型不能是不完整的类类型,除了被弃置的函数 (C++11 起)。完整性检查在函数的语境中进行,这允许成员函数返回在其中定义它们的类(或其外围类),尽管在定义点它是不完整的(它在函数体内完整)。

在函数定义的 声明符 中声明的形参,在函数体内处于作用域中。若某个形参未在函数体中使用,则它不需要具名(使用抽象声明符足矣)

  1. void print(int a, int) // 未使用第二个形参
  2. {
  3. std::printf("a = %d\n",a);
  4. }

尽管形参上的顶层 cv 限定符在函数声明中被忽略,它们亦会修饰形参的类型,这在函数体中可见:

  1. void f(const int n) // 声明 void(int) 类型的函数
  2. {
  3. // 但在体内,n 的类型是 const int
  4. }


### 弃置函数


如果取代函数体而使用特殊语法 = delete ;,则该函数被定义为弃置的(deleted)。任何弃置函数的使用都是非良构的(程序无法编译)。这包含调用,包括显式(以函数调用运算符)及隐式(对弃置的重载运算符、特殊成员函数、分配函数等的调用),构成指向弃置函数的指针或成员指针,甚或是在不求值表达式中使用弃置函数。但是,允许隐式 ODR 式使用 刚好被弃置的非纯虚成员函数。

若函数被重载,则首先进行重载决议,且仅当选择了弃置函数时程序才非良构。




  1. struct sometype
    {
    void operator new(std::size_t) = delete;
    void
    operator new = delete;
    };
    sometype p = new sometype; // 错误:尝试调用弃置的 sometype::operator new




函数的弃置定义必须是翻译单元中的首条声明:已经声明过的函数不能声明为弃置的:




  1. struct sometype { sometype(); };
    sometype::sometype() = delete; // 错误:必须在首条声明弃置





### func


在函数体内,如同以如下方式定义了函数局部的预定义变量 func




  1. static const char func[] = "function-name";




此变量具有块作用域及静态存储期:




  1. struct S
    {
    S(): s(func) {} // OK:初始化器列表是函数体的一部分
    const char
    s;
    };
    void f(const char* s = func); // 错误:形参列表是声明符的一部分



(C++11 起)

注解

在使用直接初始化语法的变量声明和函数声明之间有歧义的情况下,编译器选择函数声明;见直接初始化

示例

本节未完成原因:移动/清理

运行此代码

  1. #include <iostream>
  2. #include <string>
  3.  
  4. // 命名空间(文件)作用域中的声明
  5. // (定义在后面提供)
  6. int f1();
  7.  
  8. // 拥有默认实参的简单函数,不返回内容
  9. void f0(const std::string& arg = "world")
  10. {
  11. std::cout << "Hello, " << arg << '\n';
  12. }
  13.  
  14. // 返回指向 f0 的指针的函数
  15. auto fp11() -> void(*)(const std::string&)
  16. {
  17. return f0;
  18. }
  19.  
  20. // 返回指向 f0 的指针的函数,C++11 前的风格
  21. void (*fp03())(const std::string&)
  22. {
  23. return f0;
  24. }
  25.  
  26. int main()
  27. {
  28. f0();
  29. fp11()("test");
  30. fp03()("again");
  31. int f2(std::string); // 块作用域中的声明
  32. std::cout << f2("bad12") << '\n';
  33. }
  34.  
  35. // 简单的非成员函数,返回 int
  36. int f1()
  37. {
  38. return 42;
  39. }
  40.  
  41. // 拥有异常说明和函数 try 块的函数
  42. int f2(std::string str) noexcept try
  43. {
  44. return std::stoi(str);
  45. }
  46. catch(const std::exception& e)
  47. {
  48. std::cerr << "stoi() failed!\n";
  49. return 0;
  50. }

输出:

  1. Hello, world
  2. Hello, test
  3. Hello, again
  4. stoi() failed!
  5. 0

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 1394 C++11 弃置函数不能返回不完整类型 允许不完整的返回类型
CWG 577 C++11 待决类型 void 可用于声明无形参函数 仅允许非待决的 void
CWG 393 C++14 含有到未知边界数组的指针/引用的类型不能作为形参 允许这些类型