【问题标题】:What is byValue and byReference argument passing In C? [duplicate]什么是在 C 中传递的 byValue 和 byReference 参数? [复制]
【发布时间】:2013-12-24 21:39:59
【问题描述】:

我不明白这是什么意思。如果我要尝试猜测我会说 byValue 参数传递是当您根据变量的值传递参数时,所以我在想:

if (a == 1){
 PassAnArgumentOrSomething()
}

但是这可能是错误的:/

至于byReference,我不知道。

如果有人能帮助我,那你真棒:)

【问题讨论】:

  • @Basile Starynkevitch 引用的帖子虽然适用,但属于一般性讨论(与语言无关)。排名靠前的答案都不是 C 特定的。这个标记为 C 的问题值得一个涵盖数组参数细节的答案,可悲的是,这是一场圣战。

标签: c


【解决方案1】:

按值传递参数意味着您正在传递一个副本:

void f(int x) 
{ 
    x = 7;
    // x is 7 here, but we only changed our local copy
}

void g()
{
    int y = 3;
    f(y);
    // y is still 3 here!
}

通过引用传递参数意味着您不是传递副本,而是传递某种引用原始变量的方式。在 C 中,所有参数都是按值传递的,但通常要获得与按引用传递相同的效果,就是传递一个指针:

void f(int *x_ptr) { *x_ptr = 7; }

void g()
{
    int y = 3;
    f(&y);
    // y is 7 here 
}

数组的传递方式看起来类似于通过引用传递,但实际发生的事情要复杂得多。例如:

void f(int a[]) { a[0] = 7; }

void g()
{
    int b[3] = {1,2,3};
    f(b);
    // b[0] is 7 here! looks like it was passed by reference.
}

这里实际发生的是数组b 被隐式转换为指向第一个元素的指针(这称为衰减)。 f 参数的 int a[] 表示法实际上是指针的语法糖。上面的代码相当于:

void f(int *a) { a[0] = 7; }

void g()
{
    int b[3] = {1,2,3};
    f(&b[0]);
    // b[0] is 7 here
}

【讨论】:

  • 这根本不是隐含的。您明确传递了第一个元素。您可以轻松地使用bar = foo(arr[7]); 将数组的不同“头”传递给新函数。
  • @JonahNelson:我不确定您指的是什么代码。在f(b) 的情况下,我不会将其称为指向b 的第一个元素的指针的显式传递。如果我想明确一点,我会写f(&b[0])
  • 您在函数声明/定义中唯一要做的就是接收您声明的任何类型的 any 指针。函数调用进行数据的显式传递。由于 arr == &arr[0] 传递 arr 使指针指向第一个元素。 arr + 7 === &arr[7],因此您正在使用原始数组的子集制作一个新数组。没有什么隐含的。
  • @JonahNelson:如果arr 是一个数组,那么它就不是一个指针,它是一个数组。当它像指针一样使用时,它被转换为指针。这种转换称为衰减,它是隐式的,因为它不需要任何显式代码来实现。
  • 这与您传递的 数组索引 有什么关系?
【解决方案2】:

除了数组和函数(见下文),C 总是“按值”传递参数:每个参数的值的副本被传递给函数;该函数无法修改传递给它的实际参数:

void foo(int j) {
  j = 0;  /*  modifies the copy of the argument received by the function  */
}

int main(void) {
  int k=10;
  foo(k);
  /*  k still equals 10  */
}

如果您确实希望函数修改其参数,则可以使用指针参数获得所需的效果:

void foo(int *j) {
  *j = 0;
}

int main(void) {
  int k=10;
  foo(&k);
  /*  k now equals 0  */
}

这有时在其他语言中称为“通过引用传递”。

【讨论】:

  • (虽然值得注意的是,您仍在按值传递指针本身!)
【解决方案3】:

传值就是传值本身;它会复制该值,并且您在新函数中所做的任何更改都不会保存到原始变量中:

void foo(int num)
{
    num = 5; // Does not save to the original variable that was passed when foo was called
...
}

引用传递就是传递变量的位置;它允许新函数直接编辑原始变量:

void bar(int * num)
{
    *num = 5; // Changes the variable in the original function
...
}

【讨论】:

  • 请注意,这实际上并不是通过引用传递:您仍在将指针的值传递给函数。
  • 是的。在 C 和我相信的 C++ 中,没有办法实际上通过引用传递。然而,这是那些使用这些语言的人理解“通过引用”的方式。
  • @JonahNelson C++ 中的 references 怎么样?
  • 在 c++ 中,您也有参考。考虑: int func(int &a) { ... } 。 &a 是参考变量。不创建传递变量的副本。如果您在 func 中更改 a 的值,调用者也会看到更改后的值。
【解决方案4】:

c语言中没有引用传递

按值传递:表示您正在创建变量的临时副本并发送到参数。

通过引用传递(c 语言中没有这样的概念):意味着您只是在调用时为原始变量赋予另一个名称,并且不会创建变量的临时副本。

按值调用:

int foo(int temp)
{
    /.../
}
int main()
{
    int x;
    foo(x); /* here a temporary copy of the 'x' is created and sent to the foo function.*/

}

引用调用(c 语言中没有这个概念)

