在C语言中,指针是否被视为引用调用的一种方法?

66
在我大学的C编程课上,教授和她所撰写的书籍在提到C语言中的指针时使用“按引用调用或传递”的术语。以下是我的教授认为的“按引用调用函数”的示例:
int sum(int *a, int *b);

我的教授认为以下是“按值传递函数”的示例:

int sum(int a, int b);

我已经阅读了C语言不支持按引用调用的内容。据我的理解,指针是按值传递的。

基本上,说指针是C语言中按引用传递的方式是不正确的吗?更准确的说法是,在C语言中不能通过引用传递参数,但可以使用指针作为替代方法吗?


更新 11/11/15

从我的问题的起源来看,我相信术语的辩论已经产生了,并且实际上我看到了两个具体的区别。

  • 按引用传递(今天主要使用的术语):在像C++这样的语言中使用的特定术语
  • 按引用传递(我的教授用作解释指针的范例的术语):在像C++之前开发的语言中使用的通用术语,因此在该术语被重写之前使用

阅读@Haris的更新答案后,这就是为什么事情并不是非黑即白的道理。


6
在谈到C语言时,更准确的说法是你不能通过引用传递参数,但可以使用指针作为替代方法。我本人不写C语言,但经常解释按值传递按引用传递之间的区别,我认为维基百科关于这个主题的文章很好:https://en.wikipedia.org/wiki/Evaluation_strategy。 - Felix Kling
2
C语言是按值传递的。按引用传递在C语言范式中并没有太多意义。 - Bobby Sacamano
4
我猜你的教授是在不那么多编程语言的年代学习C语言的,所以对于“按引用传递”的确切含义苛求不太严格。想一想:你计算机的CPU并不支持“按引用传递”,这是人类发明的简化方法。 - Roflo
7
“...不是字面上所描述的情况。” - 我认为相反的是正确的:创建一个指向某个变量的指针,然后传递该指针就是最终实现按引用调用的方式。因此,为了说明值和引用之间的区别,C的示例非常有效。你必须自己处理引用和解引用,因为语言C是按值调用,而C++在编译器中隐藏了这一部分样板代码。 - JimmyB
2
@Insane *"之前学习Java(它没有按引用调用)" *... 当您在函数内部改变传递的对象时会发生什么? - brian_o
显示剩余16条评论
8个回答

58

在C中无法使用引用传递,但可以使用指针作为替代方案。

是的,这是正确的。


再详细解释一下。无论你将什么作为参数传递给C函数,都只会以值传递的方式进行传递。无论是变量的值还是变量的地址。

区别在于你发送了什么。

当我们通过值传递时,我们将变量的值传递给函数。当我们通过引用传递时,我们将变量的别名传递给函数。C可以将指针传递到函数中,但这仍然是按值传递的。它复制指针的值(地址)到函数中。


  • 如果你发送的是一个变量的值,那么函数只会收到该值,并且更改它不会影响到原始值。

  • 如果你发送的是一个变量的地址,那么也只有值(在这种情况下是地址)被发送,但由于你有该变量的地址,因此可以用它来更改原始值。


例如,我们可以看一些C++代码,以了解按值调用和按引用调用之间的真正区别。取自这个网站。

// Program to sort two numbers using call by reference. 
// Smallest number is output first.

#include <iostream>
using namespace std;

// Function prototype for call by reference
void swap(float &x, float &y);

int main()
{
   float a, b;

   cout << "Enter 2 numbers: " << endl;
   cin >> a >> b;
   if(a>b) 
     swap(a,b); // This looks just like a call-by-value, but in fact
                // it's a call by reference (because of the "&" in the
                // function prototype

   // Variable a contains value of smallest number
   cout << "Sorted numbers: ";
   cout << a << " " << b << endl;
   return 0;
}

// A function definition for call by reference
// The variables x and y will have their values changed.

void swap(float &x, float &y)
// Swaps x and y data of calling function
{
   float temp;

   temp = x;
   x = y;
   y = temp;
}
在这个C++示例中,使用了引用变量(在C中不存在)。引用网站的话说:

"引用是现有变量的别名或替代名称..."

并且

"引用的主要用途是作为函数形式参数以支持传递引用..."

这与将指针用作函数参数的用法不同,因为

"指针变量(或简称指针)基本上与其他变量相同,可以存储一段数据。与存储值(如int、double、char)的普通变量不同,指针存储一个内存地址。"

因此,当通过指针发送地址并接收时,实际上只发送了值,但当发送/接收引用变量时,实际上是发送了别名或引用。


**更新:2015年11月11日**

