第六章 函数

版权声明:转载请注明出处 https://blog.csdn.net/weixin_39918693/article/details/86564201


一、函数基础

函数调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数,此时,主调函数的执行被暂时中断,被调函数开始执行

return语句也完成两项工作:一是返回return语句中的值,二是将控制权从被调函数转移回主调函数。函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分

实参是形参的初始值。尽管实参和形参之间存在对应关系,但是并没有规定实参的求值顺序

实参的数量一定要与形参的数量一致,类型不一定要精确匹配

使用关键字void显式表示函数没有形参

任意两个形参不能同名,而且函数最外层作用域中的局部变量也不能使用与函数形参一样的名字

注意理解函数最外层作用域

形参通常不命名以表示在函数体内不会使用它,是否设置未命名的形参并不影响调用时提供的实参数量。即使某个形参不被函数使用,也必须为它提供一个实参

void表示函数不返回任何值。函数的返回类型不能是数组类型、函数类型,但是可以返回指针和引用

函数不能返回数组类型和函数类型

名字有作用域,对象有生命周期。局部变量会隐藏在外层作用域中同名的其他所有声明

内置类型的未初始化局部变量将产生未定义的值

局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响

如果局部静态变量没有显式的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0

函数名字必须在使用之前声明,函数只能定义一次,但可以声明多次

函数的声明不包含函数体,所以也就无需形参的名字。但是建议写上形参的名字。

函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也称作函数原型

我们建议在头文件中进行声明,在源文件中进行定义(变量和函数都是)

定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义和声明是否匹配(没有别的用处了吗????????)


二、参数传递

当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用。当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用

**引用传递或值传递

在C++中建议使用引用来代替指针

如果函数无须改变引用形参的值,最好将其声明为对常量的引用,底层const

拷贝大的对象比较低效,而且IO类型不能拷贝,所以建议使用引用

一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径

当形参有底层const时,传给它常量对象或者非常量对象都是可以的

函数形参的顶层const的差别不会构成函数重载

尽量使用对常量的引用,底层const的引用

数组:不允许拷贝数组、使用数组时通常会将其转换成指针

const int *
const int[]
const int[可以指定维度] 这里维度不一定要完全匹配
三者等价

数组引用形参的维度都必须完全匹配

数组第二维及以后的维度大小都是数组类型的一部分,不能省略p196

我们有时确实需要给main传递实参,一种常见的情况是用户通过设置一组选项来确定函数所要执行的操作

int main(int argc,char **argv)
第二个形参是一个数组,其元素是指向C风格字符串的指针;第一个形参argc表示数组中字符串指针的数量。argv的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0

为了编写能处理不同数量实参的函数,C++11提供了两种方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板

C++还有一种特殊的形参类型(即省略符),可以用它传递可变数量的实参。这种功能一般只用于与C函数交互的接口程序

initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。其定义在同名头文件中。如果想向其传递一个值的序列,必须使用花括号

initializer_list提供的操作中,赋值或拷贝是共享元素的。其中的元素是常量,无法改变。该类型对象中的序列可以用范围for循环来遍历

共享元素*

省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs的C标准库功能。通常,省略符形参不应该用于其他目的。省略符形参应该仅仅用于C和C++通用的类型p199

*注意省略符形参的使用场合


三、返回类型和return语句

return语句终止当前正在执行的函数并将控制权返回到主调函数中

即使返回值是void的函数,也可以用return语句来显式结束,这样增加程序可读性

return语句返回值的类型必须与函数的返回类型相同,或者能隐式的转换成函数的返回类型。 相容

保证具有返回类型的函数只能通过一条有效的return语句退出

函数返回的值要用于初始化调用点的一个临时量,该临时量就是函数调用的结果

函数调用点的临时量*

不要返回局部对象的引用或指针

调用运算符的优先级与点运算符和箭头运算符相同,并且也符合左结合律(解引用运算符优先级低一点)

调用一个返回引用的函数得到的是左值,其他返回类型得到右值

列表初始化返回值,如果列表为空,则执行值初始化

