在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
474871 次浏览
在C语言中,模拟引用传递 通过传递变量的地址 (一个指针)并对其进行解引用 地址内的函数读取或 写出实际的变量。这将 被称为“C风格” 引用传递。" < / p >

来源:www- cs-students.standford.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的指针(内存地址)到函数f中。换句话说,你传递的是一个指针而不是一个引用。

你正在传递一个指针(地址位置)的价值

这就像是在说“这里有我想让你更新的数据”。

你不是通过引用传递int型,而是通过值传递指向int型的指针。不同的语法,相同的意思。

在C语言中,要通过引用传递,需要使用地址操作符&,这个操作符应该用于变量,但在你的例子中,因为你已经使用了指针变量p,所以不需要给它加上地址操作符前缀。如果你使用&i作为参数:f(&i),这将是正确的。

你也可以添加这个,来解引用p并查看该值如何匹配i:

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

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

C中没有引用传递,但是p“指向”i,你通过值传递p。

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

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

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

值传递(模式语义) 结果传递(out模式语义) 值-结果传递(inout模式语义) 引用传递(inout模式语义) 名称传递(输入模式语义)

引用传递是入模式参数传递的第二种技术。 运行时系统不是在主例程和子程序之间来回复制数据,而是为子程序发送数据的直接访问路径。在这种策略中,子程序可以直接访问数据,有效地与主例程共享数据。这种技术的主要优点是它在时间和空间上绝对高效,因为不需要复制空间,也没有数据复制操作 在C中参数传递实现: C使用指针作为参数实现了值传递和引用传递(inout模式)语义。指针被发送到子程序,根本不复制实际数据。然而,由于指针是主例程数据的访问路径,子程序可以改变主例程中的数据。

. C采用ALGOL68的方法 c++中的参数传递实现: c++还使用指针实现了引用传递(inout模式)语义,也使用了一种特殊类型的指针,称为引用类型。引用类型指针在子程序内部隐式地解引用,但它们的语义也是引用传递

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

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

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

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

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

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

C也需要语法糖。在参数类型声明中是*,而&关于这个论点。*和&通过引用传递的C语法。

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

但是现在,通过引用传递而且来支持c#的需要在这两个参数和参数侧添加语法糖。

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

唯一剩下的论点是,在C中传递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函数二进制代码。现在,当您将一个整数视为一个值时,该值将由它的引用传递,在上面的模式中,引用在技术上显示为指针。

< p > 结论 < br > 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

注意,我们可以改变实际的指针!

龙之书是一本关于编译器的经典计算机科学教科书。因为它是有史以来最受欢迎的编译器书籍(至少在我上大学的时候是这样,也许我错了),我猜绝大多数设计语言或编写编译器的人都是从这本书中学习的。本书的第一章非常清楚地解释了这些概念,并解释了为什么C语言只采用值传递。