我目前正在阅读"实用C编程第三版";Steve Oualline。
目前,我正在阅读关于递归的第二章,在这一章中,作者提出了一个练习,读者必须尝试编写一个递归函数,计算一个数字在数组中出现的次数。
请考虑以下代码:
#include <stdio.h>
size_t count(const int [], size_t, int);
enum {ARRAY_LEN = 11};
int main()
{
const int num_array[ARRAY_LEN] = {1, 2, 3, 4, 5, 5, 6, 7, 3, 9, 0};
int number = 0;
puts("Type a number: (0-9)");
scanf("%d", &number);
printf("Number of appearances: %lun", count(num_array, ARRAY_LEN, number));
return 0;
}
size_t count(const int n_array[], size_t arr_len, int num)
{
return arr_len == 0 ? 0 : ( n_array[arr_len - 1] == num ) + count( n_array, arr_len - 1, num );
}
我的问题是:
C如何知道将哪个return
带回主函数?因为,在这个例子中,我们有3个返回,并且在特定的时间内,所有返回的条件都变为true,那么,C怎么知道有了return 0;
,它必须退出递归函数,而不继续在count()
函数内搜索另一个返回呢?
谢谢。
当您有递归函数调用时,您有多个"实例";一个函数处于活动状态,相互调用并返回。为了形象化这一点,我发现想象一下很有用,而不是在计算机上运行C程序,你有一群人在一个房间里,你们都给他们一套相同的指令,然后你让他们开始为你解决问题。对于你问题中的代码,它会是这样的。
你挑选一个人,然后说:;嘿,约翰,这是一个3号的数组:[1, 2, 3]
。你能告诉我2
这个数字在里面出现了多少次吗">
约翰从你手里接过阵法,然后看了看他的指示。数组的大小不是0,所以他不会告诉你0。他的数组的最后一个元素不是2
,所以他也不做第二件事。但要做第三件事,他需要进行递归调用,所以他选择房间里的其他人,然后说,";嘿,玛丽,这是一个2:[1, 2]
的数组。你能告诉我2
这个数字在里面出现了多少次吗">
玛丽从约翰那里接过了这个阵列,她看着自己的指示。数组的大小不是0,所以她没有告诉John 0。但是她的数组的最后一个元素是2
,所以她做了第二件事。但要做第二件事,她还需要进行递归调用,所以她选择房间里的其他人,然后说,";嘿,弗雷德,这是一个1:[1]
大小的数组。你能告诉我2
这个数字在里面出现了多少次吗">
弗雷德看了看他的指示。数组的大小不是0,所以他没有告诉Mary 0。他的数组的最后一个元素不是2
,所以他不做第二件事。他也需要进行递归调用来做第三件事,所以他选择房间里的其他人说,";嘿,简,这是一个0:[]
大小的数组。你能告诉我2
这个数字在里面出现了多少次吗">
Jane看了看她的数组,结果的大小是0。所以她说;则2
在该阵列中出现的次数为:0";。
这就是弗雷德一直在等待的。弗雷德正在编写第三份return
声明,所以无论他从简那里听到什么,都是他的答案。他说,";Mary,2
在该数组中出现的次数为:0";。
这就是玛丽一直在等的。但她正在编写第二个return
声明,所以她从弗雷德那里听到的任何消息都加了1。她说,";John,2
在该数组中出现的次数为:1〃;。
这就是约翰一直在等的。约翰正在编写第三份return
声明,所以无论他从玛丽那里听到什么,都是他的答案。他对你说:;CCD_ 21出现在该阵列中的次数为:1〃;。
现在你有了答案!
我曾经教过一堂C编程课,有一天我给班上的每个人发了一份讲义,里面有一些人类可读的指令,说明如何对二叉树进行递归、有序遍历。然后我递上一个";二叉树"——一块纸板,上面有一堆黄色的贴纸,排列成树状结构——给前排的一个随机学生,开始遍历。这绝对是一片混乱,但也很有趣。(我觉得。不管怎样,我觉得这很有趣!)
CPU有一个堆栈。这是一个非常重要的数据结构,用于描述执行上下文。堆栈可能如下所示:
计数(4,{10,20,30},3)计数(4,{10,99,33,22},4)计数(4,{10,99,33,22,44},5)main(1)
当CPU调用一个函数时,它会在顶部添加另一个堆栈帧(实际上,有些人或调试器会将其倒置,所以它可能在底部)。
当CPU从函数返回时,它从顶部移除堆栈帧,并从现在位于顶部的执行点继续。
如果您可视化这个堆栈,您将看到每个return
语句都没有任何明确的目的地——它是告诉CPU在哪里继续执行的堆栈。如果main
调用count
,则返回main
,但如果count
调用count
,则返回到count
。但不仅如此,它还记得返回哪个调用。
堆栈帧包含调用函数时使用的所有参数的副本,以及函数的局部变量的副本(它并不总是副本,但最简单和最重要的情况是它实际上是副本)。
对于初学者来说,带有多个返回语句的函数定义是糟糕的。
此外,函数应该像一样声明
size_t count( const int [], size_t, int );
该功能可以通过以下方式定义
size_t count( const int a[], size_t n, int num )
{
return n == 0 ? 0 : ( a[n-1] == num ) + count( a, n - 1, num );
}
也就是说,函数的返回类型应为size_t
。函数的第一个参数应具有限定符const
,因为传递给函数的数组在函数内没有更改。
总的来说,你应该写
printf( "Number of appearances: %zun", count( num_array, ARRAY_LEN, number ) );
至于你的问题,很明显,函数中的控制将传递给一个return语句,这取决于if语句中使用的条件。
如果arr_len
的值等于0,则控制将传递给该if语句中的return语句。
if(arr_len == 0)
return 0;
请注意,在函数的每次递归调用中,参数arr_len
的值都会递减。
因此,当arr_len
等于0时,这是函数的基本情况。在这种情况下,函数将停止执行。
否则,如果arr_len
不等于0,则如果数组的最后一个元素等于num
,则将下一个递归调用返回的值加1,并返回总和。否则,如果数组的最后一个元素不等于num,则返回函数的下一次递归调用返回的值。
if(n_array[arr_len - 1] == num)
return (1 + count(num, n_array, arr_len - 1));
else
return count(num, n_array, arr_len -1);
这就是递归编程的全部内容。
代码使用在if链中遇到的第一个返回。如果该返回递归地调用";计数";则该返回被搁置,直到嵌套的";计数";调用返回。这种情况一直持续到arr_len == 0
上的所有内容都已返回。