今天来总结一下函数的相关基础知识点,希望对各位能有所帮助,也借此完善一下自己的知识库。博主刚写博客,水平有限,望各位海涵。
目录
函数的定义和声明
什么是函数的定义和声明呢?这是我们应该首先要去理解的问题。
函数的定义:简单来说就是说明函数是如何实现的。
函数的声明:具体就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
注意事项:1.函数的定义可以放在使用后,而函数的声明必须放在使用前
2.先定义可以不用声明,后定义需要先声明
函数的分类
总体来说分为库函数和自定义函数。
1.为什么要有库函数呢?库函数是前人把一些常用的函数总结打包好了,不用我们每次编程时都要重复敲一段代码去实现一个常用函数,节省了时间。常见的库函数有scanf,printf,getchar......,使用库函数之前记得引用头文件,编译器才不会报错。常用的库函数学习网站有MSDN,Cplusplus,英文不好的可以用翻译软件阅读一下,相关介绍十分详细。
2.自定义函数则可以实现任何功能,如果我们能力够的话。虽然自定义函数的功能很多,但有很多注意事项,因为从函数的参数类型(包括实参和形参),返回类型,函数的定义声明都要自己实现。下图由一个基础的加法函数为例,看看函数是如何进行定义的
此处应该注意,z变量是在函数内被定义的,所以等函数调用完后,z变量就会被销毁,相当于一次性用品。
那我们接下来看看一整套自定义函数的使用流程
这是一个返回最大值的函数,返回类型为int,所以用一个max变量接收最大值,然后进行打印。
这里要注意的应该是a,b和x,y的关系。
a,b是实参,就是你要传进函数的参数。而x,y为形参。
我想让a,b进行比较得到最大值,所以在调用符号——()里写a,b。然后由x接受a的值,y接受b的值,这里的x相当于a,y相当于b。这就是传参的过程,具体传参稍后再讲。
注意事项:实参必须有确定的值,以便把这些值传送给形参;形参实例化之后其实相当于实参的一份临时拷贝,会开辟相应空间。
函数的传参
传参具体分为传值和传址。接下来由两段代码来展示何为传值和传址。
这是之前的得到最大值函数,为传值
这是交换两个整形内容的函数,为传址。
传址就是传的地址,所以形参接受地址时就理所应该要有指针去接收,具体是什么类型的指针呢,就要看实参的类型了。
读到这里可能道理都懂了,但往深了想,为什么有传参和传址的区别呢,统一一个传参方法不是更加方便吗?
首先通过上面两个代码,我们不难发现传值调用对形参的修改不会影响实参。而传址调用对形参的修改会影响实参。所以当我们设计函数进行传参时要明确是否要改变实参。
其次我们应该大概了解一下在调用函数的时候,都会在栈区开辟一块空间(还没学栈区的可以理解为在电脑内存里开辟一块空间),栈区存放局部变量和函数形参。而形参又是实参的一份临时拷贝,所以如果实参为一个很大的数组,所占内存很大,又传给形参,那么栈区被占的空间很多,空间利用率就较低了。而传址调用不管实参的内存多大,传的都是一个地址,形参都为指针,大小为4或8个字节,也不用开辟新的空间,也省了时间。
总体来说,就是以下两点
- 传值调用对形参的修改不会影响实参。
- 传址调用可以通过地址改变实参,不会开辟新的空间,省内存
递归
简单来说就是将一个大的事件分为很多个小的且重复的动作,不断调用函数本身去实现小且重复的动作,从而将一个大且复杂的事情简单化。
使用条件:1.存在限制条件,不能死循环,否则会栈溢出
2.每次调用函数后,都会越来越接近这个限制条件
例子:求字符串的长度
如果不是‘\0’,返回1+get_len(arr+1)
arr+1其实实现了让地址循环往后走一位,如果&(arr+1)再不为'\0',再返回1+get_len(arr+1)。此时第一行的结果其实就是2+get_len(arr+2)。
以此类推直到遇到'\0'。
例子:求斐波那契数
本质上为下图
当求得的数字较大时,使用递归的方法计算机所要计算的量是相当大的,因为每次计算一个第n项时都需要计算第n-1项和第n-2项
我们通过求解第40项来观察fib(3)的计算次数来观察
由此可知计算量巨大,越到后面效率越低。
因此此处应该使用迭代求法更优——通过对a,b的反复赋值进行求解
由此可得迭代的效率更高
为什么有时候用递归简便,而有时候用迭代简便呢?
1.许多问题是以递归的形式进行求解的,这只是因为它比非递归的形式更加清晰。
2.当一个问题相当复杂时,此时递归实现的简洁性便可以弥补它所带来的运行开销。
3.有时迭代实现比递归实现效率更高,但可读性差些。