在 C 中通过引用传递

Passing by reference in C

提问人:aks 提问时间:2/9/2010 最后编辑:S.S. Anneaks 更新时间:4/3/2023 访问量:510880

问:

如果 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 
C 指针 通过引用传递

评论

41赞 Atul 2/27/2015
在此代码中,您传递引用的哪个位置?
23赞 Some programmer dude 3/9/2016
应该注意的是,C 没有通过引用传递,它只能使用指针进行模拟
12赞 Chris Dodd 5/27/2017
正确的语句是“C 不支持通过引用式传递变量”——您需要在调用函数之前显式创建一个引用 (with),并在函数中显式取消引用它 (with )。&*
3赞 ICW 8/22/2019
@Someprogrammerdude 传递指针是按引用传递。这似乎是“精明”的 C 程序员引以为豪的事实之一。就像他们从中得到了好处一样。“哦,你可能认为 C 有通过引用传递,但不,它实际上只是传递的内存地址的值,harharhar”。从字面上看,通过引用传递只是传递变量存储位置的内存地址,而不是变量的值本身。这是 C 所允许的,并且每次传递指针时都是按引用传递的,因为指针是对变量内存位置的引用
4赞 Some programmer dude 8/22/2019
@YungGun 从技术上讲,指针本身是按值传递的。

答:

78赞 user195488 2/9/2010 #1

在 C 语言中,模拟了按引用传递 通过传递变量的地址 (指针)并取消引用 函数内的地址 读取或 写入实际变量。这将 被称为“C型” 通过引用。

来源:www-cs-students.stanford.edu

32赞 Daniel Vassallo 2/9/2010 #2

您的示例之所以有效,是因为您将变量的地址传递给使用取消引用运算符操作其值的函数。

虽然 C 不支持引用数据类型,但您仍然可以通过显式传递指针值来模拟按引用传递,如示例中所示。

C++ 引用数据类型功能较弱,但被认为比从 C 继承的指针类型更安全。这将是您的示例,适用于使用 C++ 引用

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

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

  return 0;
}

评论

5赞 2/10/2010
维基百科的那篇文章是关于C++的,而不是C.引用在C++之前就已经存在,并且不依赖于特殊的C++语法。
2赞 Daniel Vassallo 2/10/2010
@Roger:好点子......我从我的答案中删除了对 C++ 的明确引用。
1赞 2/11/2010
那篇新文章说“引用通常被称为指针”,这与你的答案并不完全一样。
60赞 Alexander Gessler 2/9/2010 #3

因为上面的代码中没有通过引用。使用指针(如 )是按地址传递的。 这是 C++ 中的引用传递(在 C 中不起作用):void func(int* p)

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

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

评论

4赞 Afzaal Ahmad Zeeshan 6/7/2017
我喜欢按地址传递的答案。更有意义。
3赞 ICW 9/11/2019
在此上下文中,地址和引用是同义词。但是你可以用这些术语来区分两者,只是对它们的原始含义不诚实。
-2赞 Maurits Rijk 2/9/2010 #4

“通过引用传递”(通过使用指针)从一开始就存在于 C 语言中。你为什么不认为?

评论

9赞 Mehrdad Afshari 2/9/2010
因为从技术上讲,它不是通过引用传递的。
10赞 John Bode 2/9/2010
传递指针值与按引用传递不同。更新 (not ) in 的值对 中没有影响。j*jf()imain()
7赞 2/10/2010
它在语义上与通过引用传递是一回事,这足以说它是通过引用传递。诚然,C 标准没有使用“参考”一词,但这既不让我感到惊讶,也不是问题。我们也没有在 SO 上谈论标准,即使我们可以参考标准,否则我们将看不到有人在谈论右值(C 标准不使用这个术语)。
2赞 Sanjeev 2/26/2022
@SamGinrich,“对于习惯于其他语言的程序员来说,C 函数的一个方面可能并不熟悉,尤其是 Fortran。在 C 语言中,所有函数参数都是“按值”传递的。-- Brian Kernighan(C 语言的创建者)和 Dennis Ritchie,“C 编程语言,第二版”,第 1.8 节。第一版写于 70 年代中期,比 Java 早了将近 20 年。这是第一本由创造 C 的人写的 C 书,这意味着他们在 C 的开头就在那里。
1赞 Sanjeev 2/28/2022
@SamGinrich 是的,可以将 PBR 实现为指向指针的指针,但不是必需的。还有其他技术可以做到这一点。无论如何,如果你在谈论PBR的实现,你就完全没有抓住重点。这实际上是关于程序员选择使用 PBR 选项时的语言规范和行为。引擎盖下的实现细节无关紧要。
361赞 tvanfosson 2/9/2010 #5

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

