第06章_函数

函数基础

局部对象

函数声明

  • 定义函数的源文件应该把含有函数声明的头文件包含进来, 编译器负责验证函数的定义和声明是否匹配

分离式编译

参数传递

传值参数

传引用参数

const 形参和实参

  • 当用实参初始化形参时会忽略掉顶层 const. 换句话说, 形参的顶层 const 被忽略掉了. 当形参有顶层 const 时, 传给它常量对象或者非常量对象都是可以的
  • 由于顶层 const 被忽略了, 因此有无顶层 const 的形参对于函数重载是一样的
    1
    2
    void fcn(int i) {}
    void fcn(const int i) {} // error, 重复定义

数组形参

main: 处理命令行选项

含有可变形参的函数

initializer_list 形参

  • initializer_list 对象中的元素永远是常量值
    1
    2
    3
    4
    5
    void error_msg(initializer_list<std::string> l) {
    for (auto beg = l.begin(); beg != l.end(); ++beg) {
    std::cout << *beg << std::endl;
    }
    }

省略符形参

  • 省略符形参是为了便于 C++ 程序访问某些特殊的 C 代码而设置的

返回类型和 return 语句

无返回值函数

有返回值函数

  • 返回一个值的方式和初始化一个变量或形参的方式完全一样: 返回的值用于初始化调用点的一个临时量, 该临时量就是函数调用的结果
  • 不要返回局部对象的引用或指针
  • 函数的返回类型决定函数调用是否是左值: 调用一个返回引用的函数得到左值, 其他返回类型得到右值
  • 函数可以返回花括号包围的值的列表. 类似于其他返回结果, 此处的列表也用来对表示函数返回的临时量进行初始化.

尾置返回类型

1
auto func(int i) -> int { return i; }

使用 decltype

1
2
int i = 0;
decltype(i) func(int i) { return i; }

返回数组指针

函数重载

  • 同一作用域内的几个函数名字相同但形参列表不同, 称为函数重载
  • main 函数不能重载
  • 不允许两个函数除了返回类型外其他所有的要素都相同
  • 一个拥有顶层 const 的形参无法和另一个没有顶层 const 的形参区分开来
    1
    2
    int func(int i);
    int func(const int i); // error
  • 如果形参是某种类型的指针或引用, 则通过区分其指向的是常量对象还是非常量对象可以实现函数重载, 此时 const 是底层的
    1
    2
    3
    4
    5
    int func(int *p);
    int func(const int *p); // yes

    int test(int &c);
    int test(const int &c); // yes

重载与作用域

  • 在内层作用域中声明函数, 将隐藏外层作用域中声明的同名实体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void func(std::string s) {
    std::cout << s << std::endl;
    }

    int main() {
    void func(int a);
    func("hello"); // error
    return 0;
    }
  • 当调用函数时, 编译器首先寻找对该函数的声明, 一旦在当前作用域中找到了所需的名字, 编译器就会忽略掉外层作用域中的同名实体

特殊用途语言特性

默认实参

  • 通常, 应该在函数声明中指定默认实参, 并将该声明放在合适的头文件中
  • 局部变量不能作为默认实参
  • 只要表达式的类型能转换成形参所需的类型, 该表达式就能作为默认实参
  • 用作默认实参的名字在函数声明所在的作用域内解析, 而这些名字的求值过程发生在函数调用时

内联函数和 constexpr 函数

  • 内联说明只是向编译器发出的一个请求, 编译器可以选择忽略这个请求
  • constexpr 函数是指能用于常量表达式的函数
  • constexpr 函数体内也可以包含其他语句, 只要这些语句在运行时不执行任何操作就行
  • 把内联函数和 constexpr 函数放在头文件中(可以在程序中多次定义, 因为编译器要想展开函数仅有函数声明是不够的, 还需要函数的定义. 但其多个定义必须完全一致, 因此通常定义在头文件中)

调试帮助

  • assert 依赖一个名为 NDEBUG 的预处理变量的状态

函数匹配

实参类型转换

函数指针

  • 函数指针指向的是函数而非对象
  • 函数的类型由它的返回类型和形参类型共同决定, 与函数名无关
  • 把函数名当作一个值使用时, 该函数自动地转换成指针
    1
    2
    pf = func;
    pf = &func; // 等价
  • 如果定义了指向重载函数的指针, 编译器通过指针类型决定选用哪个函数
  • decltype 返回的是函数类型, 不会将韩式类型自动转换成指针类型, 所以只有加上 * 才能得到指针
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int func(int);

    using Func = int(int);
    typedef int Func(int);
    typedef decltype(func) Func; // 与上两行等价, 都是函数类型

    using FuncP = int(*)(int);
    typedef int (*Func)(int);
    typedef decltype(func) *FuncP; // 与上两行等价, 都是函数指针类型

    void useFunc(Func);
    void useFunc(FuncP); // 这两个声明语句声明的是同一个函数, 在第一个语句中, 编译器自动地将 Func 表示的函数类型转换成指针
  • 返回指向函数类型的指针必须把返回类型写成指针形式, 编译器不会自动地将函数返回类型当成对应的指针类型处理