《C++ Primer》读书笔记 第六章 函数

第六章 函数

6.1 函数基础

  • 编写和调用。
  • 形参和实参。
  • 形参列表和返回类型。

6.1.1 局部对象

  • 自动对象。
  • 局部静态对象:在程序执行路径第一次经过该对象的定义时初始化,直到程序终止才被销毁。

6.1.2 函数声明

  • 函数的三要素:返回类型、函数名、形参类型。
  • 函数原型:即函数声明。

6.1.3 分离式编译

  • 在头文件中进行函数声明。

6.2 参数传递

6.2.1 传值参数

6.2.2 传引用参数

6.2.3 const形参和实参

和其他初始化过程一样,用实参初始化形参时会忽略掉顶层 const。

尽量使用常量引用。

6.2.4 数组形参

数组有两个性质:不允许拷贝,使用时会转换成指针。

几种传递数组长度信息的方法:

  • C风格字符串使用标记位。
  • 标准库规范,传两个指针。
  • 再传递一个表示大小的参数。

可以使用数组引用形参,但这会限制数组的大小。

1
void print(int (&arr) [10]);

传递多维数组:

1
void print(int (*matrix)[10], int rowSize);

6.2.5 main: 处理命令行选项

6.2.6 含有可变形参的函数

两种方式:

  • 若类型相同,可使用 initializer_list 的标准库类型。
  • 若类型不同,可编写一种特殊的函数,即可变参数模板。16.4

还有种使用省略符的方法,一般只用于与C函数交互的接口程序。

注意,实参要使用花括号的形式。

6.3 返回类型和 return 语句

6.3.1 无返回值函数

6.3.2 有返回值函数

C++11中,函数可以返回花括号包围的值的列表。因为返回的值是用于初始化调用点的一个临时量。

1
2
3
4
5
vector<string> process()
{
...
return {"xxx", "xxxx"};
}

主函数可以返回<cstdlib>里的EXIT_FAILUREEXIT_SUCCESS

6.3.3 返回数组的指针

因为数组不能拷贝,所以函数不能返回数组。不过可以返回数组的指针或引用。有以下几种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1. 使用类型别名
typedef int arrT[10];
arrT* func(int i);

// 2. 声明一个返回数组指针的函数
int (*func(int i))[10];

// 3. 使用尾置返回类型(C++11)
auto func(int i) -> int(*)[10];

// 4. 使用 decltype (C++11)
int odd[] = {1, 3, 5};
decltype(odd) *arrPtr(int i)
{
...
}

6.4 函数重载

有无顶层 const 形参无法区分开。

1
2
3
4
5
6
// 错误示例
Record lookup(Phone);
Record lookup(const Phone);

Record lookup(Phone*);
Record lookup(Phone* const);

底层 const 可以。

1
2
3
4
5
Record lookup(Account&);
Record lookup(const Account&);

Record lookup(Account*);
Record lookup(const Account*);

const_cast在重载场景下最有用:

1
2
3
4
5
6
7
8
9
10
11
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}

string& shorterString(string& s1, string& s2)
{
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(r);
}

注意,所有重载函数的声明应放在同一作用域下。

6.5 特殊用途语言特性

6.5.1 默认实参

注意,声明时,每个形参只能被赋予一次默认实参,可以在不同声明中为不同形参添加默认实参。

默认形参的初始值可以是变量,但不能是局部变量,必须声明在函数之外,可以在调用前被别的函数所更改。

6.5.2 内联函数和 constexp 函数

inline 只是向编译器发出了一个请求,编译器可以选择忽略这个请求。

constexp 函数的约定:

  • 返回值及所有形参类型都是字面值类型。
  • 函数题中必须有且只有一条 return 语句。

这两种函数可以在程序中多次定义,但是必须完全一致,因此通常定义在头文件中。

6.5.3 调试帮助

  • assert 预处理宏:<cassert>中。
  • NDEBUG 预处理变量:如果被定义,assert 将不起作用。

可以结合#ifndef进行调试。

编译器也定义了一些局部静态变量,可帮助调试:__FILE____LINE____TIME____DATE__

6.6 函数匹配

  1. 确定候选函数。

    • 同名。
    • 可见。
  2. 选出可行函数。

    • 参数数量相等(考虑默认值)。
    • 参数类型相同,或能转换。
  3. 寻找最佳匹配。(如果有的话)

    • 逐一检查参数,寻找最匹配的。需要:

      • 每个参数的匹配都不劣于匹配其他函数。
      • 至少有一个参数的匹配优于匹配其他函数。
    • 如果并不是有且只有一个函数满足,编译器将报二义性调用的错误信息。

设计良好的系统中,不应该对实参进行强制类型转换。

实参类型转换的等级:

  1. 精确匹配。

    • 类型相同。
    • 从数组或函数类型转成对应的指针类型。(6.7
    • 添加或删除顶层 const。
  2. 通过 const 转换实现的匹配。(4.11.2

  3. 通过类型提升实现的匹配。(4.11.1
  4. 通过算数类型转换或指针转换实现的匹配。(4.11.1、4.11.2
  5. 通过类类型转换实现的匹配。(14.9

6.7 函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1. 定义
bool (*pf)(const string&, const string&);

// 2. 赋值(&可选)
pf = 0;
pf = nullptr;
pf = lengthCompare;
pf = &lengthCompare;

// 3. 调用
pf("xx", "xx");
(*pf)("xx", "xx");

// 4. 函数指针形参
void useBigger(const string& s1, const string& s2, bool pf(const string &, const string &));
void useBigger(const string& s1, const string& s2, bool (*pf)(const string &, const string &));

// 5. 使用别名(*不能省略,不然是函数的别名)
typedef bool(*FuncP) (const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;

// 6. 返回指向函数的指针((*)不能省略,不然是函数的别名)
using PF = int(*)(int*, int);
PF f1(int);
F *f1(int); // *不能省略,返回值不能是函数。

int (*f1(int))(int*, int); // 不用别名的写法。

decltype(sumLength) *getFcn(const string &); // 使用 decltype 的写法,*不能省略。

如果指向重载函数,只能是符合它的那个。

练习

  • 6.1 实参和形参的区别。
  • 6.2 不要省略函数定义中的返回值类型、返回语句、大括号。
  • 6.3 编写阶乘函数。
  • 6.4 读入数据,使用编写的函数。
  • 6.5 编写绝对值函数。
  • 6.6 形参、局部变量、局部静态变量的区别。
  • 6.7 静态变量的使用。
  • 6.8 简单编程练习。
  • 6.9 理解编译器的分离式编译。
  • 6.10 编写交换函数。
  • 6.11 传引用。
  • 6.12 传引用和传指针的比较。
  • 6.13 传引用和传值。
  • 6.14 传引用。
  • 6.15 传值、传引用、传常量引用。
  • 6.16 尽量传常量引用。
  • 6.17 传引用、传常量引用的使用场景。
  • 6.18 传引用、传迭代器的使用场景。
  • 6.19 实参格式。
  • 6.20 传引用、传常量引用的使用场景。
  • 6.21 简单编程练习。
  • 6.22 编写交换两个指针的函数。
  • 6.23 简单编程练习。
  • 6.24 传数组。KEY: 可使用:void print10(const int (&ia)[10] ) { }
  • 6.25 main()的参数。
  • 6.26 main()的参数。