如果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语言不支持通过引用传递变量,为什么这样做呢?
#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;
}
因为在上面的代码中没有引用传递。使用指针(如void func(int* p))是通过地址传递的。 这是c++中的引用传递(在C中不起作用):
void func(int& ref) {ref = 4;}
...
int a;
func(a);
// a is 4 now
在C语言中,为了通过引用传递,你使用了地址操作符&,它应该用于一个变量,但在你的例子中,因为你已经使用了指针变量p,你不需要在它前面加上地址操作符。如果你使用&i作为参数:f(&i),这将是正确的。
你也可以添加这个,来解引用p,看看这个值如何匹配i:
printf("p=%d \n",*p);
简单回答:是的,C确实使用指针通过引用实现了参数传递。
在实现参数传递时,编程语言的设计者使用三种不同的策略(或语义模型):将数据传输到子程序,从子程序接收数据,或者两者都做。这些模型通常分别称为in模式、out模式和inout模式。
语言设计者设计了几个模型来实现这三个基本参数传递策略:
值传递(在模式语义中) 结果传递(out模式语义) 值-结果传递(inout模式语义) 引用传递(inout模式语义) 名称传递(inout模式语义)
Pass-by-reference is the second technique for inout-mode parameter passing. Instead of copying data back and forth between the main routine and the subprogram, the runtime system sends a direct access path to the data for the subprogram. In this strategy the subprogram has direct access to the data effectively sharing the data with the main routine. The main advantage with this technique is that its absolutely efficient in time and space because there is no need to duplicate space and there is no data copying operations.
C中的参数传递实现: C使用指针作为参数实现了值传递和引用传递(inout模式)语义。指针被发送到子程序,根本不复制实际数据。然而,由于指针是主例程数据的访问路径,子程序可以改变主例程中的数据。C采用了ALGOL68的方法。
c++中的参数传递实现: c++还使用指针实现了引用传递(inout模式)语义,也使用了一种特殊类型的指针,称为引用类型。引用类型指针在子程序内部隐式地解引用,但它们的语义也是引用传递的。
这里的关键概念是引用传递实现了数据的访问路径,而不是将数据复制到子程序中。数据访问路径可以是显式解引用指针或自动解引用指针(引用类型)。
有关更多信息,请参阅Robert Sebesta所著的《编程语言的概念》,第10版,第9章。
这不是引用传递,而是其他人所说的值传递。
C语言毫无例外地采用值传递。传递指针 作为参数并不意味着引用传递。
规则如下:
函数不能改变实际的参数值。
(以上引自《K&R》一书)
让我们试着看看函数的标量形参和指针形参之间的区别。
标量变量
这个简短的程序展示了使用标量变量的值传递。参数被称为形式参数,函数调用时的变量被称为实际参数。注意在函数中增加参数并不会改变变量。
#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); ^
指针和引用是两个不同的东西。
有几件事我没有看到有人提到。
指针是某物的地址。指针可以像其他变量一样存储和复制。因此,它有一个大小。
引用应该被视为某个东西的别名。它没有大小,不能存储。它一定是指什么。不能为空或更改。好吧,有时候编译器需要将引用存储为指针,但这是实现细节。
有了引用,你就不会有指针的问题,比如所有权处理、空检查、使用时取消引用。
代码片段(有微小修改)
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所做的)与引用传递(pass-by-reference)是完全不同的用法。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是一本关于编译器的经典计算机科学教科书。因为它是有史以来最受欢迎的编译器书籍(至少在我上大学的时候是这样,也许我错了),我猜绝大多数设计语言或编写编译器的人都是从这本书中学习的。本书的第一章非常清楚地解释了这些概念,并解释了为什么C语言只采用值传递。