指针参数和引用参数的区别是什么?

73

这两者是否相同:

int foo(bar* p) {
  return p->someInt();
}

int foo(bar& r) {
  return r.someInt();
}

忽略空指针的可能性。无论someInt()是否是虚函数,以及它们是否传递了barbar的子类,这两个函数在功能上是否相同?

这会切割什么:

bar& ref = *ptr_to_bar;
8个回答

80

为了实现更好的语义,C++标准故意未规定引用使用指针来实现。引用更像是变量的“同义词”,而不是指向变量的指针。当编译器能够意识到在某些情况下指针会过度臃肿时,这种语义会开启一些可能的优化。

还有一些区别:

  • 你不能将NULL赋值给引用。这是一个重要的区别,也是你会更倾向于使用其中一个的主要原因。
  • 当你获取指针的地址时,你得到的是指针变量的地址。当你获取引用的地址时,你得到的是被引用的变量的地址。
  • 你不能重新分配引用。一旦初始化,它就会指向其整个生命周期内的同一个对象。

6
如果将一个空指针转换成对应类型的指针并解引用,就能将NULL赋值给一个引用。例如:int &ref = (int)NULL; 另外,如果将引用包含在联合体中并修改联合体中相应的值,则可以重新分配引用。 - Grant Peters
1
我应该补充说明,这些操作永远不应该被执行,因为当一个函数要求引用时,它从来不期望NULL,并且尽管大多数引用基本上实现方式与指针相同,但在所有情况/平台下都可能不是这种情况。 - Grant Peters
2
如果你正在给一个联合体的对应值赋值,那么你实际上并没有在赋值给一个引用,对吗? - shoosh
有助于澄清的是,将一个非const引用分配给一个const值(类似于NULL,它是一个const、宏定义的值)是不“可能”的。虽然在低级别的操作中技术上可以进行这些操作,但在生产代码中不应该这样做。C++11引入了nullptr作为类型,并鼓励普遍采用它,这是很有帮助的。 - Dr.Ransom

15

忽略所有语法糖和一个可以做但另一个做不到的可能性,以及指针和引用之间的差异(在其他答案中已经解释过了)……是的,这两个在功能上完全相同!两者都调用函数,且都能很好地处理虚函数。

不,你的代码行并没有切片。它只是将引用直接绑定到由指针指向的对象。

关于为什么要使用其中之一的一些问题:

我不打算自己想出差异,如果你想知道,请参考上述链接。


13

引用是一个常量指针,即您不能更改引用以引用其他对象。如果更改,则所引用对象的值也会更改。

例如:

       int j = 10;
       int &i = j;
       int l = 20;
       i = l; // Now value of j = 20

       int *k = &j;
       k = &l;   // Value of j is still 10

8
是的,它们在功能上是相同的。由于引用需要在使用之前将其设置为对象,因此您不必处理空指针或指向无效内存的指针。
还要注意语义上的区别:
- 当您实际上要传递普通对象但该对象太大以至于传递对象的引用而不是复制更有意义(如果您不修改该对象)时,请使用引用。 - 当您想要处理内存地址而不是对象时,请使用指针。

6

很久没有使用C ++,所以我甚至不会尝试真正回答你的问题(抱歉); 但是,Eric Lippert刚刚发布了一篇关于指针/引用的优秀文章,我想让你看看。


4
不确定是否有人回答了你在底部隐藏的第二个关于切片的问题...不会引起切片。
切片是指将派生对象分配(复制)给基类对象 - 派生类的专业化被“切掉”。请注意,我说的是对象被复制,我们不是在谈论指针被复制/分配,而是对象本身。
在你的例子中,这种情况并没有发生。你只是取消引用一个指向Bar对象的指针(从而导致Bar对象)被用作引用初始化中的rvalue。不确定我术语使用是否正确...

3
正如其他人所提到的,在实现中,引用和指针在很大程度上是相同的。有一些小细节需要注意:
  • 你不能将NULL赋值给引用(shoosh提到了这一点):这很重要,因为没有“未定义”或“无效”的引用值。

  • 你可以将临时变量作为const引用传递,但是将指向临时变量的指针传递给函数是不合法的。

例如,下面的代码是正确的:

class Thingy; // assume a constructor Thingy(int,int)
void foo(const Thingy &a)
{ 
   a.DoSomething();
}

void bar( ) 
{
  foo( Thingy(1,2) );
}

但是大多数编译器会报错

void foo2( Thingy * a);

void bar2()
{
  foo( &Thingy(1,2) );
}
  • 获取变量地址以获得指针会强制编译器将其保存到内存中。将引用分配给本地变量只是创建了一个同义词;在某些情况下,这可能允许编译器将数据保留在寄存器中并避免 load-hit-store。但是,这仅适用于本地变量--一旦通过引用传递某些内容作为参数,就无法避免将其保存到堆栈中。

void foo()
{
   int a = 5;
   // this may be slightly more efficient
   int &b = a;
   printf( "%d", ++b );
   // than this
   int *c = &a;
   printf( "%d", ++(*c) );
}
  • 同样地,__restrict 关键字 不能应用于引用,只能应用于指针。

  • 你无法使用引用进行指针算术运算,因此如果你有一个指向数组的指针,那么可以通过 p+1 来获取数组中的下一个元素,但是引用在其整个生命周期中只会指向一个东西。


1

这些函数显然不是“相同的”,但就虚拟行为而言,它们的行为类似。关于切片,这仅在处理值时发生,而不是引用或指针。


1
实际上,它们可能会生成相同的机器代码,因此从这个意义上说它们是相同的。 - jmucchiello

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