【问题标题】:List parameters in recursive calls (C)列出递归调用中的参数 (C)
【发布时间】:2011-01-13 08:03:12
【问题描述】:

我正在设计一个递归算法:

int f(int[] a, int[] b){
----changing a here
----changing b here
f(a,b)
----writing a here
----writing b here
}

我知道所有数组都是指针,所以这段代码应该有问题。在所有递归调用完成后,它将写入 a 和 b 的最终值。我不希望这种情况发生。

我应该怎么做才能将该数组作为常规“按值传递”传递给递归调用?

PS:数组可以在递归调用中的任何地方动态分配或调整大小等。

【问题讨论】:

  • 不知道你在这里做什么。
  • “数组可以动态分配调整大小等” - 在 C 中 ????不是我不想帮忙——我愿意,但问题不是很清楚。您能否再解释一下,我们会尽力提供帮助。谢谢。
  • 如果 ab 是数组,您不能调整它们的大小。有关详细信息,请参阅我的答案。 “改变”和“写作”是什么意思?

标签: c list arrays recursion


【解决方案1】:

我会假设int f(int[] a, int[] b) 是一个错字:它应该是int f(int a[], int b[])

首先,arrays and pointers are different。在许多情况下,数组的名称“衰减”为指向其第一个元素的指针,但并非总是如此。否则,sizeof a 的数组 a 将无法“工作”。

其次,让我们暂时忽略递归。让我们也让函数原型更简单:

int g(int *a);

(我把int a[]改成了int *a,因为在这个上下文中,数组的名字就相当于一个指针。)

现在,您说您可以在函数调用中“动态分配或调整”数组。由于everything is passed by value in C,调用者无法看到g()a 的动态分配或调整大小:调用者仍将具有a 的旧值。但更重要的是,如果 a 的“原始”类型是一个数组,即调用者的代码如下:

int data[SIZE];
g(data);

然后尝试在g() 中调整data 的大小是不好的,因为调用中指定的参数不是动态分配的。您只能动态调整 malloc()calloc()realloc() 的结果。

所以,即使是这个简单的定义也有两个问题:

  • 如果g() 必须动态调整所给定地址所引用的内存大小,则该值必须来自动态分配,而不是数组,
  • 修复该问题后,由于g() 希望能够向调用者发出更改信号,因此您需要将指针传递给需要更改的内容。所以,g() 的原型变成:int g(int **a);

希望以上内容可以帮助您入门。如果你告诉我们更多关于你的算法,特别是你的意思:“改变”和“写作”,你会得到更好的回应。

编辑:在评论中回答你的问题:

所以当我将一个数组传递给一个函数时,它会衰减为一个指针,并且这个指针是按值传递的。如果该指针指向我在该调用之前分配的位置...

如果你在调用之前分配了一些东西,那么它就永远不是一个数组。它可以被索引为一个数组,但这不是重点。那么,您可能对这里的术语感到困惑?

...当我的新函数更改指向的值时,调用者也会更改该值。

指向的值改变了,是的。

我不希望它是这样的,所以我需要一个新函数中指向的值的副本,这样我的原始指针的指向值就不会改变。我现在清楚了吗?

现在更清楚了,但这会引发更多问题:

  • 如果您要在每次调用函数时动态分配或调整数据大小,您将如何返回这些新指针?你不能。这意味着你有自己的内存泄漏。除非你free()(递归调用的)函数本身中的数据。
  • 如何调整指针的大小?除非您使用哨兵值,否则您可能无法知道指向的数据的大小。

您是否正在使用该函数迭代地解决难题或问题?您是否在每次调用该函数时free()ing 您的数据?如果你能告诉我们,你到底想做什么?

【讨论】:

  • @Alok 是的,C 中的所有内容都是按值传递的。因此,当我将数组传递给函数时,它会衰减为指针,并且该指针是按值传递的。如果当我的新函数更改指向的值时,该指针指向我在该调用之前分配的位置,那么该值也会在调用者处更改。我不希望它是这样的,所以我需要一个新函数中指向的值的副本,这样我的原始指针的指向值就不会改变。我现在清楚了吗?
  • 我的术语可能不正确,但你明白我的意思。我正在尝试实现en.wikipedia.org/wiki/Bron–Kerbosch_algorithm 第二个版本。或许你能理解我现在关心的是什么?请检查递归调用。我猜想在每次递归调用时手动从堆中分配新数据并在函数结束时释放它可以解决问题。我说的对吗?
  • @huhuhuu:好的,我不知道算法,但是花了一些时间阅读它:您需要在每次调用函数时为 R、P 和 X 分配内存,并且然后记得在您返回或知道您已完成时释放它。而且您必须复制新分配的数据中的元素-无论如何它们的长度都将与初始长度不同,因此您无法就地修改它们。我希望这是有道理的,祝你编程好运。看起来很有趣!
  • 感谢阿洛克。我今天会努力做到的。实际上它在 java 或 C++ 中并不难,但我还不太擅长 C。