评论

4赞 bapi 3/4/2014
f(p);->这是否意味着按值传递?然后取消引用它以获取指向的整数。->你能给出更多的解释吗?
4赞 Rauni Lillemets 11/11/2014
@bapi,取消引用指针意味着“获取此指针引用的值”。
0赞 Jon Wheelock 10/11/2015
我们调用的函数的方法,该函数获取变量的地址而不是传递指针。示例:func1(int &a) 。这难道不是引用调用吗?在这种情况下,引用实际上是被采用的,在指针的情况下,我们仍然按值传递,因为我们只按值传递指针。
2赞 Danijel 11/18/2016
使用指针时,关键事实是指针的副本被传递到函数中。然后,该函数使用该指针,而不是原始指针。这仍然是按值传递的,但它有效。
3赞 ICW 9/19/2019
@Danijel 可以将指针传递给函数调用,该指针不是任何内容的副本。例如,调用函数 : 这会将指向 A 的指针传递给函数,而不会复制任何内容。它是按值传递的,但该值是引用,因此您正在“按引用传递”变量 A。说它是按引用传递是有道理的。funcfunc(&A);
4赞 dave 2/9/2010 #6

因为你要把一个指向变量 p 的指针(内存地址)传递到函数 f 中。换句话说,您传递的是一个指针,而不是一个引用。

14赞 antony.trupe 2/9/2010 #7

您正在按值传递指针(地址位置)。

这就像说“这是我希望你更新的数据的地方。

4赞 xan 2/9/2010 #8

您不是通过引用传递 int,而是通过值传递指向 int 的指针。不同的语法,相同的含义。

评论

1赞 2/10/2010
+1 “不同的语法,相同的含义。”所以同样的事情,因为意义比语法更重要。
0赞 Konfle Dolex 2/24/2013
不对。通过调用 ,它打印 111 而不是 500。如果您通过引用传递,它应该打印 500。C 不支持通过引用传递参数。void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }
0赞 xan 2/24/2013
@Konfle,如果您在语法上通过引用传递,则将被禁止。不管有什么区别,我认为你是在指出“相同的含义”并不完全正确,因为你在 C 中也有额外的功能(重新分配“引用”本身的能力)。ptr = &newvalue
0赞 Konfle Dolex 2/25/2013
我们从不写一些东西,比如它是否是通过引用传递的。取而代之的是,我们编写 这是C++中的一个示例:传递给func()的参数的值将变为。ptr=&newvalueptr=newvaluevoid func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }500
0赞 Konfle Dolex 2/25/2013
就我上面的评论而言,通过引用传递参数是没有意义的。但是,如果参数是对象而不是 POD,这将产生重大差异,因为如果函数内部的任何更改都对调用者没有影响,如果它是由 value(pointer) 传递的。如果通过引用传递,则调用方将看到更改。param = new Class()param
5赞 t0mm13b 2/9/2010 #9

在 C 中,要通过引用传递,您使用了 address-of 运算符,它应该用于变量,但在您的情况下,由于您使用了指针变量,因此您不需要在它前面加上 address-of 运算符。如果将 : 用作参数,则为 true。&p&if(&i)

您还可以添加此值以取消引用并查看该值的匹配方式:pi

printf("p=%d \n",*p);

评论

0赞 2/10/2010
为什么你觉得有必要重复所有的代码(包括那个注释块......)来告诉他他应该添加一个printf?
0赞 2/10/2010
@Neil:这个错误是由@William的编辑引入的,我现在要撤销它。现在很明显,tommieb 基本上是正确的:你可以将 & 应用于任何对象,而不仅仅是变量。
7赞 Anssi 2/9/2010 #10

p 是一个指针变量。它的值是 i 的地址。当您调用 f 时,您传递 p 的值,即 i 的地址。

评论

0赞 Sam Ginrich 3/3/2022
其中“通过引用传递”将 i 视为值,即 *p
8赞 Pavel Radzivilovsky 2/9/2010 #11

C 中没有通过引用传递,但 p “引用”到 i,并且您通过值传递 p。

7赞 Francisco Zapata 10/22/2014 #12