C聊天室里有一场长时间的辩论,在阅读了评论和回答后,我意识到可能还有另一种看待这个问题的方式,另一个角度。

让我们来看一些简单的C代码:

int i;
int *p = &i;
*p = 123;

在这种情况下,可以使用术语 p的值是对i的引用。因此,如果是这种情况,那么如果我们将相同的指针 (int* p) 发送到函数中,可以认为,由于将 i 的引用发送到了函数中,因此这可以称为按引用传递

所以,这只是一种术语和看待场景的方式问题。

我不完全反对这个观点。但对于一个完全遵循书本和规则的人来说,这是错误的。


注意:更新受这个聊天启发。


7
我认为用指针来解释C语言中的按引用传递是非常误导人的,而且也没有明确地说指针在C语言中仍然是按值传递的,这样做是不太准确的。 - Insane
1
@Insane,是的。我见过大学教授用这种方式解释。一开始我也被误导了,但后来我明白了。 - Haris
4
不仅一些教授,而且一些书籍也将其称为按引用传递(pass by reference)。 - haccks
1
@Namfuak,错误地解释术语,遗漏细节,为了简单起见,对我来说比复杂的解释更危险。 - Haris
2
@Insane 我认为这并不是误导,只是在不同的上下文中使用 - “引用”现在意味着与C++和C#等语言有些不同。从更一般的角度来看,指针*确实是值的引用。它们只是没有我们现在所关联的其他属性。由于你的老师正在教授一门明确关于C(而不是C ++)的课程,所以这是完全可以的。你基本上是在抱怨使用“计算机”一词来指代计算东西的人 - 好吧,那就是它的意思!当然,今天你首先想到的是机器,但那只是你的背景。 - Luaan
显示剩余13条评论

32

在这里,“引用”是一个多义词;一般来说,引用只是指引用某个东西的方式。指针指向所指对象,并通过(按值)传递指向对象的指针来标准化C中的引用传递。

C++引入了引用类型作为更好的表达引用的方式,并在技术英语中引入了歧义,因为我们现在可以使用“按引用传递”这个术语来指代使用引用类型按引用传递对象。

在C++上下文中,我认为前一种用法已经过时。然而,在其他上下文中(例如纯C),我相信前一种用法很常见且没有歧义。


4
显然,指针是一个引用……只是不是C++中的&引用…但它引用了一块内存。 - Grady Player
1
事实上,我曾经对x86异常感到非常困惑,并在学习C++时试图弄清楚为什么我的catch(...)无法处理#DE异常(除零错误)。结果发现术语的重载类似:C++异常与CPU异常完全不相关。 - Ruslan
在C++世界中,“引用”是指特定的语言特性,我发现最好使用“句柄”来表示通用术语——它可以是引用、指针、数组索引、映射键、文件描述符等。传递句柄可能会导致句柄复制,但目标对象是共享的。 - Ben Voigt

17

C语言是否支持引用传递?

严格来说,C语言总是使用值传递。你可以自己模拟引用传递,定义接受指针的函数并在调用时使用 & 运算符,当你向函数传递数组时,编译器会为你实现类似的模拟。(通过传递一个指针而不是整个数组,参见问题6.4等)。

另一种看待这个问题的方式是,“如果参数的类型是 int *,那么传递的是一个指向整数的指针,即传递了一个值为地址的指针。”

基本上,C没有真正等价于正式引用传递或引用参数的东西。

为了证明指针是按值传递的,让我们考虑使用指针进行数字交换的示例。

int main(void)
{
    int num1 = 5;
    int num2 = 10;

    int *pnum1 = &num1;
    int *pnum2 = &num2;
    int ptemp;

    printf("Before swap, *Pnum1 = %d and *pnum2 = %d\n", *pnum1, *pnum2);
    temp = pnum1;
    pnum1 = pnum2;
    pnum2 = ptemp;

    printf("After swap, *Pnum1 = %d and *pnum2 = %d\n", *pnum1, *pnum2);
}

不是交换数字,而是交换指针。现在为此编写一个函数。

void swap(int *pnum1, int *pnum2)
{
     int *ptemp = pnum1;
     pnum1 = pnum2;
     pnum2 = temp;
}

int main(void)
{
    int num1 = 5;
    int num2 = 10;

    int *pnum1 = &num1;
    int *pnum2 = &num2;

    printf("Before swap, *pnum1 = %d and *pnum2 = %d\n", *pnum1, *pnum2);
    swap(pnum1, pnum2);

    printf("After swap, *pnum1 = %d and *pnum2 = %d\n", *pnum1, *pnum2);
}

砰!不交换了!


一些教程将指针引用称为按引用调用,这是误导性的。有关按引用传递和按值传递之间的区别,请参见此答案