main函数的返回值可以看作是状态指示器,返回0表示执行成功,返回其他值表示执行失败。其中非0值的具体含义依机器而定。为了使返回值与机器无关,cstdlib头文件定义了两个预处理变量,我们可以使用这两个变量分别表示成功与失败。EXIT_FAILURE和EXIT_SUCCESS,通过exit函数来使用这两个变量

main函数不能调用它自己

声明一个返回数组指针的函数**

声明一个返回数组指针的函数,我们可以使用类型别名来减小声明的难度。也可以强行声明

type (*function(parameter_list))[dimension]
注意理解

也可以使用尾置返回类型来声明。尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表后面,我们在本应该出现返回类型的地方放置一个auto
例如 auto func(int i)-> int(*)[10]

decltype并不会将数组转化为指针类型,对数组名使用decltype得出的是数组类型(其中内含有维度信息)。


四、函数重载

如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数

同一作用域内*

在调用重载函数的时候,编译器会根据传递的实参类型推断想要调用的是哪个函数

main函数不能重载

对于重载的函数来说,他们应该在形参的数量或形参的类型上有所不同(只有返回类型不同是不行的)

形参的数量或类型

在函数重载中,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来

当传递给函数一个非常量对象或者是指向非常量对象的指针时,编译器会优先选用非常量版本的函数

只有在函数进行非常相似的操作时,才适合进行函数的重载

const_cast可以改变变量的底层const属性(可加可减),其常用于函数重载p209

*底层const属性

函数匹配:把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫函数确定

调用重载函数有三种可能结果:最佳匹配、无匹配、二义性调用

不要将函数声明于局部作用域

在内层作用域中声明的名字,它将隐藏外层作用域中声明的同名实体,在内层进行函数调用的时候,外层的同名的函数声明根本就不会被考虑。在不同的作用域中无法重载函数名

在C++语言中,名字查找发生在类型检查之前

名字查找和类型检查


五、特殊用途语言特性

默认实参:一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值

如果想在调用时使用默认实参,只要在调用函数的时候省略该实参就可以了

函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参

当设计含有默认实参的函数时,尽量让不怎么使用默认值的参数出现在前面,而让那些经常使用默认值的形参出现在后面

通常应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。通常来说函数只声明一次,但是也可多次声明。但是要注意,在给定作用域中一个形参只能被赋予一个默认实参。后续的声明只能为之前没有默认值的形参添加默认实参。声明中给予默认实参的时候,可以不含形参的标识符

这部分需要看具体的例子

局部变量不能作为默认实参。用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时。(注意理解)p213

注意上述的意思

内联函数可避免函数调用的开销。将函数指定为内联函数,通常就是将它在每个调用点上“内联的”展开。内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。内联机制用于优化规模较小、流程直接、频繁调用的函数

*只是一个请求

constexpr函数是指能用于常量表达式的函数。constexpr函数定义要遵循以下约定:函数的返回类型及所有形参的类型都得是字面值类型(算术类型、指针、引用等),而且函数体中必须有且只有一条return语句。注意constexpr函数的定义格式。constexpr函数体内也是可以包含其他语句的,只要这些语句在运行时不执行任何操作就行(例如空语句、类型别名、using声明)

***constexpr函数一般只有一条语句

在执行初始化任务的时候,编译器把对constexpr函数的调用替换成其结果值,constexpr函数被隐式的指定为内联函数

constexpr函数不一定返回常量表达式,但是一般都是返回常量表达式。建议都返回常量表达式

内联函数和constexpr函数可以在程序中多次定义。但是多个定义必须完全一致,所以,建议内联函数和constexpr函数通常定义在头文件中

*建议将内联函数和constexpr函数定义在头文件中

程序可以包含一些用于调试的代码,这些代码只在开发时使用,当应用程序编写完成准备发布时,要先屏蔽掉调试代码。这时需要用到两项预处理功能:assert和NDEGUG

预处理宏其实是一个预处理变量。assert宏使用一个表达式作为它的条件。如果表达式为假,assert输出信息并终止程序的执行。如果表达式为真,assert什么也不做。assert宏定义在cassert头文件中

