关于C++:引用与执行中的指针有什么不同?

How is a reference different from a pointer in implementation?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Difference between pointer variable and reference variable in C++

我正在读关于Stanley Lippman的"C++对象模型里面的书"。令我困惑的是,对象的"引用"和对象的"指针"之间的区别。我知道一个引用在声明时必须被初始化,而一个指针可以留给以后的初始化。但我想知道它们之间的物理实现差异。

为什么要有"引用"机制;它是否与指针的函数重叠?在什么情况下我们应该使用指针以外的引用?多谢。


可以将引用视为隐式取消引用的常量指针(请注意)。一次引用,总是引用。它允许轻松编写代码。当然,除非引入了移动语义和R值引用。该标准没有规定如何实现引用,正如它没有规定如何实现指针一样。但大多数时候,指针是对象地址的同义词。


大多数引用都是使用指针变量实现的,即引用通常占用一个字的内存。然而,一个纯粹在本地使用的引用可以——而且常常被优化器消除。例如:

1
2
3
4
5
6
7
  struct S { int a, int b[100]; };  
  void do_something(const vector<S>& v)
  {
    for (int i=0; i<v.size(); ++i) {
        int*& p = v[i].b;
          for (int j=0; j<100; ++j) cout <<p[j];
  }

在这种情况下,P不需要存储在内存中(也许它只是存在于寄存器中,也许它会消失在指令中)。


可以时使用引用,必要时使用指针。需要使用指针的原因:

  • 它可能没有指向的对象(空指针,没有空引用)。
  • 您可能需要在其生命周期中引用不同的对象。
  • 您可能需要引用整个对象数组(但std::vector通常更好)。
  • 然而,在有些情况下,两者的用法根本没有重叠,而且您不能简单地用一个代替另一个。对于一个明显的例子,考虑以下代码:

    1
    2
    3
    4
    template <class T, size_t N>
    size_t size(T(&matrix)[N]) {
        return N;
    }

    这允许您查找数组的大小:

    1
    2
    3
    4
    5
    int array1[some_size];
    int array2[some_other_size];

    size_t n = size(array1);  // retrieves `some_size`
    size_t m = size(array2);  // retrieves `some_other_size`

    …但是如果你试图传递一个指针给它,它就不会编译:

    1
    2
    3
    int *x = new int[some_size];

    size_t n = size(x);    // won't compile

    在最好的情况下,当一部分指针要拒绝被传递给一个指针时,编写接收指针的代码似乎毫无意义。


    引用是伪装的指针,或者至少这是一种安全的方式来考虑它们。

    其中一个原因是因为C和C++没有"VAR"参数,如PASCAL等。使用引用给了你非常接近的东西,但是不同于"var",它也可以在别处使用——例如,给你快速的速记…

    1
    2
    int& alias = thing.blah [foo]->bar;
    handle (alias, alias+1, alias*2);

    引用也由许多函数返回,这意味着函数的"结果"既可以写入也可以读取,如…

    1
    2
    3
    std::deque<int>  mydeque;
    mydeque.push_back (1);
    mydeque.back () += 1;  //  increment the top item on the stack

    使用指针也可以实现相同的功能,但引用更方便。通常的术语是"句法糖"。

    关于引用的一个有用(但不完全可靠)的事实是,您不太可能意外地更改基础指针,因为访问该指针需要一些工作,而不是指向值。这对于选择-使用在代码中创建最少混乱的代码是一个有用的提示。


    指针是一个独立的值,与它指向的数据无关。(数字0x12345678作为指针值是有意义的,即使在内存中的地址0x12345678处没有有意义的数据。)因为它是一个独特的值,所以它可以自己操作:您可以递增或递减它,将它与其他指针进行比较,并将它的值打印到屏幕上,而不管它实际上是否"指向任何薄的"。"

    你不能用引用来做这些事情,因为它不是一个独特的值。它是某个现有值的别名、备用名称(可能在不同的作用域中)。这使得引用更容易使用(因为它们的行为与所引用的对象一样,不需要取消引用),而且更安全(因为,如果使用得当,它们总是引用一个对象;不存在悬空引用或空引用)。

    引用通常会在编译后的机器代码中转换为指针,这可能是真的,但您应该将其视为编译器的私有实现细节,而不是保证。引用是它们自己的概念,有自己的用例,而不仅仅是使用指针的不同方式。

    当您需要一个独特的类似指针的值,您可以独立于它指向的数据进行操作和检查时,使用指针(或者,最好是智能指针类)。当您只需要一种方法来获取存在于一个作用域中的值并使其在另一个作用域中可用时(例如,将对象传递到函数而不复制它),请使用引用。


    在大多数情况下,引用都是内部指针(尤其是在存储或传递到函数时)。它们的操作方式与指针相同,只要只使用对两者都有效的操作。

    引用的额外检查和语法糖严格来说是一个编译时特性,与类型系统非常相似(在编译过程中,大多数关于数据类型的信息都会丢失)。

    重要的区别是

    • 引用始终指向对象(不能为空)
    • 引用仅指向一个对象(而不是指向指针可能指向的数组)
    • 必须首先初始化引用(否则会出现编译错误)
    • 初始化后不能修改引用以指向其他地方
    • 当引用变量保持活动状态时,删除引用指向的对象是未定义的行为(但不是编译错误)。


    应该使用引用而不是指针的一个示例:

    1
    2
    3
    4
    5
    6
    7
    8
    enum day
    {
        Mon, Tue, Wed, Thu, Fri, Sat, Sun
    };

    day d;

    day *operator++(day *d);

    可以使用++&d调用operator++,它看起来不那么直观。

    这里的一个大问题是,所有重载的运算符函数都必须是类的成员,或者具有类型t、t&;或t const&;的参数,其中t是类或枚举类型。所以,在这种情况下day *operator++(day *d);甚至不会编译。

    使用参考,即

    1
    day &operator++(day &d);

    它可以简单地调用为++d;

    这是一篇很好的文章,我从那里得到了这个例子。

    干杯


    C++引用在很大程度上是一种句法机制。

    考虑

    1
    2
    3
    int x;
    int &y = x;
    int *z = &x;

    表达式(*z)与(y)相同。

    除了保存您键入的几个字符之外,这里进行的唯一语言操作是as-reference不能为空。


    引用对于C++程序员来说是很好的。编译器仍然将引用实现为指针,因此它不关心您是使用指针还是引用。

    正如您所说,在许多情况下,引用比指针更好,因为它们保护被调用方不受错误输入的影响(错误指针通常导致SEG错误),并且您还可以将它们设置为const,这比设置指向const的指针提供更好的更改保护。