1. 函数基础
典型函数结构:
返回类型,函数名,0个或者多个形参组成的列表,函数体。
调用运算符()
:
作用于一个表达式(可能是函数或者指向函数的指针),括号内是一组用逗号隔开的实参。
一个函数调用的例子:
并且,每次调用函数时,都要重新创建形参。
1.1 形参和实参
实参是形参的初始值。实参和形参之间有对应关系,且二者类型和数目必须匹配,或者实参类型可以转换为形参的类型,但是并未规定实参的求值顺序。编译器可以任意可行顺序对实参求值。
任意两个形参都不能同名。
如果函数用不上某个形参,此类形参通常不命名以表示不会在函数内使用。但是这类形参也必须给一个实参。
返回值设置为void* 表示不返回任何值。
1.2 局部对象
形参和函数体内部定义的变量统称为局部变量。
1.2.1 自动对象
1.2.2 局部静态对象
如果局部静态对象未被显式初始化,则会默认初始化。内置类型的局部静态对象会被初始化为0;
静态局部变量在函数内定义 它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它
虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值
1.2.3 函数声明
函数可以声明多次,但是只能定义一次。函数声明和定义十分相似,不同的是声明无需函数体,只需用一个;
代替即可。
函数声明没有函数体,无需形参名字(需要形参类型)。但是一般会写上名字,可以帮助更好理解函数功能。
分离式编译的概念:分离式编译允许把程序分割到几个文件中,每个文件独立编译。
2. 参数传递
如果形参是引用形式(称为引用传递或者函数被传引用调用),它将绑定到对应的实参上,否则将实参的值拷贝后赋值给形参(称为值传递或者函数被传值调用)。
2.1 传值参数
在传的形参不是引用格式时,会先将实参做一个拷贝,得到一个副本,再将该副本赋值为形参。所以对形参的改动不会影响到实参。
但是当传递的是指针的时候,虽然传进来的是指针的副本,对指针的所有修改不会生效,但是指针所指向的对象却是真实和准确的,所以可以假借指针修改对象。
2.2 传引用参数
使用引用避免拷贝,主要有两大原因:
当无需修改形参值时最好声明为常量引用。
2.3 const形参和实参
传参和赋值一样,从实参到形参会忽略顶层const。即当形参有顶层const,可以给他传常量或非常量皆可。
这种忽略顶层const的行为会导致某些问题:
首先明确:
假设按照上述规则定义了如下两个函数,明显二者参数有明显区别:
但是由于忽略顶层const的特性,上述的两个fcn都可以传非常量,就会造成不知道调用哪一个。
最好将参数定义为常量引用:
- 常量引用能隐含一定的类型转换。比如可以接受非常量实参,甚至是某些类型和形参不一样的实参。这会使得函数能接受的参数类型更多,更不易出错。
- 如果某些代码没有把形参定义为常量引用的代码习惯,那么在定义一些习惯良好的(将形参定义为常量引用)接口时会报错。如下:
2.4 数组形参
数组作为形参有三种写法:
2.4.1 如何获取数组长度
- 在数组结束的位置放置标记,当读到标记停止。
- 传递数组的起始位置和尾后位置。
- 显式传递长度。把数组长度作为参数传递。
2.4.2 数组引用作为形参
这种写法限制了传入的参数必须是长度为10的数组。
2.4.3 传入多维数组
数组的第二维是数组类型的一部分,不允许省略。前面提到了,表示一维数组引用时,一维数组的第一维也是类型的一部分。
2.5 main:处理命令行
第一个形参argc表示数组argv中的字符串数量。第二个数组表示的是参数相关。
2.6 含有可变形参的函数
2.6.1 所有实参类型相同(类型不同的方法在书16.4节介绍)
所有实参类型相同,使用initializer_list
。
initializer_list
也是模板类型,实例化该对象时,需指明列表中元素类型。使用方法如下:
initializer_list
实例化后的对象元素永远是常量,无法修改。
传参时,实参用花括号括起来。
程序能同时含有initializer_list
和其他形参。如下:
新版本调用方式如下:
2.6.2 与C交互的写法
位于varargs
C标准库。
3.返回类型和return语句
3.1 无返回值函数
如果想提前退出,可使用return;
语句。返回值为void
的函数的return
也可以使用第二张方式,不过它的表达式部分必须是一个返回void
的函数。
3.2 有返回值函数
return
语句的返回值类型必须和函数的返回类型相同或者能转换成函数的返回类型。
应确保有返回值的函数能且只能通过一条return
语句退出。
如果返回的是非引用,则将返回值拷贝到临时量内。如果返回的是引用,不会拷贝对象,只返回对象的引用。
3.2.1 不要返回局部对的引用或者指针
注意第二个return语句,很容易出现误判。
而返回局部对象本身是可以的,反正已经拷贝过了,原来的变量被释放也无所谓。
3.2.2 返回类类型的函数和调用运算符
调用运算符和成员运算符(.
,->
)具有相同的优先级,且满足左结合律(自左向右运算)。
3.2.3 引用返回左值
函数的返回值为引用,调用该函数得到左值。否则得到右值。
3.2.4 列表初始化返回值
C++11规定,函数可以将花括号包裹的列表作为返回值,用该列表对返回值初始化。
示例:
3.2.5 主函数main的返回值
一般而言,返回值为非void
的函数必须返回一个值。但是main
是个例外,允许main
没有返回值而结束,会隐式的插入return 0;
预处理变量不能通过名字空间访问。
3.2.6 返回值数组的指针
3.2.7 尾置返回类型
这种方式对返回值比较复杂的函数最有效。
对付复杂的返回值,还可以使用decltype:
4. 函数重载
名字相同,形参列表不同,称为函数重载。main
函数不能重载。
注意是形参列表不同,C++不允许两个函数仅有返回类型不同。
4.1 重载和const参数
一个有顶层const的形参和没有顶层const的形参是一样的。
底层const可以区分形参,可以用来实现重载:
编译器可以通过判断实参是否是常量来确定用哪个函数。如果不是常量,优先选择非常量引用/指针版本。
函数匹配/重载确定:把函数调用和一组重载函数中的某一个关联起来。
调用重载函数时的可能情况:
4.2 重载与作作用域
一旦在当前工作域中找到了所需要的名字,编译器就会忽略掉外层作用域中的同名实体。
如下所示:
5. 特殊用途语言特性
5.1 默认实参
在声明函数时,有些形参在调用中大部分时间使用一样的值,这个反复出现的值称为默认实参。
在实际调用时如果想用默认实参的值,只需在初始化时省略该实参即可。在函数调用时,实参依次赋值给形参,剩下的用默认实参填补。
只要作为实参的表达式的类型能转换为形参所需的类型,该表达式就能作为实参。
如果将变量作为默认实参,会在函数声明所在的作用域内完成名字的解析,在函数调用时完成求值。
如果在调用函数前重新给变量赋值,在调用时该赋值会生效,如果是重新声明了变量一次,会以之前的变量值作为实参。
5.2 内联函数和constexpr函数
虽然函数易于理解和管理,但是会引入调用函数的开销。
内联函数:将一个函数定义为内联函数,就是该函数在调用点内联展开。在函数返回类型前加上inline,将其声明为内联函数。内联函数用于优化规模较小、流程直接且调用频繁的函数。
5.3 constexpr函数
函数返回类型和所有的形参类型都必须是字面值类型。一般被当作常量表达式使用。
编译器调将对constexpr函数的调用替换为其结果值,且constexpr被隐式指定为内联函数。
允许constexpr的返回值不是一个常量。但其返回类型必须是字面值类型。
5.4 assert预处理宏
宏名字存在必须唯一。含有cassert头文件的程序不能再定义assert的变量。
5.5 NDEBUG预处理变量
可用NDEBUG
预处理变量。如果该变量被定义,则assert失效,否则assert将进行检查。
示例:
6. 函数匹配
6.1 确定候选函数
第一步:选出候选函数组成的重载函数集,即满足如下两个条件的函数:
- 与被调函数同名
- 其声明在调用点可见
第二步:考察实参,选出能被这组实参调用的函数。称为可行函数。
- 形参数量和实参相等(默认实参存在的情况除外)
- 实参和形参对应的类型相同,或者实参可以转化为形参。
如果找不到,会报无匹配函数的错。
第三步:寻找最佳匹配。检查实参,找和实参类型最匹配的形参对应的函数。
何为最匹配?
如果找不到,会报错二义性调用的错。
6.2 实参类型转换
为了明确最佳匹配的优劣评判标准,划分了如下的匹配情况:
6.2.1 类型提升和算术类型转换
小整形会默认转为int,所以会调用int版本,除非参数是short,精确匹配。
6.2.2 函数匹配和const
在4.1节提到,如果两个函数的参数区别在于是否含有底层const
,当传入的参数为非常量,会优先调用不含底层const
的那个函数。
现在用函数匹配的知识解释下为什么:
当传入的参数是常量时,只能调用有底层const的参数,因为无法将非常量引用绑定到常量上。
当传入的参数不是常量时,都能满足调用要求,但是要调用有底层const的函数涉及到类型转换,所以选择精确匹配的版本。
7. 函数指针形参
不能定义函数类型的形参,但是可以定义函数指针类型的形参。虽然看起来是函数,实际使用时当成指针。
在传实参时,可以直接传入函数名,会自动转为指针。
typedef定义函数类型:
typedef的用法和用它定义数组类型有点像,定义数组是:
都是 typedef
+ 类型 + 自定义类型名 + 形参/维度 格式的。
7.1 返回指向函数的指针
同数组类似,不能用函数作为实参,也不能返回函数。但是可以用函数的指针作为参数,也能返回函数的指针。
与作为参数时不同的是,在作为形参和实参时,可以用函数或者函数指针作为对象,因为会把函数自动转为函数指针。作为返回值时,必须写成函数指针形式,编译器不会自动把函数返回类型当成指针。
可以将函数指针起一个别名:
即可以用下列语句完成函数指针声明和定义:
一种原始直接的写法:
或者使用尾置返回法: