6. 函数
重载函数:同一个名字可以对应几个不同的函数
6.1 函数基础
函数定义包括:返回类型、函数名字、形参列表(可以没有也可以有多个)、函数体。
以求数的阶乘函数为例:
- 主调函数是调用函数的程序、被调函数是函数的程序;
- 实参是形参的初始值,在调用过程中对函数的形参进行初始化;
- 函数的形参列表可以为空,但是不能省略;
- 当函数的返回类型为void时表示函数不返回任何值,函数的返回类型不能是数组类型或函数类型(但可以是指向数组或函数的指针)。
6.1.1 局部对象
- 名字有作用域,对象有生命周期
- 名字的作用域是程序文本的一部分,名字在其中可见
- 对象的生命周期是程序执行过程中该对象存在的一段时间
形参和函数体内部定义的变量统称为局部变量,尽在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中同名的其它所有声明中。
- 自动对象:在经过变量定义语句时创建对象,到达定义所在的块末尾时销毁它(形参);
- 局部静态对象:static类型,另局部变量的生命周期贯穿函数调用及之后的时间;
6.1.2 函数声明
- 函数的名字必须在使用之前声明,只能定义一次但可以声明多次(可以在头文件声明,源文件定义,定义函数的源文件应该把含有函数声明的头文件包含进来);
- 函数声明无须函数体以及形参的名字(但是写上可以便于理解);
- 通过函数的三要素(返回类型、函数名、形参类型)描述了函数的接口;
- 函数声明又称为函数原型;
6.1.3 分离式编译
6.2 参数传递
- 形参是引用类型:形参绑定到对应的实参上,对应的实参被引用传递/函数被传引用调用,引用形参是它对应的实参的别名;
- 形参不是引用类型:将实参的值拷贝后赋给形参,形参和实参是两个独立的对象,这样的实参被值传递/函数被传值调用;
6.2.1 传值参数
实参的值拷贝后赋给形参,对变量的改动不会影响初始值(不影响实参)。
指针形参:拷贝之后形参和实参是两个不同的指针(指向同一个对象),通过指针可以修改它所指对象的值。
6.2.2 传引用参数
通过引用形参,函数可以改变实参的值。
- 拷贝大的类类型对象或者容器对象比较低小,有的类类型不支持拷贝操作,因此可以通过引用形参访问该类型对象。
- 一个函数只能返回一个值,通过引用形参可以一次返回多个结果。例如想要同时返回某字符第一次出现的位置以及出现的总次数:
函数运行后,ctr的值为'o'出现的次数。
6.2.3 const形参和实参
允许定义名字相同的函数,但是不同函数的形参列表应该有明显的区别。
指针或引用形参与const:
对于变量的初始化:可以用非常量初始化一个底层const对象,但是反过来不行,一个普通的引用不能用常量初始化必须用普通引用初始化
类似的:
尽量使用常量引用
6.2.4 数组形参
- 不允许拷贝数组(无法使用传值);
- 使用数组时通常会将其转换成指针;
形参写成类似数组的形式:
防止数组下标超过范围的方法:
使用标记指定数组长度:C风格字符串:存储在字符数组中,数组本身包含一个结束标记,最后一个字符后面跟着一个空字符;
使用标准库规范管理数组长度:传递指向数组首元素和尾后元素的指针
显示传递一个表示数组大小的形参:
- 当函数不需要改变数组的时候,数组形参应该是指向const的指针;
- 函数回改变数组元素值的时候,形参定义为指向非常量的指针;
6.2.5 main:处理命令行选项
命令行选项通过两个可选形参传递给main函数:
第一个表示数组中字符串的数量,第二个是一个数组,其元素为指向C风格字符串的指针
6.2.6 含有可变形参的函数
对于无法提前预知传递几个实参的情况
6.3 返回类型和return语句
return语句终止当前正在执行的函数,将控制权返回到调用该函数的地方:
6.3.1 无返回值的函数
对于返回类型为void的函数:
- return:直接返回
- return expression:expression必须时另一个返回void的函数
6.3.2 有返回值的函数
return语句返回值的类型必须与函数的返回类型相同。
- 值如何被返回:返回的值用于初始化调用点的一个临时量,该临时量为函数调用的结果;
- 不要返回局部对象的引用或指针:函数完成后,局部变量的存储空间被释放,其引用或指针是一个不存在的对象;
- 返回引用的函数得到一个左值,返回其他类型得到右值;
- 函数可以返回花括号包围的值的列表;
- 递归函数:如果一个函数调用了它自身,该函数成为递归函数;
6.3.3 返回数组指针
因为数组不能被拷贝,所以函数不能返回数组,但是可以返回数组的指针或引用。使用类型别名可以简化这一过程:
6.4 函数重载
同一作用域内几个函数名字相同但形参列表不同。调用时编译器根据传递的实参类型推断执行哪个函数。
定义重载函数:
顶层const无法区分:一个拥有顶层const的形参无法和一个没有顶层const的形参区分开来
底层const可以区分:当指向/引用的对象类型为const及非const时,两个函数可以区分开
6.5 特殊用途语言特性
6.5.1 默认实参
函数每次调用时可以被赋予相同的值:
一旦某个形参被赋予了默认值,它后面所有形参都必须有默认值。
一个形参只能被赋予一次默认实参,在函数的多次声明中后续声明只能为之前没有默认值的形参添加默认实参,且该形参右侧所有形参都必须有默认值:
默认实参不能是局部变量:
6.5.2 内联函数
内联函数可以避免函数调用的开销,在函数的返回类型前加上关键字inline声明为内联函数:
6.5.3 constexpr函数
指能用于常量表达式的函数,函数的返回类型及所有形参的类型都是字面值类型,函数体中有且只有一条return语句,constexpr函数被隐式地指定为内联函数。
new_ sz函数永远返回42
内联函数和constexpr函数可以在程序中多次定义,但是多个定义必须完全一致,因此把内联函数和constecpr函数放在头文件内。
6.5.4 调试帮助
为了有选择地执行调试代码,程序可以包含一些用于调试的代码但是只在开发程序时使用,在正常使用时屏蔽掉调试代码的方法。
assert预处理宏(定义在cassert头文件中):
expr=0:assert输出信息并终止程序的执行
NDEBUG预处理变量:
assert的行为 依赖于NDEBUG预处理变量的状态,定义NDEBUG后assert不执行任何操作。
6.6 函数匹配
确定候选函数和可行函数:
- 候选函数:与被调用的函数同名,其声明在调用点可见;
- 可行函数:考察本次调用提供的实参,选出能被这组实参调用的函数。形参数量与本次调用的实参数量相等,每个实参类型与对应的形参类型相同;
寻找最佳匹配:
实参类型与形参类型越接近,匹配得越好。
含有多个形参的函数匹配:
- 函数每个实参的匹配都不劣于其它可行函数需要的匹配
- 至少有一个实参的匹配由于其他可行函数提供的匹配
- 如果没有满足以上条件的函数,编译器将报错
6.7 函数指针
函数指针指向函数,函数类型由它的返回类型和形参类型共同决定,与函数名无关。
使用函数指针:
把函数名作为一个值使用时,函数自动转换成指针:
使用函数指针调用函数无需解引用:
重载函数指针:
函数指针形参:
和数组类似,不能定义函数类型的形参,但是形参可以是指向函数的指针。
可以直接把函数作为实参使用,此时会自动转换成指针:
返回函数指针:
和数组类似,不能返回函数类型,但可以返回指向函数的指针。
将auto和decltype用于函数指针类型: