提问人:aks 提问时间:2/9/2010 最后编辑:S.S. Anneaks 更新时间:4/3/2023 访问量:510880
在 C 中通过引用传递
Passing by reference in C
问:
如果 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 语言中,模拟了按引用传递 通过传递变量的地址 (指针)并取消引用 函数内的地址 读取或 写入实际变量。这将 被称为“C型” 通过引用。
来源:www-cs-students.stanford.edu
您的示例之所以有效,是因为您将变量的地址传递给使用取消引用运算符操作其值的函数。
虽然 C 不支持引用数据类型,但您仍然可以通过显式传递指针值来模拟按引用传递,如示例中所示。
C++ 引用数据类型功能较弱,但被认为比从 C 继承的指针类型更安全。这将是您的示例,适用于使用 C++ 引用:
void f(int &j) {
j++;
}
int main() {
int i = 20;
f(i);
printf("i = %d\n", i);
return 0;
}
评论
因为上面的代码中没有通过引用。使用指针(如 )是按地址传递的。
这是 C++ 中的引用传递(在 C 中不起作用):void func(int* p)
void func(int& ref) {ref = 4;}
...
int a;
func(a);
// a is 4 now
评论
“通过引用传递”(通过使用指针)从一开始就存在于 C 语言中。你为什么不认为?
评论
j
*j
f()
i
main()
因为您要将指针的值传递给方法,然后取消引用它以获取指向的整数。
评论
func
func(&A);
因为你要把一个指向变量 p 的指针(内存地址)传递到函数 f 中。换句话说,您传递的是一个指针,而不是一个引用。
您正在按值传递指针(地址位置)。
这就像说“这是我希望你更新的数据的地方。
您不是通过引用传递 int,而是通过值传递指向 int 的指针。不同的语法,相同的含义。
评论
void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }
int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }
ptr = &newvalue
ptr=&newvalue
ptr=newvalue
void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }
500
param = new Class()
param
在 C 中,要通过引用传递,您使用了 address-of 运算符,它应该用于变量,但在您的情况下,由于您使用了指针变量,因此您不需要在它前面加上 address-of 运算符。如果将 : 用作参数,则为 true。&
p
&i
f(&i)
您还可以添加此值以取消引用并查看该值的匹配方式:p
i
printf("p=%d \n",*p);
评论
p 是一个指针变量。它的值是 i 的地址。当您调用 f 时,您传递 p 的值,即 i 的地址。
评论
C 中没有通过引用传递,但 p “引用”到 i,并且您通过值传递 p。
简短的回答:是的,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 章。
这不是按引用传递,而是其他人所说的按值传递。
C 语言无一例外地是按值传递的。传递指针 作为参数并不意味着按引用传递。
规则如下:
函数无法更改实际参数值。
(以上引文其实出自《K&R》一书)
让我们试着看看函数的标量参数和指针参数之间的区别。
标量变量
这个简短的程序显示了使用标量变量的按值传递。 称为形式参数,AT 函数调用称为实际参数。注意,函数中的递增不会更改。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
评论
我认为 C 实际上支持通过引用传递。
大多数语言要求句法糖通过引用而不是值传递。(例如,C++ 在参数声明中需要 &)。
为此,C 语言还需要语法糖。它在参数类型声明中是 *,在参数中是 &。所以 * 和 & 是用于通过引用传递的 C 语法。
现在有人可能会争辩说,真正的引用传递应该只需要参数声明的语法,而不是参数端的语法。
但现在出现了 C#,它确实支持引用传递,并且需要参数和参数方面的语法糖。
C 没有 by-ref 传递导致表达它的语法元素表现出底层技术实现的论点根本不是一个论点,因为这或多或少适用于所有实现。
剩下的唯一论点是,在 C 语言中传递 ref 不是一个整体特性,而是结合了两个现有特性。(将参数的 ref 取为 &,期望 ref 按 * 键入。例如,C# 确实需要两个语法元素,但它们不能彼此分开使用。
这显然是一个危险的论点,因为语言中的许多其他特征都是由其他特征组成的。(如C++中的字符串支持)
在 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
您正在做的是按值传递,而不是按引用传递。 因为您正在将变量“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); ^
评论
指针和引用是两个不同的方法。
有几件事我没有看到提到。
指针是某物的地址。指针可以像任何其他变量一样存储和复制。因此,它有一个大小。
引用应被视为某物的别名。它没有大小,无法存储。它必须引用一些东西,即。它不能为 null 或更改。好吧,有时编译器需要将引用存储为指针,但这是一个实现细节。
使用引用,您不会遇到指针问题,例如所有权处理、null 检查、使用时取消引用。
评论
该代码片段(稍作修改)
void add_number(int * const a) {
*a = *a + 2;
}
也存在于 C++ 中,在语义上等效于
void add_number(int &a) {
a = a + 2;
}
在这两种情况下,编译器都应生成函数的相等二进制代码。现在,当您将整数视为一个值时,该值由它的引用传递,在上层模式中,引用在技术上显示为指针。add_number
结论
C 支持通过引用传递实例的语义。
即使从技术上讲,你通过,这是一个参考。int *a
*a
将指针称为引用(就像 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 只能按值传递。
评论
下一个:在 C 中按引用或值传递对象#
评论
&
*