指针变量和引用变量之间的区别是什么?


当前回答

与流行观点相反,引用可能为NULL。

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

当然,使用引用要困难得多,但如果你管理它,你会为了找到它而绞尽脑汁。引用在C++中并不安全!

从技术上讲,这是一个无效引用,而不是空引用。C++不支持在其他语言中可能会发现的空引用作为概念。还有其他类型的无效引用。任何无效引用都会引发未定义行为的幽灵,就像使用无效指针一样。

实际错误是在分配给引用之前取消引用NULL指针。但我不知道任何编译器会在这种情况下生成任何错误——错误会传播到代码中更远的地方。这就是这个问题如此阴险的原因。大多数情况下,如果取消引用NULL指针,就会在该位置崩溃,而且不需要太多调试就可以解决问题。

我上面的例子简短而做作。这是一个更真实的例子。

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

我想重申,获得空引用的唯一方法是通过格式错误的代码,一旦获得了它,就会得到未定义的行为。检查空引用是没有意义的;例如,您可以尝试如果(&bar==NULL)。。。但是编译器可能会优化不存在的语句!有效引用永远不能为NULL,因此从编译器的角度来看,比较总是错误的,并且可以自由地将if子句作为死代码来消除-这是未定义行为的本质。

避免麻烦的正确方法是避免取消引用NULL指针来创建引用。这里有一种自动化的方法来实现这一点。

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

要从具有更好写作技巧的人那里了解这个问题,请参阅Jim Hyslop和Herb Sutter的空引用。

有关取消引用空指针的危险的另一个示例,请参见Raymond Chen在尝试将代码移植到另一个平台时暴露未定义的行为。

其他回答

虽然引用和指针都用于间接访问另一个值,但引用和指针之间有两个重要的区别。第一个是引用总是引用一个对象:在没有初始化引用的情况下定义引用是错误的。赋值行为是第二个重要区别:赋值给引用会更改引用绑定的对象;它不会将引用重新绑定到另一个对象。初始化后,引用始终引用同一基础对象。

考虑这两个程序片段。首先,我们将一个指针分配给另一个指针:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

在赋值ival之后,pi所寻址的对象保持不变。赋值会更改pi的值,使其指向不同的对象。现在考虑一个分配两个引用的类似程序:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

此赋值更改了ri引用的值ival,而不是引用本身。赋值后,两个引用仍然引用其原始对象,并且这些对象的值现在也相同。

引用是另一个变量的别名,而指针保存变量的内存地址。引用通常用作函数参数,因此传递的对象不是副本而是对象本身。

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 

与流行观点相反,引用可能为NULL。

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

当然,使用引用要困难得多,但如果你管理它,你会为了找到它而绞尽脑汁。引用在C++中并不安全!

从技术上讲,这是一个无效引用,而不是空引用。C++不支持在其他语言中可能会发现的空引用作为概念。还有其他类型的无效引用。任何无效引用都会引发未定义行为的幽灵,就像使用无效指针一样。

实际错误是在分配给引用之前取消引用NULL指针。但我不知道任何编译器会在这种情况下生成任何错误——错误会传播到代码中更远的地方。这就是这个问题如此阴险的原因。大多数情况下,如果取消引用NULL指针,就会在该位置崩溃,而且不需要太多调试就可以解决问题。

我上面的例子简短而做作。这是一个更真实的例子。

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

我想重申,获得空引用的唯一方法是通过格式错误的代码,一旦获得了它,就会得到未定义的行为。检查空引用是没有意义的;例如,您可以尝试如果(&bar==NULL)。。。但是编译器可能会优化不存在的语句!有效引用永远不能为NULL,因此从编译器的角度来看,比较总是错误的,并且可以自由地将if子句作为死代码来消除-这是未定义行为的本质。

避免麻烦的正确方法是避免取消引用NULL指针来创建引用。这里有一种自动化的方法来实现这一点。

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

要从具有更好写作技巧的人那里了解这个问题,请参阅Jim Hyslop和Herb Sutter的空引用。

有关取消引用空指针的危险的另一个示例,请参见Raymond Chen在尝试将代码移植到另一个平台时暴露未定义的行为。

引用的另一个有趣用法是提供用户定义类型的默认参数:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

默认风格使用引用的“bind const reference to a temporary”方面。

塔林♦ 说:

不能像使用指针那样获取引用的地址。

事实上你可以。

我引用了另一个问题的答案:

C++常见问题解答说得最好:与指针不同,一旦引用绑定到对象,就不能将其“重新放置”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址可以获得引用的地址;记住:引用是它的引用)。