【问题标题】:What happens to an array during recursion in C?在 C 中递归期间数组会发生什么?
【发布时间】:2014-07-28 04:07:04
【问题描述】:

在下面的代码中,我尝试在二叉树中打印从根到叶子的所有路径。 如果我写一个递归函数如下:

    void printPath(BinaryTreeNode * n, int path[],int pathlen)
    {
    //assume base case and initializations taken care of

        path[pathlen++] = n->data;
        printPath(root->left,path,pathlen);
        printPath(root->right,path,pathlen);
    }

(我特意删除了基本情况和边缘情况处理以提高可读性)

path 数组会发生什么?它只是在每次递归调用期间被修改的一个全局副本吗? pathlen 变量是否覆盖了一些 path 值,给人的感觉是每个堆栈帧都有自己的本地path 的副本,因为 pathlen 对于每个堆栈帧都是本地的?

【问题讨论】:

  • path 只是一个指针变量,所以无论复制什么,它们都必须引用相同的内存地址。
  • 这是一个很长的问题:“它只是在每次递归调用期间被修改的一个全局副本,并且 pathlen 变量覆盖了一些路径值,给人的感觉是每个堆栈帧都有自己的本地路径的副本,因为 pathlen 对于每个堆栈帧都是本地的?”。您可能想尝试改写一下,因为在某些时候很难理解您在问什么......
  • @barakmanos 我尝试尽可能添加昏迷。这有帮助吗?

标签: c recursion tree tree-traversal


【解决方案1】:

传递int[] 变量实际上几乎就像传递int* 一样。递归函数的第一次调用传递了真正的int[],它只不过是内存中的一个地址,与每个递归调用中使用的地址相同。

基本上,如果您放置调试打印,例如

printf("%p\n", path);

在您的递归函数中,您会看到地址始终相同,既不会改变也不会被修改。在调用期间唯一被压入栈帧的是数组的地址,但始终保持不变。

【讨论】:

  • 您能否解释一下我对 pathlen 变量的作用的疑问?基本上它负责给我兄弟节点的完整路径镜头?
【解决方案2】:

欢迎来到数组指针衰减。当你传递一个数组时,会发生两件不同的事情:

  1. 当你声明一个函数来接受一个数组参数时,你实际上是在定义这个函数来接受一个指针参数。即声明

    void foo(int bar[]);
    

    完全等价于

    void foo(int* bar);
    

    数组的声明已衰减为指向其第一个元素的指针的声明。

  2. 每当你使用数组时,它也会衰减成指向它的第一个元素的指针,所以代码

    int baz[7];
    foo(baz);
    

    又完全等同于

    int baz[7];
    foo(&baz[0]);
    

    只有两个例外情况不会发生这种数组指针衰减:语句sizeof(baz)&baz

这两种效果共同造成了数组通过引用传递的错觉,尽管 C 只通过值传递指针。


array-pointer-decay 的发明是为了允许根据指针算术定义数组下标运算符:语句baz[3] 被定义为等同于*(baz + 3)。试图将 3 添加到数组的表达式。但由于数组指针衰减,baz 衰减为int*,在该int* 上定义了指针算法,并产生指向数组中第四个元素的指针。然后可以取消引用修改后的指针以获取 baz[3] 处的值。

【讨论】:

    【解决方案3】:
    void printPath(BinaryTreeNode * n, int path[],int pathlen);
    

    编译器真的是这样看的

    void printPath(BinaryTreeNode * n, int *path, int pathlen);
    

    路径数组会发生什么?是否只是在每次递归调用期间修改的一个全局副本

    什么都没有。相同的path 被传递,因为在C 数组传递只是一个指针复制操作;不,它不是全局副本,而是传递给函数第一次调用的参数,并且几乎总是存在于堆栈中。

    并且 pathlen 变量覆盖了一些路径值,给人的感觉是每个堆栈帧都有自己的本地路径副本,因为 pathlen 对于每个堆栈帧都是本地的?

    由于您修改的是数组元素的值,而不是指向数组开头的指针,因此path 本身指向的内容(始终是数组)没有任何改变。就像你说的那样,它可能会给人一种感觉(特别是如果你习惯了其他语言的这种结构),但实际上只有相同的路径会被传递。

    旁白:您似乎没有处理退出条件,就目前而言,当您开始修改超出数组范围的元素时,这将是一个无限循环并且很可能是未定义的行为.

    【讨论】:

    • 我正在处理您在“旁白”中提到的要点。我只是选择不在这里分享它以获得更短的代码。
    猜你喜欢
    • 2011-08-28
    • 1970-01-01
    • 2020-11-03
    • 2012-08-02
    • 2020-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多