声明函数

来自cppreference.com
< cpp‎ | language

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

目录

[编辑] 函数声明

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

被声明的函数类型由返回类型(由声明语法decl-specifier-seq 提供)和函数声明器( declarator )组成。

noptr-declarator ( parameter-list ) cv(可选) ref(可选) except(可选) attr(可选) requires(可选)(C++20) (1)
noptr-declarator ( parameter-list ) cv(可选) ref(可选) except(可选) attr(可选) -> trailing requires(可选)(C++20) (2) (C++11 起)

declarator 语法的其他形式见声明

1) 常规函数声明语法
2) 尾随返回类型声明:尾随返回类型仅允许在最外层函数声明器。此情况下的 decl-specifier-seq 必须包含关键词 auto
noptr-declarator - 任何合法的 declarator ,但若它以 * 、 & 、 或 && 开始,则它必须为括号所环绕。
parameter-list - 可以为空、函数参数的逗号分隔列表(细节见后述)
attr(C++11) - 可选的属性列表。这些属性应用于函数类型,而非函数自身。出现于声明器中标识符之后的属性会与出现于声明开端的属性合并,若它们存在。
cv - const/volatile 限定,只允许在非静态成员函数中
ref(C++11) - 引用限定,只允许在非静态成员函数中
except - 动态异常规定(C++17 前)

noexcept 规定(C++11)之一。注意异常规定不是函数类型的一部分 (C++17 前)

trailing(C++11) - 尾随返回类型,在返回类型依赖于参数名的情况有用,例如 template <class T, class U> auto add(T t, U u) -> decltype(t + u); ,或在类型复杂的情况,例如在 auto fpif(int)->int(*)(int)
requires(C++20 起) - requires 子句声明关联于函数的制约,这必须为重载决议所选择的的函数所满足。(例如: void f1(int a) requires true; )注意关联的制约是函数签名的一部分,但不是函数类型的一部分。

函数声明器可与其他声明器混合,其中 decl-specifier-seq 允许:

// 声明一个 int 、一个 int* 、一个函数,及一个指向函数的指针
int a = 1, *p = NULL, f(), (*pf)(double);
// decl-specifier-seq 是 int
// declarator f() 声明(但不定义)
//                一个无参数并返回 int 的函数
 
struct S
{
    virtual int f(char) const, g(int) &&; // 声明二个非静态成员函数
    virtual int f(char), x; // 编译时错误: virtual (在 decl-specifier-seq 中)
                            // 仅在非静态成员函数的声明中允许
};

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

同任何声明,出现于声明前的属性和在声明器中立即出现在标识符后的属性应用于声明或定义的同一实体(在此例中,应用到函数)

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

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

void f() [[noreturn]]; // 错误:此属性对效果无作用
(C++11 起)

返回类型推导

若函数声明的 decl-specifier-seq 包含关键词 auto ,则尾随返回类型可以省略,且编译器将从用于 return 语句的表达式类型推导出它。若返回类型不使用 decltype(auto) ,则推导遵循模板参数推导的规则。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 的目的的立即语境中。

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

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

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); } // 错误,歧义

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

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(可选) decl-specifier-seq declarator (1)
attr(可选) decl-specifier-seq declarator = initializer (2)
attr(可选) decl-specifier-seq abstract-declarator(可选) (3)
attr(可选) decl-specifier-seq abstract-declarator(可选) = initializer (4)
... (5)
void (6)
1) 声明具名(形式)参数。 decl-specifier-seqdeclarator 的含义见声明
int f(int a, int *p, int (*(*x)(double))[3]);
2)默认值,声明具名(形式)参数。
int f(int a = 7, int *p = nullptr, int (*(*x)(double))[3] = nullptr);
3) 声明一个无名参数
int f(int, int *, int (*(*)(double))[3]);
4)默认值声明无名参数
int f(int = 7, int * = nullptr, int (*(*)(double))[3] = nullptr);
5) 声明一个变参数函数,只能作为参数列表的最末参数出现。
int printf(const char* fmt, ...);
6) 指示函数不采用参数,它是空参数列表的准确同意语: int f(void);int f(); 声明同一函数。注意类型 void (可以有 cv 限定)不能在其他情况下用于参数列表: int f(void, int);int f(const void); 是错误(尽管能使用其导出类型,如 void* )。{{rev inl|since=c++11|在模板中,只能使用非依赖的 void (若以 T = void} 实例化,则采用单个 <code>T 类型参数的函数不会成为无参数函数)</code>

尽管 decl-specifier-seq 隐含可以存在异于类型指定符的 指定符,受允许的其他指定符唯有 register 还有 auto (C++11 前) ,且它无效果。

(C++17 前)

若任何函数参数使用占位符auto制约类型之一),则函数声明替代地成为缩写的函数模板声明:

void f(auto (auto::*)(auto)); // #1
template<typename T, typename U, typename V> void f(T (U::*)(V)); // 同 #1
 
