函数定义:
一个典型的函数定义包括以下部分:返回类型(return type)、函数名、由形参(parameter)组成的列表、函数体(function body)。
函数声明:
和其他名字一样,函数的名字也必须在使用之前声明。类似于变量,函数只能定义一次,但可以声明多次。如果一个函数永远不会被用到,允许只有声明,没有定义。
函数的声明和函数的定义很类似,唯一的区别是函数声明无需函数体。由于不包含函数体,形参的名字也可以不用。
参数传递:
传引用和传值:
形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将被绑定到对应的实参上面,否则,将实参的值拷贝后赋值给形参。
1、使用引用传递避免拷贝,如果无须改变形参的值,最好声明为常量引用
2、使用引用形参返回额外信息
const形参和实参
顶层const作用于对象本身,和其他初始化过程一样,当用实参初始化形参时会忽略掉顶层const。所以:
调用时,因为顶层const被忽略,所以可以传入常量和非常量,同时,因为忽略顶层const的问题:
虽然C++支持函数重载,但是由于忽略了顶层const的关系,这样属于重复定义了该函数。
数组形参
1、普通数组形参
因为数组不允许被拷贝以及使用数组时通常会将其转化为指针。因此当我们向函数传递一个数组时,实际上传递的是指向数组首元素的指针。
如果这样定义三个函数,编译器会报错,因为重复定义了。
2、数组引用形参
C++允许将变量定义成数组的引用,基于此,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,用法如下:
3、传递多维数组
和其他数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。又因为并不存在多维数组,多维数组只是数组的数组,所以多维数组的首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维的大小都是数组类型的一部分,所以不能忽略:
默认实参
|
|
如上面的代码中,每个形参都提供了一个默认实参,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。
因为get_area函数为它的所有形参都提供了默认实参,所以我们可以用0、1、2个实参来调用该函数:
函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右侧位置),所以只能省略尾部的实参。
函数的返回值
1、没有返回值的函数
没有返回值的函数只能存在于返回类型是void的函数中。返回void的函数不要求非得有return语句,因为在这类函数的最后一句后面会隐式地执行return。
2、有返回值的函数
返回类型不是void的函数内的每一条return语句必须返回一个与分会类型相同类型的,或者是能隐式地转换成函数的返回类型的值。
3、值的返回的方式
返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
注意:不要返回局部对象的引用或者指针
4、引用返回左值
函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。可以像使用其他左值那样来使用返回引用的函数的调用,特别是,我们能为返回类型是非常量引用的函数的结果赋值。
5、返回数组指针
同样,因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或引用,但是由于返回数组的指针或引用的写法太过复杂,推荐使用尾置返回类型。(之后另开博客介绍)
函数重载
1、重载函数的定义
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为函数重载(overload)。
这些函数接受的形参类型不一样,但是执行的操作类似。当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数。
对于重载的函数,它们应该在形参数量或形参类型上有所不同。
1、不允许两个函数出了返回类型外其他所有的要素都相同,若如此,则第二个函数的声明是错误的。
2、虽然两个形参列表看起来不一样,但其实是一样的,如下:
|
|
|
|
拥有顶层const的形参无法和另一个没有顶层const的形参区分开来
底层const可以被区分
2、重载函数的调用
编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较结果决定调用哪个函数,这个过程叫做函数匹配(function matching)或重载确定(overload resolution),下面会着重介绍。
调用重载函数的三种结果:
1、编译器找到一个与实参最佳匹配(best match)的函数,并生成调用该函数的代码。
2、找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配(no match)的错误信息。
3、有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为二义性调用(ambiguous call)。
内联函数
将函数指定为内联函数(inline),通常就是将它在每个调用点上“内联地”展开。
该内联函数在调用的时候:
将在编译过程中展开成类似于下面的形式:
一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。
函数匹配
1、选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。候选函数有两个特征:一是与被调用的函数同名,二是其声明在调用点可见。
2、考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数(viable function)。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。
3、寻找最佳匹配(如果存在的话),从可行函数中选择与本次调用最匹配的函数。原则是实参类型与形参类型越接近,匹配的越好。含有多个形参的函数匹配情况比较复杂,如果出现:
- 该函数每个实参的匹配都不劣于其他可行函数需要的匹配。
- 至少有一个实参的匹配优于其他可行函数提供的匹配。
如果在检查了所有实参之后没有一个函数“脱颖而出”,则该调用是二义性调用。
实参类型转换优先级
1、精确匹配,包括以下情况:
- 实参类型和形参类型相同
- 实参从数组类型或函数类型转换成相应的指针类型
- 向实参添加顶层const或者从实参中删除顶层const
2、通过const转换实现的匹配
3、通过类型提升实现的匹配
4、通过算术类型转换或指针转换实现的匹配
5、通过类类型转换实现的匹配
函数指针
函数指针声明和定义
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
该函数的类型是bool(const string &, const string &)。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可:
函数指针的使用
当我们将函数名作为一个值使用时,该函数自动地转换成指针。
此外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针。
在指向不同函数类型的指针间不存在转换规则,必须精确匹配
函数指针形参
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上确实当成指针使用: