C语言笔记初级篇第三章:函数与递归

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言笔记初级篇第三章:函数与递归相关的知识,希望对你有一定的参考价值。

第三章:函数

(1)函数是什么

在计算机科学中,子程序是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。

C语言中的函数分为库函数自定义函数

(2)库函数

A:什么是库函数

我们知道,在学习C语言时,为了检验成果,我们总是会利用的“printf”这样一个函数将结果打印到屏幕上,其实在C语言刚刚出现时,要想实现屏幕打印效果,是要自己定义一个打印函数的,这样效率就显得十分低下。所以在以后的C语言中,为了统一规定以及提高效率,会提供一些常用的,公用的函数以供我们使用。比如printf函数就是在标准输入输出这个库函数下的一个函数

B:如何学习库函数

第一点:学习库函数的网站

学习库函数的网站:Reference - C++ Reference

第二点:如何学习库函数

学习库函数当然不是要全部把库函数记住,而是在编程过程中如果需要某种需求,进而联想到是否库函数中有能实现这种功能的函数。比如我么在操作字符串时,需要有一个字符串复制的需求,那么我们就去联想字符串是str,复制是copy,而正好库函数中有strcpy这样一个函数能满足我们的需求。现在就利用上面的网站,去演示如何阅读这样的手册

了解完之后,我们就可以写出代码了

(3)自定义函数

A:自定义函数

库函数是不可能实现我们的全部需求的,所以很多时候需要我们自己去定义函数,实现某些功能

B:函数的组成

C:定义自己的函数

(4)函数的参数

A:引例:为什么不能交换成功

第一点:为什么不可以交换

如下:利用前面说过的自定义函数,写一个交换两个数的函数,并调用它

接下来:用调试的方法解释为什么没有交换成功

第二点:正确的交换方法

既然把值传到函数后,函数自己开辟了自己的空间去操作自己的变量,导致了交换不成功。而之前我们说过利用指针可以直接操作内存,所以我们可以调用函数直接操作变量的内存,进而类似于一种“远程操控”。

仍然用调试的方法解释

B:形参和实参

实参:也即实际参数,是真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

形参:也即形式参数,形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

综上,这样就完完全全揭示了为什么第一次的交换不能成功:形参实例化之后其实相当于实参的一份临时拷贝

(5)函数的调用

A:传值调用

就像第一次的交换函数,函数的形参和实参分别占有不同的内存块,对于形参的修改不会影响到实参

B:传址调用

就像第二次的交换函数,将地址传递给函数,使得函数内部可以直接操纵函数外的变量

C:何时采用传值,何时采用传地址

如果不改变变量,就像要求两个数的最大值,我只需让函数返回谁大,并不去改变他们,那么就采用传值;如果要改变变量,就像第二次交换函数,a和b的值是互换了,那么就需要传地址。

(6)函数的嵌套调用和链式访问

A:函数的嵌套调用

函数的嵌套调用说白了就是你调用我,我调用它

B:链式访问

链式访问就是把一个函数的返回值作为另一个函数的参数

这里要特别注意下面一个有趣的打印

(7)函数的声明和定义

A:函数的声明和定义

函数声明:告诉编译器我有一个函数,参数是什么,返回类型是什么,但是是否存在无关紧要。函数的声要先声明后使用,一般函数的声明要放在头文件中。

函数定义:函数的定义是指函数的具体实现

B:函数的声明和定义的真正用途

这样在一个文件里,先声明后定义的方式,纯属脱裤子放屁

真正的使用方式,上述中调用的加法要放在其他模块里,这个模块包括一个头文件,用于声明函数;一个源文件,用于实现函数的功能,最后在主文件导入头文件后调用

初学者不易理解这样做的目的。举个例子,要完成一个大的工程,比如说要写出一个计算器,5个人完成,但是5个人不可能在一个文件里同时写。所以就将任务发布下去,一个人写加法,一个人写减法,等等,最后把所有集合起来,就完成了这样一项工作。

(8)递归

A:递归初识

第一点:何为递归

程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 递归的主要思考方式在于:把大事化小

第二点:最简单的递归

第三点:递归会遇到的问题

像上面的那个递归,如果无休无止的下去,有一刻,程序会抛出错误“stackflow”,中文名意味栈溢出。我们的内存空间被划分为栈区,堆区,静态区,其中堆区是用来存放静态变量,函数的,每一次递归就相当于每一次向内存申请空间,总有一颗,堆区耗尽,抛出错误。

B:案例说明

第一:接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4

代码如下

初学者,在理解这段代码时是有一定困难,先通过画图,来辅助理解

以输入123为例:当调用n=1时,发现不满足条件,这个递归的函数结束,返回上一递归的函数,上一递归的函数就是将n=12传递给它的那个函数,当返回过去,调用执行完毕,if判断完毕,n=12,接着执行下一步输出,就输出了12%10=2,依次类推。

也可以这样说递归其实保存了一个状态,当被递归的函数结束后,返回到上一次层函数时,所有的状态就回到了那一层函数时的状态。

第二:不允许使用库函数,使用递归求字符串长度

先用普通的方式实现,其实也就是“strlen”库函数的实现原理,代码如下

这里需要提前说明的是,将数组传递函数,传进去的是该数组首元素的地址,所以形参是一个指针

接着使用递归的方式,代码如下

递归讲求一个大事化小的原则,试想如果字符串里什么都没有,只有结束标记,那么直接返回没有即可。如果指针扫描到的不是结束标记,那么就表示字符串长度起码为1,那么它的字符长度应该是1加上指针移动到下一个元素上并调用该函数的返回值,也就是递归

第三:使用递归的方式求n的阶乘

第四:求第n个斐波那契数


long long Fibonacci_Solution1(unsigned int n)

    if(n <= 0)
        return 0;
 
    if(n == 1)
        return 1;
 
    return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2);

C:递归的大问题

在斐波那契数列那个案例中,当求前几个数列时速度很快,但随输入的数字越大,我们发现计算机算的很慢,有时连人工的速度都比不上。

可以发现在算数列时,每次递归,有很多步骤都是重复的,这些函数之间似乎不联系,有些数据不公用,就是我算我的,我完成我的任务,所以这也是递归很大的一个问题

为了验证这一点,我们定义一个全局变量count,计算第40个斐波那契数,我们想看一下,计算第十40斐波那契数的时候,第3个斐波那契数被重复计算了几次

可以发现count达到了惊人的三千多万次,也就说明其实这三千多万次是没必要重复计算的,更何况这只是第三次的。

D:问题的解决方案

上述斐波那契数列是可以写成非递归的方式的

以上是关于C语言笔记初级篇第三章:函数与递归的主要内容,如果未能解决你的问题,请参考以下文章

C语言笔记初级篇第二章:分支与循环

C语言笔记初级篇第二章:分支与循环

C语言笔记初级篇第四章:数组入门

C语言笔记初级篇第四章:数组入门

C语言笔记初级篇第一章:C语言快速入门及注意事项

C语言笔记初级篇第一章:C语言快速入门及注意事项