void g1(const C1*, C2&); // #2 (假设 C1 和 C2 是概念)
template<C1 T, C2 U> void g1(const T*, U&); // 同 #2
(概念 TS)

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

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

1) 首先, decl-specifier-seq 和声明器按如同在任何声明中的方式组合,以确定类型。
2) 若类型是“ T 的数组”或“ T 的未知边界数组”,则它被替换成“指向 T 的指针”
3) 若类型是函数类型 F ,则它被替换成“指向 F 的指针”类型
4) 从参数类型中丢弃顶层 cv 限定符(此调整只影响函数类型,但不修改参数的属性: int f(const int p, decltype(p)*);int f(int, const int*); 声明同一函数)

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

int f(char s[3]);
int f(char[]);
int f(char* s);
int f(char* const);
int f(char* volatile s);

下列声明亦声明同一函数:

int f(int());
int f(int (*g)());
参数类型不能是包含到未知边界数组的引用或指针类型,包含这种类型的顶层指针/数组,或指向以这些类型为参数的函数的指针 (C++14 前)

指示可变参数的省略号形参前的逗号是可选的,即使它跟随指示形式参数包展开的省略号,故下列函数模板是完全相同的:

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

这种声明被使用时的例子是 std::is_function 的实现

[编辑] 函数定义

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

attr(可选) decl-specifier-seq(可选) declarator virt-specifier-seq(可选) function-body

where function-body is one of the following

ctor-initializer(可选) compound-statement (1)
function-try-block (2)
= delete ; (3) (C++11 起)
= default ; (4) (C++11 起)
1) 常规函数体
2) 函数 try 块(这是包装在 try/catch 块内的通常函数体)
3) 显式被删除的函数定义
4) 显式被默认的函数定义,仅对特殊成员函数允许
attr(C++11) - 可选的属性列表。这些属性与出现在 declarator 中标识符之后的属性结合(见此页面顶部),若它们存在。
decl-specifier-seq - 带指定符的返回类型,如在声明文法
declarator - 函数声明器,与上述函数声明文法相同。和函数声明一样,它可后随 requires-clause (C++20 起)
virt-specifier-seq(C++11) - overridefinal ,或它们任意顺序的结合(仅对成员函数允许)
ctor-initializer - 成员初始化器列表,仅在构造函数中允许
compound-statement - 花括号环绕的语句序列,它们构成函数体
int max(int a, int b, int c)
{
    int m = (a > b)? a : b;
    return (m > c)? m : c;
}
// decl-specifier-seq 是“ int ”
// 声明器是“ max(int a, int b, int c) ”
// 体是 { ... }

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

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

函数定义的声明于 declarator 的参数在体内处于作用域中。若在函数体中不使用参数,则它不需要被命名(足以使用抽象声明器)

void print(int a, int) // 不使用第二个参数
{
    std::printf("a = %d\n",a);
}

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

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

被删除的函数

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

若函数被重载,则首先发生重载决议,且仅若被删除函数被选择程序才为病态。

struct sometype
{
    void* operator new(std::size_t) = delete;
    void* operator new[](std::size_t) = delete;
};
sometype* p = new sometype; // 错误:尝试调用被删除的 sometype::operator new

函数的删除定义必须是翻译单元中的首条声明:前置声明函数不能声明为被删除:

struct sometype { sometype(); };
sometype::sometype() = delete; // 错误:必须在首条声明删除

__func__

在函数体内,函数局域的预定义变量 __func__ 被以如同下列方式定义

static const char __func__[] = "function-name";

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

struct S
{
    S(): s(__func__) {} // OK :初始化器列表是函数体的一部分
    const char* s;
};
void f(const char* s = __func__); // 错误:参数列表是声明器的一部分
(C++11 起)

[编辑] 注意

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

[编辑] 示例

#include <iostream>
#include <string>
 
// 命名空间(文件)作用域中的声明
// (定义在后面提供
int f1();
 
// 拥有默认参数的简单函数,不返回内容
void f0(const std::string& arg = "world")
{
    std::cout << "Hello, " << arg << '\n';
}
 
// 返回指向 f0 指针的函数
auto fp11() -> void(*)(const std::string&)
{
    return f0;
}
 
// 返回指向 f0 指针的函数, C++11 前风格
void (*fp03())(const std::string&)
{
    return f0;
}
 
int main()
{
    f0();
    fp11()("test");
    fp03()("again");
    int f2(std::string); // 块作用域中的声明
    std::cout << f2("bad12") << '\n';
}
 
// 简单的非成员函数,返回 int
int f1()
{
    return 42;
}
 
// 拥有异常规定和函数 try 块的函数
int f2(std::string str) noexcept try
{ 
    return std::stoi(str);
}
catch(const std::exception& e)
{
    std::cerr << "stoi() failed!\n";
    return 0;
}

输出:

Hello, world
Hello, test
Hello, again
stoi() failed!
0

[编辑] 缺陷报告

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

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