int foo(int& temp)
{
   /.../
}
int main()
{
    int x;
    foo(x); /* here no temporary copy of 'x' is being created rather the variable *temp* in the calling function is just another name of the variable a in the main function*/
}

【讨论】:

  • 您可能应该提到您的第二个示例不是 C,而是 C++。
  • @alk 感谢您的帮助!
  • 引用又名别名
【解决方案5】:

深入讲解

每当程序加载时,它都会获得一个称为地址空间的内存区域,该区域被划分为多个区域

  1. code/text:包含程序语句(语句集合)。
  2. global :包含全局变量(如果有)。
  3. constant :用于常量或字面量存储。
  4. :用于动态内存需求。
  5. stack : 函数使用了它的变量。

考虑一个这样定义的函数

void doSomething(int x)
{
   x++;
}//end of function

在另一个函数或主函数中被称为 doSomething(5) 或 doSomething(y)。

这里 x 是函数“doSomething”的局部变量。它在堆栈区域的某处获得它的家(内存位置)。

当 doSomething(5) 被调用时,5 被复制到 x 的内存或 doSomething(y) 被调用 value/content y (不同的内存位置) 被复制到 x 的内存。在 x 上应用的任何操作都不会影响 y 的内容/值。因为它的内存位置不同。

每当执行流程到达函数结束x死亡/被破坏。 x 的任何值都不可访问/可用。简而言之,更新丢失且 y 不受影响(更改未反映)。

这就是所谓的按值调用

现在 考虑另一个定义为的函数

void doSomething(int *x)
{
    (*x)++;
}

被称为doSomething(&y)

这里 x 被称为 pointer(概念上称为引用*)。它也会在堆栈区域的某个位置返回
当 doSomething(&y) 被调用时,y 的地址 被复制到 x 的位置块。因为这个 x 是一个特殊的变量,所以称为 pointer,它保存了地址,据说 x 引用/指向 y

(*x)++ 被应用时,这里 * 是间接运算符,它将使 x 引用 context 即。 (*x)++ 会将 y 的值间接更改 1。x 的值本身不会发生任何事情。

每当执行流程达到函数结束 *x 死亡/被破坏如预期但这次改变 变为 y(间接地)在堆栈区域的某处仍然存在(变化被反映)。

也不是说这次 doSomething(&5) 或 doSomething(any literal) 不可能,因为获取任何文字的地址都是非法

这就是所谓的引用调用/指针调用。

请注意,引用调用在 C++ 中具有其他含义,但在概念上保持不变。

【讨论】:

    【解决方案6】:

    我们先来看看函数的“调用”。

    在 C 中,函数的参数通常是“按值”。下面,在调用sqrt(x) 之后,x 的值没有改变,因为sqrt() 收到了值x副本,因此永远无法影响x

    y = sqrt(x);
    // x did not change
    printf("%f is the square root of %f\n", y, x);
    

    函数的数组参数似乎是“通过引用”的。在调用fgets() 之后,预计buf 会被更改。 fgets() 没有收到char 数组的副本,而是“引用”。因此fgets() 可能会影响数组的内容。

    char buf[80] = { 0 };  // array 80 of char
    printf("Before <%s>\n", buf);   // "<>"
    fgets(buf, sizeof buf, stdin);
    printf("After <%s>\n", buf);    // "<Hello World!>"
    

    现在让我们看看“接收”部分:函数。

    doubleintstruct类型的函数参数按值接收。这里f的值是上面xcopy。对f 所做的任何更改都不会影响调用代码中的x

    double sqrt(double f) {
      double y;
      ... // lots of code
      return y;
    }
    

    现在是棘手的一点,这就是 C 语言中的大量讨论。

    下面fgets() 中的参数s 没有分配到上面的buf(char 的数组80),而是为s 分配了从buf 派生的指针类型buf 的第一个元素的地址。通过知道buf 的数组元素的地址(通过s),fgets() 会影响上面打印的内容。

    char *fgets(char * restrict s, int n, FILE * restrict stream) {
      // code
      s[i] = ThisAndThat();
      // code
      return s;
    }
    

    现在让我们看看如何调用fgets()

    char *p = malloc(80);
    *p = '\0';
    printf("Before <%s>\n", p);   // "<>"
    fgets(p, 80, stdin); 
    printf("After <%s>\n", p);    // "<Hello World!>"
    

    在第二个示例中,p 是“指向 char 的指针”。它通过传递给fgets()fgets() 通过s 收到p副本

    中心思想:fgets()知道它是否收到“数组第一个元素的地址”或“指向 char 的指针”的副本。在这两种情况下,它都将s 视为“指向字符的指针”。

    假设fgets() 做了以下事情。在s 被更改之前,它会影响s 指向的数据。然后s,指针,改变了。 s 是一个本地 变量,这个赋值不会改变调用例程变量bufp

    char *fgets(char * restrict s, int n, FILE * restrict stream) {
      // lots of code
      s[0] = '\0';
      // more code
      s = NULL;
      return s;
    }
    

    注意:还有其他问题需要考虑,例如将函数作为参数传递以及将数组封装在结构中。

    【讨论】:

      猜你喜欢
      • 2012-08-22
      • 1970-01-01
      • 2010-11-13
      • 1970-01-01
      • 2012-05-22
      • 1970-01-01
      • 2021-10-18
      • 1970-01-01
      • 2020-07-27
      相关资源
      最近更新 更多