简短的回答:是的,C 确实使用指针实现了通过引用传递的参数。

在实现参数传递时,编程语言的设计者使用三种不同的策略(或语义模型):将数据传输到子程序,从子程序接收数据,或两者兼而有之。这些模型通常称为 in 模式、out 模式和 inout 模式。

语言设计人员设计了几个模型来实现这三个基本参数传递策略:

Pass-by-Value(在语义模式下) Pass-by-Result(输出模式语义) Pass-by-Value-Result(输入模式语义) Pass-by-Reference(输入模式语义) Pass-by-Name(输入模式语义)

按参考传递是输入模式参数传递的第二种技术。 运行时系统不是在主例程和子程序之间来回复制数据,而是向子程序发送对数据的直接访问路径。在此策略中,子程序可以直接访问数据,有效地与主例程共享数据。这种技术的主要优点是它在时间和空间上绝对有效,因为不需要复制空间,也没有数据复制操作。

C 语言中的参数传递实现: C 使用指针作为参数实现按值传递和按引用传递(inout 模式)语义。指针被发送到子程序,并且根本不复制任何实际数据。但是,由于指针是主例程数据的访问路径,因此子程序可能会更改主例程中的数据。C从ALGOL68年起就采用了这种方法。

C++ 中的参数传递实现: C++ 还使用指针和一种称为引用类型的特殊指针(称为引用类型)实现按引用传递(inout 模式)语义。引用类型指针在子程序中隐式取消引用,但其语义也是按引用传递的。

因此,这里的关键概念是,按引用传递实现了对数据的访问路径,而不是将数据复制到子程序中。数据访问路径可以是显式取消引用的指针,也可以是自动取消引用的指针(引用类型)。

有关更多信息,请参阅 Robert Sebesta 所著的《编程语言的概念》一书,第 10 版,第 9 章。

197赞 Ely 5/29/2015 #13

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

C 语言无一例外地是按值传递的。传递指针 作为参数并不意味着按引用传递。

规则如下:

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

(以上引文其实出自《K&R》一书)


让我们试着看看函数的标量参数和指针参数之间的区别。

标量变量

这个简短的程序显示了使用标量变量的按值传递。 称为形式参数,AT 函数调用称为实际参数。注意,函数中的递增不会更改。paramvariableparamvariable

#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

在我看来,这清楚地证明了指针是按值传递的。否则将在函数调用之后。ptrNULL

评论

2赞 Sam Ginrich 2/25/2022
“PTR 的地址”不正确:您打印指针,它是变量的地址,而不是它的地址,本来是 'printf(“PTR 的地址 %d\n”, &ptr);' 。传递引用的技术层是传递指针,而不是幻觉!在函数 2 中,*param 实际上是一个引用。
0赞 Dan 4/5/2022
正确。“指针 - 按值传递”部分中演示的内容实际上应该改写为:“printf(”其中指针参数指向 %d\n“, param);” 和 “printf(”其中指针 ptr 指向 %d\n“, ptr);”。抓得好^^ 我认为要显示的内容确实是:“printf(”指针参数地址 %d\n“, &param);” 和 “printf(”指针 ptrs 地址 %d\n“, &ptr);”
0赞 Ely 4/5/2022
是的,你是对的。我纠正了这一点。事实上,我想表明他们所指向的地址是相同的。指针的地址在这里并不真正有趣(也不相同)。
-2赞 Johannes 6/23/2017 #14

我认为 C 实际上支持通过引用传递。

大多数语言要求句法糖通过引用而不是值传递。(例如,C++ 在参数声明中需要 &)。

为此,C 语言还需要语法糖。它在参数类型声明中是 *,在参数中是 &。所以 * 和 & 用于通过引用传递的 C 语法。

现在有人可能会争辩说,真正的引用传递应该只需要参数声明的语法,而不是参数端的语法。

但现在出现了 C#,它确实支持引用传递,并且需要参数和参数方面的语法糖

C 没有 by-ref 传递导致表达它的语法元素表现出底层技术实现的论点根本不是一个论点,因为这或多或少适用于所有实现。

