#include <stdio.h>
void reverseSentence();
int main() {
printf("Enter a sentence: ");
reverseSentence();
return 0;
}
void reverseSentence() { //(1)
char c;
scanf("%c", &c); //input: abc (2)
if (c != '\n') { (3)
printf("%c-",c); //output1: a-b-c- (4)
reverseSentence(); // call reserseSentence (5)
printf("%c", c); // output2 will return after output1 finish : cba (6)
}
}
显示:abc-cba
在void
reverseSentence()
程序中将工作:
1 -> 2 -> 3 -> 4 -> 5 -> 1 -> 2 -> 3 -> 4 -> 5 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 5 - > 6 ->5 -> 6
你能解释一下它是如何工作的吗?为什么c
变量可以存储所有值?
这里的问题似乎是对局部变量如何工作的理解。我将使用一个简化模型,其中堆栈仅用于局部变量。
main
调用时reverseSentence
,局部变量 ( c
) 在堆栈上分配:
+--------+
| c | <------current running copy of reverseSentence
+--------+
| main |
一些声明后,再次reverseSentence
调用reverseSentence
。因此在堆栈上创建了下一个副本:
+--------+
| c | <------current running reverseSentence
+--------+
| c | <------previous running reverseSentence
+--------+
| main |
随着越来越多的递归调用,栈中的变量越来越多c
。
返回时reverseSentence
,该运行版本的局部变量被销毁。
+--------+
| xxxxxx | <----- reverseSentence that returned
+--------+
| c | <------current running copy of reverseSentence
+--------+
| main |
实际上,该模型有点复杂。根据处理器的体系结构,堆栈可能包含返回地址或指向上下文的指针等。但作为局部变量如何工作的第一个模型,这应该足够了。
我觉得图片会更清晰,f代表reverseSentence。
scanf()
将一次吃一个字符,因此每次reverseSentence()
重复它都会转到下一个直到它碰到换行符然后 if 测试不进行并且递归结束。由于递归的工作原理是 last instance 执行 last printf
,因此您可以cba
退出。
从某种意义上说,您可以将输入“abc”视为错误:原因是如前所述,该程序一次只会将一个字符放入c
. 这通常不是处理用户输入的好方法:最好将其设为处理整个输入字符串的数组。将递归引入其中只会无缘无故地使它变得混乱。
char c
不存储数据,每次它会读取一个字符并scanf
打印它然后,因为这是一个递归函数,它会首先转到输入的最后一个字符并从头到尾打印它们。
这个程序没有反转句子,因为没有句子,它只是读取一些字符并使用递归函数从最后一个打印到第一个。
但是这段代码太长了,不建议,而是你可以定义一个字符串并将其作为输入,然后从最后到第打印它(除了\0
),但即使这种方法也没有反转句子,你的程序和这个方法都是从最后到第一打印。
函数不为其变量使用固定的内存位置。每次调用函数时,它都会获得新的内存位置以用于其变量。
大多数系统实现这一点的方式是整个程序都有一个称为堆栈的保留内存区域。有一个堆栈指针 (SP) 指示正在使用的堆栈的当前部分。每当函数启动时,它都会更改 SP 以指向堆栈中的新区域。然后它将所有局部变量存储在与新 SP 值相关的位置,例如 SP+4、SP+8、SP+24 等。当函数返回时,它会将 SP 恢复为其先前的值。
因此,c
在每次调用中reverseSentence
都位于不同的位置。
最常见的是,堆栈指针从操作系统和程序加载器确定的某个高地址开始,并且“向下”增长,这意味着,当函数需要堆栈上的更多空间时,它会从堆栈指针中减去一些数字给一个较低的地址。当函数返回时,它通过添加相同的数字(或通过从保存它的位置加载前一个堆栈指针值)来反转它。