C++ primer 6章 函数
第6章:函数
函数基础
- 函数定义:包括返回类型、函数名字和0个或者多个形参(parameter)组成的列表和函数体。
 - 调用运算符:调用运算符的形式是一对圆括号 
(),作用于一个表达式,该表达式是函数或者指向函数的指针。 - 调用函数时圆括号内是用逗号隔开的实参(argument)列表。
 
- 函数调用过程:
- 1.主调函数(calling function)的执行被中断。
 - 2.被调函数(called function)开始执行。
 
 - 形参和实参:形参和实参的个数和类型必须匹配上。
 - 返回类型: 
void表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。 - 名字:名字的作用于是程序文本的一部分,名字在其中可见。
 
局部对象
- 生命周期:对象的生命周期是程序执行过程中该对象存在的一段时间。
 - 局部变量(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是隐藏的。
 - 自动对象:只存在于块执行期间的对象。当块的执行结束后,它的值就变成未定义的了,形参就是一种自动对象。
 - 局部静态对象: 
static类型的局部变量,生命周期贯穿函数调用前后。 
函数声明
- 函数声明:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称函数原型。
 - 在头文件中进行函数声明:建议变量在头文件中声明;在源文件中定义。
 - 分离编译: 
CC a.cc b.cc直接编译生成可执行文件;CC -c a.cc b.cc编译生成对象代码a.o b.o;CC a.o b.o编译生成可执行文件。 
参数传递
- 形参初始化的机理和变量初始化一样。
 - 引用传递(passed by reference):又称传引用调用(called by reference),指形参是引用类型,引用形参是它对应的实参的别名。
 - 值传递(passed by value):又称传值调用(called by value),指实参的值是通过拷贝传递给形参。
 
1  | //指针传递的写法  | 
传值参数
- 当初始化一个非引用类型的变量时,初始值被拷贝给变量。
 - 函数对形参做的所有操作都不会影响实参。
 - 指针形参:常用在C中,
C++建议使用引用类型的形参代替指针。 - 引用也比直接进行值传递,也就是进行值拷贝效率更高,甚至有的类型不支持拷贝操作,因此应该尽量用引用。当函数不需要改变形参的值时,应该将其声明为常量引用。
 
传引用参数
- 通过使用引用形参,允许函数改变一个或多个实参的值。
 - 引用形参直接关联到绑定的对象,而非对象的副本。
 - 使用引用形参可以用于返回额外的信息。
 - 经常用引用形参来避免不必要的复制。
 void swap(int &v1, int &v2)- 如果无需改变引用形参的值,最好将其声明为常量引用。
 - 当传入的实参为右值(字面值)时,不能使用引用传递。
 
void f(T)和void f(&T)的区别:
void f(T)的参数通过值传递,在函数中T是实参的副本,改变T不会影响到原来的实参。 void f(&T)的参数通过引用传递,在函数中的T是实参的引用,T的改变也就是实参的改变。
const形参和实参
- 形参的顶层
const被忽略。void func(const int i);调用时既可以传入const int也可以传入int。 - 我们可以使用非常量初始化一个底层
const对象,但是反过来不行。 - 在函数中,不能改变实参的局部副本。
 - 尽量使用常量引用,除非要修改传输实参的值。
 
数组形参
- 当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
 - 要注意数组的实际长度,不能越界。
 
main处理命令行选项
int main(int argc, char *argv[]){...}- 第一个形参代表参数的个数;第二个形参是参数C风格字符串数组,
argv的实参从argv[1]开始,argv[0]保存程序的名字,并非用户输入。 
可变形参
initializer_list提供的操作(C++ 11):
| 操作 | 解释 | 
|---|---|
initializer_list<T> lst; | 
默认初始化;T类型元素的空列表 | 
initializer_list<T> lst{a,b,c...}; | 
lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const。 | 
lst2(lst) | 
拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。 | 
lst2 = lst | 
同上 | 
lst.size() | 
列表中的元素数量 | 
lst.begin() | 
返回指向lst中首元素的指针 | 
lst.end() | 
返回指向lst中微元素下一位置的指针 | 
initializer_list使用demo:
1  | void err_msg(ErrCode e, initializer_list<string> il){  | 
- 所有实参类型相同,可以使用 
initializer_list的标准库类型。 - 实参类型不同,可以使用
可变参数模板。 省略形参符:
...,便于C++访问某些C代码,这些C代码使用了varargs的C标准功能。initializer_list永远是常量值,因此无法改变initializer_list中元素的值,因此在使用时如果用到了引用,应该使用常量引用类型。
返回类型和return语句
无返回值函数
没有返回值的 return语句只能用在返回类型是 void的函数中,返回 void的函数不要求非得有 return语句。
有返回值函数
return语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型。- 值的返回:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
 - 不要返回局部对象的引用或指针。
 - 引用返回左值:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。
 - 列表初始化返回值:函数可以返回花括号包围的值的列表。(
C++11) - 主函数main的返回值:如果结尾没有
return,编译器将隐式地插入一条返回0的return语句。返回0代表执行成功。 
返回数组指针
int *p1[10]指的是含有十个指针的数组;int (*p1)[10]指的是p1是指向含有十个数据的数组的指针 ;Type (*function (parameter_list))[dimension]- 使用类型别名: 
typedef int arrT[10];或者using arrT = int[10;],然后arrT* func() {...} - 使用 
decltype:decltype(odd) *arrPtr(int i) {...} - 尾置返回类型: 在形参列表后面以一个
->开始:auto func(int i) -> int(*)[10](C++11) 
例子:编写一个函数声明,使其返回数组的引用并且该数组包含10个string对象。
1  | string (&fun())[10];//直接返回string  | 
函数重载
- 重载:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
 main函数不能重载。- 重载和const形参:
- 一个有顶层const的形参和没有它的函数无法区分。 
Record lookup(Phone* const)和Record lookup(Phone*)无法区分。 - 相反,是否有某个底层const形参可以区分。 
Record lookup(Account*)和Record lookup(const Account*)可以区分。 
 - 一个有顶层const的形参和没有它的函数无法区分。 
 - 重载和作用域:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
 
特殊用途语言特性
默认实参
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');- 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。
 - 有默认形参的函数可以传入实参,也可以不传入。
 
内联(inline)函数
- 普通函数的缺点:调用函数比求解等价表达式要慢得多。
 inline函数可以避免函数调用的开销,可以让编译器在编译时内联地展开该函数。inline函数应该在头文件中定义,在函数返回值的前面加上inline关键字即可声明内联函数。- 内联机制一般用于规模较小,流程直接,频繁调用的函数。
 
constexpr函数
- 指能用于常量表达式的函数。
 constexpr int new_sz() {return 42;}- 函数的返回类型及所有形参类型都要是字面值类型,函数体中有且仅有一条return语句。
 constexpr函数应该在头文件中定义。
调试帮助
assert预处理宏(preprocessor macro):assert(expr);expr为0,assert输出信息并终止程序的执行;expr不为0,assert什么也不做。
开关调试状态:
CC -D NDEBUG main.c可以定义这个变量NDEBUG,如果定义NDEBUG,关闭调试状态。
1  | void print(){  | 
函数匹配
- 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
 - 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
 - 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。
 - 寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好。
 
函数指针
- 函数指针:是指向函数的指针。
 bool (*pf)(const string &, const string &);注:两端的括号不可少。- 函数指针形参:
- 形参中使用函数定义或者函数指针定义效果一样。
 - 使用类型别名或者
decltype。 
 - 返回指向函数的指针:1.类型别名;2.尾置返回类型。