欢迎光临
我们一直在努力

新手学C语言: 第8天 函数递归与指针

前言:

今天过生日喵,开心喵,所以今天多学了一点。

正文:

什么是函数递归:

函数在其定义内部调用自身,把一个复杂的问题简化,拆分成多个小问题,直到不能再分,这就是函数的递归。

递归的核心要素:

递归总共有两个核心要素:
1. 终止条件,也就是在什么情况下不再进行递归调用并返回结果。如果没有终止条件,便会导致死递归,进而导致栈溢出。

2.递归调用:函数自己调用自己时,每次问题都应该逐渐被拆分,逐步接近终止条件。

那么到底什么是递归呢?用最简洁直白的方式来讲,可以理解为高中里面的数列,函数调用的参数前一项与后一项存在着某种固定的关系,就是我们数列里面学的递推公式,递推可以用来求阶乘、求斐波那契数,总体而言是偏向于数学方面的用法。

递归与循环:

递归与循环很相似,都是为了将问题细化成更多小问题,方便解决。

递归的优点:代码内容简洁,逻辑较为清晰

递归也有缺点,函数调用,有可能会重复计算,并且存在栈溢出的风险

循环的优点则是效率更高,而且相比于递归,就我个人的做题经验而言,循环是更容易想到的,也是写起来更加容易的。并且循环没有栈溢出的风险,但循环的问题是要有循环开始的条件、内部条件的更新和循环的范围。总而言之,循环的语法结构相较于递归比较复杂。

在处理一个复杂问题时,循环很容易写下一大片臃肿的嵌套,而递归处理起来则简洁很多。我们在选择的时候应该扬长避短。如果这个问题的深度不是太深,便可以选择递归,既避免了栈溢出的风险,又防止消耗太多的内存。但是如果这个问题有很清晰的逻辑,而且较为复杂、有很多条件,那就推荐用循环。虽然循环较为臃肿,但在写的时候总体上逻辑是清晰的,也比递归更加容易想到。

比如像最经典的一个例子,计算斐波那契数,所谓斐波那契数,就是从第二项开始,后一项等于前两项之和,在计算斐波那契数时,一般便推荐使用循环,因为它的计算有着较多的限制条件,如果使用递归的话,再计算第40个左右的斐波那契数时,计算次数就能有上千万次。

结构如下:

int fb(int n)

{

int a=0,b=1,c=0;

if(n==0)

{return 0;}

if(n==1)

{return 1;}

for(int i=2;i<=n;i++)

{c=a+b;

a=b;

b=c;}

return c;

}

栈溢出:

每次函数调用时,都会在内存的栈区申请一块空间。当递归的层次太深时,申请出的空间被耗尽,程序便会崩溃,这就是栈溢出。

如何避免栈溢出呢?
1. 确保有终止条件
2. 控制递归的深度
3. 当递归次数过多时,考虑用循环。

指针的核心知识:

终于学到指针了,相信对于每个刚开始学C语言的朋友,不管你有没有开始,在开始前或开始后,都或多或少会听人说过指针这一块是 C 语言的分水岭,根据我学习时的体验来看,确实如此,自我学 C 语言以来,除了设计复杂项目之外,最难理解、最难应用的便是指针。

内存与地址:

在我们使用计算机时,CPU 处理数据时需要从内存中进行读取。内存被划分为了一个个内存单元,每个单元的储存大小为 1 个字节,每个内存单元都有 1 个编号,这个编号便是地址。而在 C 语言中,地址有了 1 个新的名字,便叫做指针。

字节这个词相信大家不陌生,抖音的母公司便叫做字节跳动,一个字节的内容是多少呢?一个字节等于8个比特,也就是8个二进制单位。

指针变量:

接下来这部分知识便是较为难理解的,很关键的部分了。

首先我们先引入两个操作符,分别是取地址操作符和解引用操作符:&   *

取地址操作符相信大家是不陌生的,前面学习scanf函数时便用到了这个内容,解引用操作符则相对难理解一些,毕竟在中文语境中,“解引用”这个词实际上并没有能从字面上显而易见的意思。

解引用操作符与取地址操作符两者在逻辑上互为逆运算符。这里我查了一些资料,发现解引用操作符还有另外一个名字,叫做 Indirection Operator,间接寻址操作符。

一个指针变量该怎么声明呢?

int a=10

int*pa=&a

在有解引用操作符时,pa 便表示一个指针变量,int 则表示 pa 指向的是 int 类型的对象。

假设我们现在有一个指针变量,就叫它 a,那么你可以有两种状态,第一直接用指针A操作的便是指针本身,第二便是解引用指针 a,操作的便是指针所指向的东西。

假设我们是一个快递员,指针就像是一个快递单,如果我们直接操作指针的话,我们可以做什么呢?可以把快递单上的信息涂抹改掉,也可以把这张单子撕掉,重新建一张新的单子所以说,当我们不加解引用操作符时,使用指针变量 p 操作的便是指针本身,可以决定指针指向哪里,但是你无法决定指针所指向的那个位置里面有什么。就像你可以拿着快递单确定快递送往哪里,但是你却不能确定快递单所指向的那户人家中有什么。

解引用状态的指针变量,便是按照快递单上的地址找到那户人家,打开门,这时你便可以操作家中的事物,这时候我们所进行的操作,便是针对所取地址的那个变量中储存的东西,之前我们在讲变量的时候说过,变量本身就像是一个盒子,解引用过后的指针变量便可以让你操作盒子内部的东西

那么为什么一定要解引用呢?因为指针变量和解引用的指针变量在编译器里是两种不同的类型,指针变量本身是一个地址,而解引用的指针变量属于整数类型或是其他的类型,所以同样的操作对两者有着不同的作用。

取地址操作符就好比是一个求导,那么解引用操作符便是一个积分

所以说  *(&a)=a

如果实在理解不了,也可以把它当做负负得正

这里补充一个冷门知识点,我们在打印 sizeof 的结果时,常常会用 %zu 代表无符号的整形(不同的平台规定不同,可能是 unsigned long,也可能是 unsigned int)但是如果我们要打印指针的加减结果,需要使用 %zd,也就是有符号的。

指针的类型:

声明指针的类型对我们而言有什么意义呢?主要是在以下两个应用方面体现:
一可以决定解引用的时候的范围,指针的类型决定了对指针解引用时一次可以操作几个字节。第二个方面来讲,指针类型决定了指针向前或向后走一步有多大距离,也就是指针加减整数的步长。

指针运算:

指针运算主要有以下几个种类:
1. 指针与整数之间的运算
2. 指针与指针之间的运算
3. 指针的关系运算

对于一个解引用时的指针,如果我们对于它加上或者减掉一个整数n,便意味着指针会指向向前或向后的第 n 个元素

对于两个指针指向同一块内存空间的情况,如果两个指针相减,最后的结果便是两个指针所指元素位置差,也就是两个指针之间的元素个数

指针的关系运算则是指可以通过指针来遍历数组等

传值调用与传址调用:

常见的传值调用便是数值之间的拷贝,比如像形参与实参。这里需要重提一嘴,形参是实参的一份临时拷贝,修改形参并不影响实参。但是如果使用传值调用便可以。因为传递的是地址,函数通过地址直接操作主调函数中的变量

二级指针:

目前我接触到的内容还不知道这玩意儿有什么用,具体来讲可以理解为指针的嵌套。指针变量本身也是变量,也存在地址,存放指针变量的地址的变量就是二级指针。

总结:

今天学习了函数递归与指针的知识,知识和定义很多,不过应用却不太多,具体的应用应该得等到后面的结构体和链表,单独是今天学的内容,还无法发挥指针的大部分作用。

赞(0)
未经允许不得转载:171主机测评 » 新手学C语言: 第8天 函数递归与指针
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址