在C语言中通过引用传递参数

258

如果C语言不支持引用传递变量,为什么这段代码可以正常工作?

#include <stdio.h>

void f(int *j) {
  (*j)++;
}

int main() {
  int i = 20;
  int *p = &i;
  f(p);
  printf("i = %d\n", i);

  return 0;
}

输出:

$ gcc -std=c99 test.c
$ a.exe
i = 21 

41
在这段代码中,你在哪里传递了“_reference_”? - Atul
23
需要注意的是,C语言没有传递引用的方式,只能使用指针来模拟 - Some programmer dude
12
正确的表述应该是“C语言不支持隐式地通过引用传递变量”——在调用函数之前需要显式地创建一个引用(使用&符号),并在函数内显式地取消引用(使用*符号)。 - Chris Dodd
4
传递指针是传递引用。这似乎是那些“精明”的C程序员引以为傲的事实之一,他们对此感到非常兴奋。“哦,你可能认为C是按引用传递的,但实际上只是传递内存地址的值哈哈哈。” 传递引用字面上意味着传递变量存储位置的内存地址,而不是变量的值本身。这是C所允许的,每次你传递指针时都是传递引用,因为指针是一个变量内存位置的引用 - ICW
4
从技术上讲,指针本身是按值传递的。 - Some programmer dude
显示剩余3条评论
19个回答

361
因为您将指针的传递给方法,然后取消引用它以获取所指向的整数。

4
f(p); -> 这是否意味着按值传递?然后通过取消引用来获取所指向的整数。 -> 请问能否给出更多解释。 - bapi
4
解引用指的是“获取指针所指向的值”。 - Rauni Lillemets
我们所说的通过地址调用函数的方法而不是传递指针的方法,被称为引用调用。例如:func1(int &a)。这不是按引用调用吗?在这种情况下,真正采用了引用,而在指针的情况下,我们仍然是按值传递,因为我们只是按值传递指针。 - Jon Wheelock
2
在使用指针时,关键的事实是将指针的副本传递到函数中。然后函数使用该指针,而不是原始指针。这仍然是按值传递,但它可以工作。 - Danijel
3
可以将一个非副本的指针传递给函数调用。例如,调用函数func: func(&A); 这将在不复制任何内容的情况下将指向A的指针传递给函数。虽然这是按值传递,但该值是一个引用,因此您正在“按引用传递”变量A。不需要复制任何内容。可以说这是按引用传递。 - ICW
重要提示:解除引用的指针是一个引用。不清楚为什么指针被视为除地址以外的其他东西。 - Sam Ginrich

197

正如其他人所说,这不是按引用传递,而是按值传递。

C语言在任何情况下都是按值传递。将指针作为参数传递并不意味着按引用传递。

规则如下:

函数无法更改实际参数的值。

(上面的引文实际上来自K&R的书)


让我们尝试了解函数的标量和指针参数之间的区别。

标量变量

此短程序使用标量变量显示按值传递。 param 被称为形式参数,函数调用时的 variable 被称为实际参数。请注意,在函数中增加 param 不会更改 variable

#include <stdio.h>

void function(int param) {
    printf("I've received value %d\n", param);
    param++;
}

int main(void) {
    int variable = 111;

    function(variable);
    printf("variable %d\m", variable);
    return 0;
}

结果是

I've received value 111
variable=111

传引用的假象

我们稍微修改了代码。现在param是一个指针。

#include <stdio.h>

void function2(int *param) {
    printf("I've received value %d\n", *param);
    (*param)++;
}

int main(void) {
    int variable = 111;

    function2(&variable);
    printf("variable %d\n", variable);
    return 0;
}

结果是

I've received value 111
variable=112

这让你相信参数是按引用传递的。但实际上不是这样,它是按值传递的,参数的值是一个地址。int类型的值被递增,这就是让我们认为这是一个按引用传递的函数调用的副作用。

指针 - 按值传递

那么,我们如何展示/证明这个事实呢?也许我们可以尝试标量变量的第一个例子,但是改为使用地址(指针)。让我们看看这是否有帮助。

#include <stdio.h>

void function2(int *param) {
    printf("address param is pointing to %d\n", param);
    param = NULL;
}

int main(void) {
    int variable = 111;
    int *ptr = &variable;

    function2(ptr);
    printf("address ptr is pointing to %d\n", ptr);
    return 0;
}

结果将是两个地址相等(不用担心确切的值)。

示例结果:

address param is pointing to -1846583468
address ptr   is pointing to -1846583468

依我看,这明确证明了指针以传值方式传递。否则,在函数调用之后ptr将为NULL


