高程整理6-函数
6.函数
基本
- 定义无参函数 括号里面除了空着,也可以加一个
void:
1 | int fun1(void) |
-
函数返回值类型缺省,在C语言里缺省默认为int(不建议缺省),在C++里报错。
-
ANSI C++要求main函数只能返回int类型。不管在哪里,都建议将int作为唯一的main函数返回类型。
-
形参:函数定义实现那里出现的参数;实参:调用函数时填到括号里的参数。
-
调用函数只会把实参复制一份给形参,形参怎么搞都不影响实参。“单向传值”,“只读“。
-
形参在调用时刻申请内存,函数结束时刻释放内存。
-
实形参类型不一致,进行以形参为目标的隐式类型转换。可能导致错误。
-
返回值类型和函数定义的返回类型不一致,进行以函数定义返回类型为目标的隐式类型转换。可能导致错误。
-
返回类型
void的函数,可以写return;,也可以不写,其他返回类型一般必须写。 -
如果函数定义中有分支、循环导向return语句的,如果没有覆盖全部分支出口(无论从表达式上看是否已经覆盖)都会报警告。
-
函数的定义在调用后面,则需要在调用前声明。括号里一定要包含形参类型,形参的名字不一定要一样,甚至不一定需要形参名。
-
不建议
max(max(n1,n2),n3)的方式用2个参数的最大值函数求3个值的最大值,在反复求最大值的场景下,性能损耗较大,调用函数的过程比较复杂。
递归函数
- 递归的思想:分治,不用考虑整个问题的细节,只需要考虑递推关系,即n-1和n的关系。
- 汉诺塔的算法:必须使用递归思想。三根柱子ABC,现在A上有n个盘,要移到B上。递归的逻辑:
- 将【A】上的【n】个盘移到【B】上,【C】作为辅助:
- 将【A】上的【n-1】个盘移到【C】上,【B】作为辅助
- 将A上的最大盘移到B上
- 将【C】上的【n-1】个盘移到【B】上,【A】作为辅助
假设n-1的事情已经有人干好了,本轮递归只需要负责好n-1到n的部分即可。其中的第1和3步的逻辑也是一样,也会再往上找。
- 递归不设置终止条件是一种错误的用法。
- 递归的数量也是有限的,递归套太多层会导致栈溢出。
- 能套几层也跟函数体内声明多少变量有关。程序会准备一个系统栈,栈大小固定,递归就会把函数的现场状态入栈,变量多那么一次存的东西也多,能套的层数就少。这个东西的表现和不同的编译器,不同的平台都有关系。
内部外部函数
内部外部函数,和静态全局变量和外部全局变量的关系是差不多的,只是外部函数的话,在其他的源文件里,直接声明,不用加extern就可以使用(当然去写一个extern也不算错)。内部函数就是普通函数加个static,就只有这个源文件可以使用这个函数了。声明内部函数的时候,必须加static,实现处可以不加(但建议都加上)。
重载函数
overload:一个函数名字承载了多个具体的实现方式。常用于参数表不一样但是要去实现同一个概念的功能。仅C++支持,C不行。
直接定义多个同名函数,就可以实现重载。重载要求你定义的这些函数,必须在参数类型和参数数量上要有不同,如果都相同,即使你返回类型不一样,也是不行的。
系统查找的顺序:
- 先匹配参数类型、参数数量完全一致的函数
- 匹配能通过系统提供的转换,来达到参数类型、参数数量完全一致的函数
比如说:
1 | void fun(int a, int b) |
- 匹配能通过用户提供的转换,来达到参数类型、参数数量完全一致的函数
暂时没有具体的例子,应该是可以自己定义类似于long->int这种转换的吧
匹配成功则不继续匹配,匹配不到就报错
函数参数的默认值
可以让函数的部分形参拥有默认参数,调用的时候不输就用默认值。这些形参必须排在参数表的最后。int fun(int a = 0, int b)不行。
void fun(int a, int b = 10, int c = 20)这个函数,可以fun(1);,可以fun(1,2);,可以fun(1,2,3);
对于函数声明,函数声明里必须把默认值写出。然后函数实现处不能再写默认值,直接按照普通形参定义。否则会说你重复定义默认值。
使用默认值的最佳实践:用于一个有潜在扩充参数需求的东西上面。比如一个事情,本来你觉得用2个信息就可以,其他的3个信息定死了就行了。此时你就可以让3个形参拥有默认值,实现的时候按照5个变量执行。这样以后万一那3个信息要改,那在函数调用后面多个参数就好了,不需要整个函数都去改。
有默认参数的函数可能会和重载函数产生冲突。比如
1 | int fun(int a, int b = 10); |