+1;虽然我仍然喜欢@Haris的答案,因为它感觉更完整,但即使我搜索了很多次,我仍然没有看到这个链接! - Insane
1
@CompuChip,整数并不是通过引用传递的,而是传递了地址来访问整数,因为地址指向整数。 - Haris
@haccks 是的,显然有一些区别,例如 int* 可以为空,对于 int* 需要不同的语法,而且通过指针比通过引用更容易操作其他内存中的变量。但我认为这些只是表面上的差异,主要的观点是 int*int& 都传递了一个整数的 _身份_,而不仅仅是它的 _值_。换句话说,它传递了指向整数的指针的 _值_,而不是身份本身(f(int* p){p=0;} 不会改变任何相关内容)。 - CompuChip
@CompuChip; 是的。我在我的回答中提到了这一点的例子。 - haccks
1
@haccks 所以我们似乎又达成了共识 :) - CompuChip
显示剩余4条评论

16
根据C99标准(重点在此):
6.2.5类型
20个基于对象和函数类型可以构建任意数量的派生类型,如下所示:
...
— 可以从函数类型或对象类型派生指针类型,称为引用类型。指针类型描述了其值提供对引用类型实体的引用的对象。从引用类型T派生的指针类型有时称为“指向T的指针”。从引用类型构造指针类型称为“指针类型派生”。指针类型是完整的对象类型。
基于上述内容,你的教授所说的是有道理且正确的。指针通过值传递给函数。如果指针指向有效实体,则其值提供对实体的引用。

看我添加的例子,在那里没有显式发送地址。这将是正确的按引用传递的示例。如果你明确地发送了地址,并且它被一个指针接收,那么它实际上只是按值传递。 - Haris
8
好的,但我们又回到了C、C++的不同之处了……例如,在C语言中没有void swap(float &x, float &y)这样的写法,只有void swap(float *x, float *y) - David C. Rankin
1
@DavidC.Rankin 从语义上讲,它们是相同的。执行后,xy的值被交换。语法不同,但意思完全相同。 - luk32

11
“按引用传递”是一个概念。是的,您将指针的值传递给函数,但在这种情况下,该指针的值被用于引用变量。
有人使用螺丝刀类比来解释指针传递不应称为按引用传递,说你可以用硬币拧螺丝,但那并不意味着你会把硬币称为螺丝刀。我认为这是一个很好的类比,但他们得出了错误的结论。事实上,即使指针不同于C++引用,您仍然可以说您使用它们来进行“按引用传递”。

6

C语言仅仅传递参数的值,没有其他。但是,指针是一种可用于有效传递参数的引用的机制。就像硬币可以在你有合适的螺丝时有效地用作螺丝刀一样:一些裂纹螺丝甚至被选择以与硬币很好地配合使用。它们仍然不能将硬币变成真正的螺丝刀。

C++仍然是按值传递参数。C++引用比指针更加有限(虽然具有更多隐式转换),不能成为数据结构的一部分,其使用看起来更像通常的按引用调用代码,但是它们的语义,虽然非常适合满足按引用参数的需求,但仍然比Fortran参数或Pascal var参数等纯按引用实现更加明显,并且您可以在函数调用上下文之外完全使用引用。


5

您的教授是正确的。 按值传递时,它被复制。 通过引用传递时,它不会被复制,引用指向其所在位置。

按值传递时,将int传递给函数,它被复制,对副本的更改不会影响原始值。

通过引用传递,使用指针传递相同的int,它不会被复制,您正在修改原始值。

通过引用传递,数组始终是按引用传递的,您可以有十亿个项目在数组中,只需说出它在哪里即可,您正在修改原始值。


3

在支持按引用传递的语言中,存在一种方法可以将某些东西赋给函数,以便调用者可以使用它来识别一个变量,直到被调用的函数返回,但是只能存储在在那之后不再存在的位置。因此,调用者可以知道通过将某些函数的引用传递到变量时将执行的任何操作都将在函数返回时完成。

比较C和C#程序:

// C               // C#
int x=0;           int x=0;
foo(&x);           foo(ref x);
x++;               x++;
bar();             bar();
x++;               x++;
boz(x);            boz(x);

C编译器无法知道“bar”是否会更改x,因为foo()接收到了一个未受限制的指向它的指针。相比之下,C#编译器知道bar()不可能更改x,因为foo()只接收到了一个临时引用(在.NET术语中称为“byref”),并且没有任何复制的byref可以在foo()返回后继续存在。
传递指向事物的指针允许代码执行与传递引用语义相同的操作,但是传递引用语义使得代码可以提供关于它不会做什么的更强的保证。

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