2
"ptr的地址"不正确:您打印了指针,它是变量的地址,而不是它的地址,正确的方式应该是´printf("ptr的地址 %d\n", &ptr);´。 传递引用的技术层面是传递指针,而不是幻觉!在function2中,*param实际上是一个引用。 - Sam Ginrich
正确。在“指针-按值传递”部分展示的内容实际上应该重新表述为:“printf(“指针参数指向的位置%d\n”,param);”和“printf(“指针ptr指向的位置%d\n”,ptr);”。很好地捕捉到了^^我认为原意是要显示以下内容: “printf(“指针参数地址%d\n”,&param);”和 “printf(“指针ptr的地址%d\n”,&ptr);” - Dan
是的,你说得对。我纠正了那个错误。实际上,我想表明它们所指向的地址是相同的。指针的地址在这里并不真正重要(也不相同)。 - Ely

78
在C语言中,通过传递变量的地址(指针)并在函数内解引用该地址以读取或写入实际变量来模拟按引用传递。这将被称为"C风格按引用传递"。来源:www-cs-students.stanford.edu

60

以上代码中没有传递引用。使用指针(例如void func(int* p))是通过地址传递。 这是C++中的传递引用的方法(在C中无效):

void func(int& ref) {ref = 4;}

...
int a;
func(a);
// a is 4 now

4
我喜欢“传址调用”的答案,更有意义。 - Afzaal Ahmad Zeeshan
3
在这个语境中,“地址”和“参考”是同义词。但你可以使用这些术语来区分它们,只是这样做并不忠实于它们的原始含义。 - ICW

32
你的例子可行是因为你将变量的地址传递给一个使用 解引用运算符 来操作它的值的函数。
虽然 C 不支持 引用数据类型,但你仍可以通过显式地传递指针值来模拟传递引用,就像你的例子中一样。
C++ 中的引用数据类型比从 C 中继承的指针类型更安全,尽管不如后者强大。下面是使用 C++ 引用 修改的你的例子:
void f(int &j) {
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

5
这篇维基百科文章是关于C++而不是C。参考资料存在于C++之前,并不依赖于特殊的C++语法才存在。 - Roger Pate
2
@Roger:好观点...我从我的回答中删除了对C++的明确引用。 - Daniel Vassallo
1
那篇新文章说“引用通常被称为指针”,这与你的答案有些不同。 - Roger Pate

14
您正在按值传递指针(地址位置)。 这就像是在说“这里是我想让您更新的数据所在的位置。”

8

C语言中没有传递引用的方式,但是指针p“指向”变量i,并且你通过传递指针p的值来完成操作。


7

在C语言中,所有参数都是按值传递的。使用指针会让我们误以为我们是通过引用来传递参数,因为变量会改变。然而,如果你打印出指针变量的地址,你会发现它没有被改变。实际上,函数内部接收到的是地址的一个拷贝。以下是一个示例代码片段:

void add_number(int *a) {
    *a = *a + 2;
}

int main(int argc, char *argv[]) {
   int a = 2;

   printf("before pass by reference, a == %i\n", a);
   add_number(&a);
   printf("after  pass by reference, a == %i\n", a);

   printf("before pass by reference, a == %p\n", &a);
   add_number(&a);
   printf("after  pass by reference, a == %p\n", &a);

}

before pass by reference, a == 2
after  pass by reference, a == 4
before pass by reference, a == 0x7fff5cf417ec
after  pass by reference, a == 0x7fff5cf417ec

7
简短回答:是的,C使用指针实现参数传递的引用。
在实现参数传递时,编程语言的设计者使用三种不同的策略(或语义模型):将数据传输到子程序中、从子程序中接收数据或两者都执行。这些模型通常称为输入模式、输出模式和双向模式。
语言设计师制定了几种模型来实现这三种基本参数传递策略:
传值(输入模式语义) 传结果(输出模式语义) 传值-传结果(双向模式语义) 传引用(双向模式语义) 按名称传递(双向模式语义)
传引用是双向模式参数传递的第二种技术。与在主例程和子程序之间来回复制数据相反,运行时系统向子程序发送一个直接访问路径以访问数据。在这种策略中,子程序可以直接访问数据并与主例程共享数据。这种技术的主要优点是非常高效,因为无需复制空间且没有数据复制操作。
C实现参数传递时既采用传值方式,也采用传引用(双向模式)语义,使用指针作为参数。指针被发送到子程序中,根本不会复制任何实际数据。但是,因为指针是访问主例程数据的路径,所以子程序可能会更改主例程中的数据。C从ALGOL68那里采用了这种方法。
C ++还使用指针和一种特殊类型的指针(称为引用类型)实现传递引用(双向模式)语义。引用类型指针在子程序中隐式解引用,但它们的语义也是传递引用。
因此,关键概念在于传递引用实现对数据的访问路径,而不是将数据复制到子程序中。数据访问路径可以是显式解引用的指针或自动解引用的指针(引用类型)。
有关更多信息,请参阅Robert Sebesta的《编程语言概念》第10版第9章。

7

p是一个指针变量,它的值是i的地址。当你调用f时,你传递了p的值,也就是i的地址。


当“按引用传递”将i视为值时,它是*p。 - Sam Ginrich

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接