一、函数的声明和定义
1、像下面这种把函数定义在主函数的后面,编译器可能会报出警告。如下:
原因:编译器一行一行的扫描代码,当扫描到调用Add函数时,在前面并没有发现有Add函数的定义,尽管在之后扫描到了Add函数,但前面编译器依然进行了报错。
上面报错的解决办法:1、将函数定义在主函数的上面。 2、对Add函数进行声明。(如下)
注意:函数的声明只是告诉编译器这个函数叫什么,但这个函数存不存与函数的定义有关。
来用模块化写一个减法的实现。在使用模块化来写减法函数前,我们需要创建一个源文件(.c)和头文件(.h)。我们需要在源文件中完成减法函数的实现,在头文件中进行减法函数的声明。但我们在 test.c 中使用减法函数时,只需引出减法函数的头文件(#include "sub.h")即可使用减法函数。引减法函数的头文件就是拷贝其 sub.h 中的内容(即 减法函数的声明)。
2、在初识C语言中,我们常常喜欢在一个 .c文件中完成我们所需的工程,但在公司中,我们会通过模块化来完成我们的工程。
1.我们需要多人协作来完成一个工程。
2.模块化可以使我们的代码容易阅读以及更加容易维护。
3.了解一下静态库
程序员A做了一个游戏打算卖给B公司,A 说每年给他多少**元,程序的维护可以交给他。就把游戏卖给了B公司,但如果A把源代码也给了B公司,那么B公司可能找几个程序员仿造A写了类似的游戏。所以为了保护A的权益,我们可以将代码编译成静态库,将静态库和头文件一同卖给B公司,这样B公司就可以根据头文件知道如何使用代码,而不清楚代码的实现是怎样的。
实现一个加法的静态库。静态库文件打开为乱码。
以上就是我们为什么要将函数的定义与声明分开写。打包卖就更方便。
二、函数的递归
1、接受一个整型值(无符号),按照顺序打印他的每一位。例如:输入:1234 打印:1 2 3 4
这里我们想要打印结果为1 2 3 4,只需将下面代码进行存储起来,然后依次打印即可。
利用数组来存储数据,然后再将他们依次打印出来,这样也能实现我们想要的效果。
但我们有没有更巧妙的方法呢?这里不妨让我们用递归试一试。代码如下:
print函数的作用就是将问题简化,我们的目的是打印1234的每一位,但我们发现4是比较容易打印出来的,所以我们可以先按顺序打印123,然后再打印4。
print(123) 4 --> print(12) 3 4 --> print(1) 2 3 4 ,我们发现当打印到1的时候就不需要拆分了,所以限制条件就是n>9。
这里可以体会函数递归通过一层一层递延,在一层一层回归的过程。
如果我们将print函数里面的限定条件给去了(即:if(n>9)或将print函数里面print(n/10)改为print(n)),这时函数里面会一直调用print函数,从而导致栈溢出。栈溢出问题会在下一章博客中补充。
注意:递归一定不能是死递归,写函数递归需要满足两个必要条件:
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
2、编写函数不允许创建零时变量,求字符串长度
上面的代码我们使用了临时变量,现在来让我们试试通过递归的形式不用临时变量来写求字符串长度。代码如下:
让我们来想下是否能简化问题,我们需要求“abc” 的长度,当第一个字符不等于 ‘\0’,那我们可以将问题简化为 1 + my_strlen("bc") --> 1 + 1 + my_strlen("c") -->1 + 1 + 1 +my_strlen("") --> 1 + 1 + 1 + 0 ,最终,我们就可以实现求字符串的长度。
这个函数递归依旧符合递归的两个必要条件。
三、函数的迭代与递归
循环是迭代,迭代不一定是循环。
1、求n的阶乘
用递归的方式
用迭代的方式(非递归)
思考:那么以后我们解决问题是否都可以用递归来解决呢?
来让我们看看下面的代码。
函数递归的方式来求斐波那契数
当我们求第50个数时由于计算量非常大,我们可以改为求第40个数,依旧可以发现计算量很大,那么让我们来看看3这个数被计算了多少次
由下面代码可见,用函数递归的方法求斐波那契数是非常低效率的。当我们求第40个数,第3个数被重复大量计算。
让我们再用迭代来尝试一下吧。我们只需要创建三个变量就能求第n个斐波那契数。
从上面的代码可以看出,非递归的方式要快很多,那么我们什么时候改用递归解决问题,什么时候改用迭代解决问题呢?
当我们用递归写代码时发现非常简单,且写出的代码没有明显的缺陷,这时我们就用递归来写代码。如果我们用递归写发现虽然简单,但有明显的问题,比如上面的斐波那契数,那么这时我们不应该使用递归,而用迭代写代码。
3、并非死递归才会栈溢出
递归如果层次太深就有可能出现栈溢出的情况,所以我们需要注意