预处理宏名和预处理变量名在程序内必须唯一。所以不要为了其他目的使用assert。因为cassert有可能已经通过其他头文件包含在程序中了

assert宏常用于检查“不能发生”的条件

assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。我们使用#define定义NDEBUG

assert应该仅用于检验那些确实不可能发生的事情。我们可以把assert当成调试程序的一种辅助手段,但是不能用它替代真正的运行时逻辑检查,也不能替代程序本身应该包含的错误检查

#ifndef、#ifdef、#endif也可以用在源文件中

C++编译器定义了__func__:表示当前调试的函数的名字,其是一个const char的静态数组

预处理器定义了另外4个对于程序调试很有用的名字:

  • 1、FILE:存放文件名的字符串字面值
  • 2、LINE:存放当前行号的整型字面值
  • 3、TIME:存放文件编译时间的字符串字面值
  • 4、DATE:存放文件编译日期的字符串字面值

六、函数匹配

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数。候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见

重载函数集:候选函数*

第二步考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型

可行函数

如果函数含有默认实参,则我们在调用该函数时传入的实参数量可能少于它实际使用的实参数量

如果没有找到可行函数,编译器将报告无匹配函数的错误

函数匹配的第三步是从可行函数中选择与本次调用最匹配的函数。它的基本思想是:实参类型于形参类型越接近,他们匹配的越好

在进行多个参数的函数匹配的时候,如果有且只有一个函数满足下列条件,则匹配成功:一是该函数每个实参的匹配都不劣于其他可行函数需要的匹配。二是至少有一个实参的匹配优于其他可行函数提供的匹配。如果没有一个函数脱颖而出,那么就会二义性调用

调用重载函数时应尽量避免强制类型转换(如果一定需要强制类型转换,那么只能说明你的程序设计不合理)

为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级如下:

  • 1、精确匹配:类型完全相同、实参从数组或函数类型向其对应的指针类型转换、对实参进行添加或删除顶层const
  • 2、通过const转换实现的匹配(一般是非底层const向底层const的转换)
  • 3、通过类型提升实现的匹配
  • 4、通过算术类型转换或指针转换实现的匹配
  • 5、通过类类型转换实现的匹配

类型提升和算术类型转换的匹配:所有算术类型转换的级别都一样。实参会自动提升到合适的类型,然后再初始化形参。

如果重载函数的区别在于他们的引用类型的形参是否引用了const,或者指针类型的形参是否指向了const,则当调用发生时编译器通过实参是否是常量来决定选择哪个函数(并不是实参本身是否是常量,你懂的!)

括号中的注释错了*


七、函数指针

函数类型由它的返回类型和形参类型共同决定,与函数名无关。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可

当我们把函数名作为一个值使用时,该函数名自动的转换为指针
pf = 函数名 和 pf = &函数名 等价

我们也可以使用指向函数的指针调用该函数,无需提前解引用指针(也可以解引用)

在指向不同函数类型的指针间不存在转换规则。函数指针可以为nullptr或0(表示未指向任何一个函数)

函数和函数指针之间需要精确匹配

虽然不能定义函数类型的形参(其实可以,其会被自动转化为函数指针),但是形参可以是指向函数的指针。我们可以直接将函数作为实参使用,它会自动转换为指针

我们可以通过typedef和decltype定义函数类型和函数指针类型,decltype不会将数组和函数转化为其所对应的指针类型

typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2;
Func和Func2都是函数类型,可以直接使用,函数类型会被直接转化为函数指针类型
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;
FuncP和FuncP2都是函数指针类型

我们必须把返回类型写成指针形式,编译器不会自动将函数返回类型当成对应的函数指针类型处理。使用类型别名来写会比较容易一点,也可以强行写,也可以使用尾置返回类型。和返回数组指针非常类似。p223

auto(没有例子)和decltype的使用方式和返回数组指针的用法非常类似

猜你喜欢

转载自blog.csdn.net/weixin_39918693/article/details/86564201