指针引用和引用指针的区别

66

指针、引用和指向指针的指针在C++中有什么区别?

在什么情况下应该优先选择其中的一种?

6个回答

76

首先,指向指针的引用就像引用任何其他变量一样:

void fun(int*& ref_to_ptr)
{
    ref_to_ptr = 0; // set the "passed" pointer to 0
    // if the pointer is not passed by ref,
    // then only the copy(parameter) you received is set to 0,
    // but the original pointer(outside the function) is not affected.
}

在C++中,指向引用的指针是非法的,因为与指针不同,引用只是一个概念,允许程序员创建其他东西的别名。指针是内存中的位置,保存着其他东西的地址,但引用却不是。

现在,如果你坚持将引用视作指针来处理,可能最后一点不太清楚,比如:

int x;
int& rx = x; // from now on, rx is just like x.
// Unlike pointers, refs are not real objects in memory.
int* p = &x; // Ok
int* pr = ℞ // OK! but remember that rx is just x!
// i.e. rx is not something that exists alone, it has to refer to something else.
if( p == pr ) // true!
{ ... }

从上面的代码可以看出,当我们使用引用时,我们处理的内容并不是与它所指向的内容分离的。因此,引用的地址就是它所指向的内容的地址。这就是为什么从你所说的角度来看,并不存在所谓的引用地址。


3
最重要的一点(正如您所表达的)是,您无法获取指向引用(或引用的引用)等类似内容的指针,因为引用只是对另一个对象的别名。 - Martin York
2
好的,但是你刚刚创建了一个指向引用的指针:int* pr = ℞ 你能解释一下吗? - Mateusz
7
不,它不是。它使用一个引用(rx)来获取对象(x)的指针,而不是引用的指针。 - Ben Voigt
我建议重新构思这个答案的开头。我想知道是否可以创建一个指向引用对象的指针,谷歌告诉我“在C++中,指向引用的指针是非法的”,参考了这个答案。所以我认为int* pr = &rx是非法的。 - Mircode

60

指向指针

C++中的指针就是一个存储内存地址的值(通常是32位的值)。

假设你有一个用户输入的整数值(十进制为78,十六进制为0x4E)。

它会以类似于这样的方式存储在内存中(我特意简化了这个例子):

Memory address    Value
0x12345678        0x0000004E

如果您想创建一个指向这个值的“指针”,它在内存中的表示如下:

Memory address    Value
0x22334455        0x12345678

现在你有一个指针,其内存地址为0x22334455,其值为0x12345678,也就是用户输入的整数值(0x4E)所存储的内存地址。

假设你想创建一个指向该指针值的“指针”,看起来会像这样:

Memory address    Value
0x11335577        0x22334455

现在你在内存中有一个新的“指针”值,它存储着先前定义的指针值的内存地址。

指针可以无限制地这样创建——关键是记住指针只是另一个编译器将其解释为内存位置的值(它提供了各种访问语义,例如特殊于“指针”类型的*->)。

指向指针的引用

引用可以被看作是对另一个真实对象的视图或别名。当你创建一个指向指针的引用(称为myReference)时,你只是定义了一个可以用来访问你之前在内存中定义的指针的新名称myReference

在内部,引用使用指针实现,但这超出了你的问题所涉及的范围。

与C++中的其他类型相比,引用具有限制——例如,在创建引用时,必须始终初始化引用以“引用”实际对象,而指针可能指向无效或未初始化的内存。

指向引用的指针

这并不存在。正如前面所述,引用仅仅是另一个对象的别名。你不能“指向”引用,因为它本身不是一个对象,而只是另一个真实对象的名称。

当然,你可以有一个指向引用所引用对象的指针。但现在我们回到了普通指针领域。

关于参数的注释

当你将参数按值传递给方法或例程时,实际上是将对象的“副本”传递给了方法。在例程内部对该值进行的任何更改将在例程返回时丢失,因为该参数将被视为例程上下文中的局部变量。

如果要修改传入的参数以便客户端(调用)代码可以访问更改,必须通过指针引用传递该参数。

例如:

void myMethod(int myValue)
{
    // NOTE: This change will be lost to the caller!
    myValue = 5;
}

void myMethod2(int* myValue)
{
    // Correct way of modifying pointer parameter value
    *myValue = 5;
}

void myMethod3(int& myValue)
{
    // Correct way of modifying reference parameter value
    myValue = 5;
}

现在假设您的方法需要为指针分配内存。 您可能会尝试这样做:

void myMethod4(int* myValue)
{
    // Warning: You will lose the address of the allocated
    // memory when you return!
    myValue = new int[5];
}

但要记住,这里修改的是指针值的副本,而不是真正的指针值。由于你希望在这个例程中修改指针,而不是指针“指向”的,因此你需要将其作为“指向指针”的指针或“指向指针”的引用传递进去:

