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


当前回答

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

    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. 

其他回答

除了这里的所有答案,

可以使用引用实现运算符重载:

my_point operator+(const my_point& a, const my_point& b)
{
  return { a.x + b.x, a.y + b.y };
}

使用参数作为值将创建原始参数的临时副本,而使用指针将不会调用此函数,因为指针算法。

与流行观点相反,引用可能为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在尝试将代码移植到另一个平台时暴露未定义的行为。

指针和引用之间的差异

指针可以初始化为0,而引用不能初始化。事实上,引用也必须引用对象,但指针可以是空指针:

int* p = 0;

但我们不能有int&p=0;以及int&p=5;。

事实上,要正确执行此操作,我们必须首先声明并定义了一个对象,然后才能引用该对象,因此前面代码的正确实现将是:

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

另一个重要的点是,我们可以在不初始化的情况下声明指针,但是在引用的情况下,不能这样做,因为引用必须始终引用变量或对象。然而,这样使用指针是有风险的,因此通常我们检查指针是否确实指向某个对象。在引用的情况下,不需要这样的检查,因为我们已经知道在声明期间引用对象是强制性的。

另一个区别是指针可以指向另一个对象,但是引用总是引用同一个对象

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

另一点:当我们有一个类似STL模板的模板时,此类类模板将始终返回一个引用,而不是指针,以便使用运算符[]轻松读取或分配新值:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

关于引用和指针的一些关键相关细节

指针

使用一元后缀声明符运算符声明指针变量*指针对象被分配一个地址值,例如,通过分配给数组对象、使用一元前缀运算符的对象地址或分配给另一个指针对象的值指针可以重新分配任意次数,指向不同的对象指针是保存指定地址的变量。它占用的内存存储量等于目标机器体系结构的地址大小例如,可以通过增量或加法运算符对指针进行数学操作。因此,可以使用指针等进行迭代。要获取或设置指针引用的对象的内容,必须使用一元前缀运算符*来取消引用它

工具书类

引用在声明时必须初始化。引用使用一元后缀声明符运算符&声明。初始化引用时,可以使用它们将直接引用的对象的名称,而不需要一元前缀运算符&一旦初始化,引用就不能通过赋值或算术操作指向其他对象无需取消引用该引用以获取或设置其引用的对象的内容对引用的赋值操作操作它指向的对象的内容(初始化后),而不是引用本身(不改变它指向的位置)对引用的算术运算操作它指向的对象的内容,而不是引用本身(不会改变它指向的位置)在几乎所有的实现中,引用实际上都存储为被引用对象的内存中的地址。因此,它占用的内存大小与目标机器体系结构的地址大小相同,就像指针对象一样

尽管指针和引用的实现方式几乎相同,但编译器对它们的处理方式不同,导致了上述所有差异。

文章

我最近写的一篇文章比我在这里展示的要详细得多,对这个问题非常有帮助,特别是关于记忆中的事情是如何发生的:

数组、指针和引擎罩下的引用深度文章

不同之处在于,非常量指针变量(不要与指向常量的指针混淆)可能会在程序执行过程中的某个时间发生更改,需要使用指针语义(&,*)运算符,而引用只能在初始化时设置(这就是为什么您只能在构造函数初始化器列表中设置它们,但不能以其他方式设置它们),并使用普通值访问语义。基本上,引用是为了支持运算符重载而引入的,正如我在一些非常古老的书中所读到的那样。正如有人在这个线程中所说的,指针可以设置为0或任何您想要的值。0(NULL,nullptr)表示指针初始化为空。取消引用空指针是错误的。但实际上,指针可能包含一个不指向某个正确内存位置的值。反过来,引用试图不允许用户初始化对某个无法引用的对象的引用,因为您总是向其提供正确类型的右值。尽管有很多方法可以将引用变量初始化到错误的内存位置,但最好不要深入了解细节。在机器级,指针和参考都通过指针统一工作。让我们假设在基本参考中是句法糖。rvalue引用与此不同,它们自然是堆栈/堆对象。