剩下的唯一论点是,在 C 语言中传递 ref 不是一个整体特性,而是结合了两个现有特性。(将参数的 ref 取为 &,期望 ref 按 * 键入。例如,C# 确实需要两个语法元素,但它们不能彼此分开使用。

这显然是一个危险的论点,因为语言中的许多其他特征都是由其他特征组成的。(如C++中的字符串支持)

7赞 Arun Suresh 7/11/2017 #15

在 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
1赞 Sunay 11/22/2017 #16

您正在做的是按值传递,而不是按引用传递。 因为您正在将变量“p”的值发送到函数“f”(在 main 中为 f(p);)

在 C 语言中,通过引用传递的相同程序将如下所示,(!!该程序给出 2 个错误,因为 C 不支持通过引用传递)

#include <stdio.h>

void f(int &j) {    //j is reference variable to i same as int &j = i
  j++;
}

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

  return 0;
}

输出:-

3:12: error: expected ';', ',' or ')' before '&' token
             void f(int &j);
                        ^
9:3:  warning: implicit declaration of function 'f'
               f(a);
               ^

评论

0赞 Sam Ginrich 12/9/2022
您知道,C++编译器的历史实现实际上是语言转换器,它们会以明确定义的方式将您的脚本带入C语言,并猜猜是什么:“void f(int &j)”将变成“void f(int *j)”和参数访问“j”和“*j”,这两个引用在C++中,其中术语“引用”实际上是定义的。事实上,C 编译器无法理解 C++-reference 语法不会撤消引用和取消引用指针的严格语义等效性。
2赞 user2187033 5/8/2018 #17

指针和引用是两个不同的方法。

有几件事我没有看到提到。

指针是某物的地址。指针可以像任何其他变量一样存储和复制。因此,它有一个大小。

引用应被视为某物的别名。它没有大小,无法存储。它必须引用一些东西,即。它不能为 null 或更改。好吧,有时编译器需要将引用存储为指针,但这是一个实现细节。

使用引用,您不会遇到指针问题,例如所有权处理、null 检查、使用时取消引用。

评论

0赞 Sam Ginrich 2/25/2022
这是不正确的。应用于引用的 sizeof 运算符提供类型的大小,应用于引用的地址运算符“&”提供指针,指针可以存储,这在传递参数时在技术上会发生。
0赞 Sanjeev 4/3/2023
这是绝对正确的!这个术语的混淆来自Java和JavaScript等语言使用这个词来表示指针,这是这个词的用法,而不是在传递引用中。
0赞 Sam Ginrich 2/25/2022 #18

该代码片段(稍作修改)

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

也存在于 C++ 中,在语义上等效于

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

在这两种情况下,编译器都应生成函数的相等二进制代码。现在,当您将整数视为一个值时,该值由它的引用传递,在上层模式中,引用在技术上显示为指针。add_number

结论
C 支持通过引用传递实例的语义。
即使从技术上讲,你通过,这是一个参考。
int *a*a

1赞 Sanjeev 2/26/2022 #19

将指针称为引用(就像 Java 和 Javascript 所做的那样)是引用一词与通过引用完全不同的用法。C 不支持按引用传递。这是重新编写的示例,以表明它并没有真正通过引用传递值,而只是通过值传递指针。

#include <stdio.h>

void f(int *j) {
  int k = (*j) + 1;
  j = &k;
}

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


  printf("i(ptr) = %p\n", &i);
  printf("j(ptr) = %p\n", p);


  return 0;
}

这是输出

i = 20
j = 20
i(ptr) = 0x7ffdfddeee1c
j(ptr) = 0x7ffdfddeee1c

如您所见,该值保持不变,但更重要的是指针也不会更改。但是,C++ 允许通过引用传递。这是通过 C++ 编译器放置的相同示例,但在标头中添加了 & 符号,使其成为参考参数。

#include <stdio.h>

void f(int *&j) {   // note the & makes this a reference parameter.
                    // can't be done in C
  int k = (*j) + 1;
  j = &k;
}

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


  printf("i(ptr) = %p\n", &i);
  printf("j(ptr) = %p\n", p);


  return 0;
}

这是输出

i = 20
j = 21
i(ptr) = 0x7ffcb8fc13fc
j(ptr) = 0x7ffcb8fc13d4

请注意,我们能够更改实际指针!

关于这个主题的一个很好的参考是 The Dragon Book——一本关于编译器的经典计算机科学教科书。由于它是使用最广泛的编译器教科书(或者至少在我上大学的时候是这样),我猜想绝大多数设计语言或编写编译器的人都是从这本书中学到的。本书的第 1 章非常清楚地解释了这些概念,并解释了为什么 C 只能按值传递。

评论

0赞 Sam Ginrich 2/27/2022
请参阅以下 Johannes 的回答