void myMethod5(int** myValue)
{
    // Correct way of allocating memory in a method
    // via pointer-to-pointer
    *myValue = new int[5];
}

void myMethod6(int*& myValue)
{
    // Correct way of allocating memory in a method
    // via reference-to-pointer
    myValue = new int[5];
}
在这两个示例中,调用 myMethod5myMethod6 的代码将通过 myValue 参数指针或引用正确获取新分配内存的内存地址。

2
“必须在创建引用时始终将其初始化为指向实际对象”这种说法并不完全正确。您可以预先将引用绑定到对象将来占据的位置。只是在对象处于完全构造状态之前,不能允许左值到右值的转换。例如:您可以使用成员变量的地址调用基类构造函数。构造顺序规定该成员尚不存在,但存储对它的引用是完全合法的。 - Ben Voigt

13

指针不可能指向引用。


6
参考是指针的抽象。对于新手来说,引用比指针更难出错,并且更高级一些。
你不需要引用。你总是可以使用指针。然而,有时候使用引用可以使代码更易读。
一个典型的初学者示例是链表。假设你有一个名为“list”的变量,它包含指向第一个元素的指针。如果你想要添加一个东西到头部,你需要给你的add()函数一个双指针,因为它需要能够修改“head”。然而,你可以使用指向指针的引用。在这里,我们希望在列表本身中使用指针,因为我们将对它们进行变异,但是如果我们传递一个对列表头的引用而不是双指针,add()函数将更清晰。
它们只是一种样式选择。如果你正在开发一个较大的项目,你应该使用项目的样式。如果没有,你可以使用任何你觉得更好的方法。但是,如果你希望成为一个稍微成功的C++程序员,你应该熟练掌握所有的样式。
还值得一提的是,你不能有一个指向引用的指针。这是因为引用实际上只是另一个变量的另一个名称,可能在某个其他范围内。拥有一个指向引用的指针是没有意义的。你真正想要的是指向原始数据的指针,不涉及引用。

1
你不需要引用。不,你从未编写过需要它们的任何代码。许多其他人使用运算符、移动语义、模板,其中变量可以是值或引用,并且应该以任何一种方式正常工作,而不需要无休止地进行近似复制,有/没有星号/和符号,通过引用传递而不使代码成为一个难看的星号/和符号汤,每个调用者都必须重写等。忽略整个语言中不能没有引用就无法工作的部分,并将其余部分视为“有时使用引用可以更容易阅读”的行为是愚蠢的。 - underscore_d

4

需要注意的是,尽管引用本身不是一个对象,因此没有可访问的地址,但引用可以包含在对象内部,而容纳引用的对象确实具有地址。

struct contains_ref
{
     int& ref;
     contains_ref(int& target) : ref(target) {}
};

“引用是别名”这个解释并不是错误的,但通常会伴随着误导性的说法。引用并不等同于原始对象。它有自己的生命周期,由包含它的作用域或对象确定,而不是它所指向的对象。引用可以超出对象的生命周期,并被用于指向在相同地址创建的新对象。
把引用看作它真正的本质——一个围绕指针的抽象,将null排除在有效值之外,并防止重新赋值,而不是什么神奇的东西。引用的唯一异常属性不是源自其指针本质的,而是临时变量的生命周期扩展。
实际上,这是因为C++没有提供任何用于引用自身而不是其目标的语法,这导致了这个后果。所有运算符,包括赋值运算符,都只是应用于目标。

我冒昧地开了一个关于你的陈述的问题,如果你有时间的话。 https://dev59.com/qIfca4cB1Zd3GeqPiWm3 - user349594

0

试着自己看看每个东西都是什么。示例程序只是打印int的值和不同实体的地址:

#include<stdio.h>

int main(){
  int myInt ;
  int *ptr_to_myInt = &myInt;
  int *ptr_to_myInt_ref = ptr_to_myInt;

  myInt = 42;

  printf("myInt is %d\n",myInt);
  printf("ptr_to_myInt is %x\n",ptr_to_myInt);
  printf("ptr_to_myInt_ref is %x\n",ptr_to_myInt_ref);
  printf("&ptr_to_myInt is %x\n",&ptr_to_myInt);

  return 0;
}

输出:

myInt is 42
ptr_to_myInt is bffff858
ptr_to_myInt_ref is bffff858
&ptr_to_myInt is bffff854

因此,指向int的指针和指向int引用的指针实际上是完全相同的东西。从代码中可以看出这一点,因为指向引用的指针只是另一种给指针取别名的方式(它表示“为我保存以下地址”)。

现在,指针也需要一些内存空间,如果您打印该指针的引用(最后一个printf语句),它只是指示指针所在的内存位置。


问题在问c++而不是c!此外,“引用”与内存地址是不同的主题,它是在c++中添加的。它们有些相关但并不相同。 - milad

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