【解决方案2】:

【讨论】:

    【解决方案3】:

    假设您希望输出的顺序与原始顺序相同,如果您不希望外部调用看到在递归调用中对数组所做的更改,则需要复制它们。最简单的方法可能是将它们分配到堆栈上,然后从参数中进行内存复制,尽管如果它变得太大会导致 SO。第二个最简单的方法是 malloc 并释放它们。您可能还需要传递数组大小。您不能在 C 中按堆栈上的值传递数组。

    【讨论】:

    • 我一开始就是这么想的。我想知道除了复制之外是否还有其他方法可以做到这一点。但我想没有。
    • 如果 C 有按值传递,它会将数组的值复制到堆栈中。如果您希望内部函数具有一个值,而外部函数保留另一个值,那么您需要一个副本。
    • C 确实有传值。数组衰减成指针,按值传递:)
    • 一旦腐烂凝固,就不一样了
    【解决方案4】:

    只需在将它们传递到下一个递归级别之前打印它们。

    【讨论】:

    • 输出顺序颠倒了。
    【解决方案5】:

    你的问题不是很清楚,但我正在阅读的是:

    您有一个在两个堆分配数组上运行的递归算法。有可能其中一个递归调用必须重新分配数组,因此当它返回到下一个级别时,旧指针将不再有效。您想知道如何“按值传递数组”以避免这种情况。

    首先,您不能在 C 中按值传递数组。句号。数组总是作为指向第一个元素的指针传递,没有办法绕过它。

    第二,答案总是另一个层次的间接性。 :)

    为了解决在递归调用期间必须重新分配数组的问题,您要做的是让函数采用两个指向指针的指针(int**),其中*a 给出了地址指向 A 数组的第一个元素的指针,(*a)[n] 给出了 a 数组的第 n 个元素。这样,您可以根据自己的意愿重新分配数组(更改*a 的值),但a 本身的值始终保持不变。通过这样做,调用堆栈上方的函数实例将自动继承递归调用所做的重新分配,因为它们传递给递归调用的指针 (a) 的指向值 (*a) 是修改以反映实际数据的新位置。

    例如

    int f(int** a, int** b)
    {
    
      /* Do stuff on arrays using (*a)[n] and (*b)[n] */
    
      if (some_condition) {
         /* Time to reallocate */
         *a = realloc(*a, new_size);
         *b = realloc(*b, new_size);
      }
    
      /* Do stuff on arrays using (*a)[n] and (*b)[n] */
    
      f(a, b);  /* Recursive call */
    
      /* Do more stuff using (*a)[n] and (*b)[n] */
      /* a and b are still valid pointers, even though *a and *b might point somewhere new */
    
     return foo;
    }
    
    void use_f(void)
    {
       int* a = malloc(starting_size);
       int* b = malloc(starting_size);
    
       f(&a, &b);
    }
    

    【讨论】:

      【解决方案6】:

      鉴于要求:

      int f(int[] a, int[] b){
      ----changing a here
      ----changing b here
      f(a,b)
      ----writing a here
      ----writing b here
      }
      

      我应该怎么做才能将该数组作为常规“按值传递”传递给递归调用?

      PS:数组可以在递归调用中的任何地方动态分配或调整大小等。

      f() 中的代码要么被授权对数组进行更改(就像现在一样),要么没有被授权进行更改。如果它被授权进行更改,那么您无需做太多事情,除了担心如果您使用动态分配是否会泄漏内存。

      如果代码无权更改数组,则必须复制数组。您可以通过包含适当的 const 限定符来防止代码随意修改它们:

      int f(const int *a, const int *b) { ... }
      

      请注意,您不能在 C 中按值传递数组。您可以让调用者传递可修改的副本 - 或者您可以让接收者(被调用者?)制作副本;如果接收方要在不应该​​进行修改时进行修改,则其中一个或另一个必须这样做。

      【讨论】:

        【解决方案7】:

        最简单且最安全的选项是传递指针和大小。假设您正在研究快速排序之类的东西:

        
        void sort_range( int* ptr, size_t count )
        {
            size_t pivot;
        
            assert( ptr ); /* make sure we have memory */
        
            if ( count < 2 ) return; /* terminate recursion */
        
            pivot = partition( count ); /* select a pivot */
        
            assert( pivot < count ); /* make sure we don't overrun the buffer */
        
            sort_range( ptr, pivot ); /* sort left part */
            sort_range( ptr + pivot, count - pivot ); /* sort right part */
        
            merge( ptr, count, pivot ); /* merge ranges */
        }
        

        始终注意您正在使用的内存块的大小。不幸的是,C 语言并不容易,所以你必须养成检查内存范围的习惯。

        【讨论】:

          猜你喜欢
          • 2021-01-12
          • 2021-12-20
          • 2022-01-24
          • 2013-07-01
          • 2016-04-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-14
          相关资源
          最近更新 更多