C ++中的指针变量和引用变量之间有什么区别?


3260

我知道引用是语法糖,因此代码更易于读写。

但是有什么区别呢?


100
我认为要点2应该是“允许一个指针为NULL,但不允许引用。只有格式错误的代码才能创建NULL引用,并且其行为未定义。”
Mark Ransom

19
指针只是对象的另一种类型,和C ++中的任何对象一样,它们可以是变量。另一方面,引用绝不是对象,而只是变量。
Kerrek SB 2012年

19
编译时没有警告:int &x = *(int*)0;在gcc上。引用确实可以指向NULL。
Calmarius 2012年

20
引用是变量别名
Khaled.K,2013年

20
我喜欢第一句话完全是谬论。引用具有自己的语义。
2014年

Answers:


1704
  1. 可以重新分配一个指针:

    int x = 5;
    int y = 6;
    int *p;
    p = &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);
    

    引用不能,并且必须在初始化时分配:

    int x = 5;
    int y = 6;
    int &r = x;
    
  2. 指针在堆栈上有自己的内存地址和大小(x86上为4字节),而引用共享相同的内存地址(带有原始变量),但也占用了堆栈上的一些空间。由于引用具有与原始变量本身相同的地址,因此可以将引用视为同一变量的另一个名称。注意:指针指向的内容可以在堆栈或堆上。同上一个参考。我在此声明中的主张不是指针必须指向堆栈。指针只是保存内存地址的变量。此变量在堆栈上。由于引用在堆栈上有其自己的空间,并且地址与引用的变量相同。堆栈与堆的更多信息。这意味着编译器不会告诉您引用的真实地址。

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
    
  3. 您可以使用指向提供额外级别间接功能的指针。而引用仅提供一种间接级别。

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
    
  4. 可以直接分配指针nullptr,而引用则不能。如果您尽力而为,并且知道如何做,则可以作为参考的地址nullptr。同样,如果您尽力而为,则可以引用一个指针,然后该引用可以包含nullptr

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
    
  5. 指针可以遍历数组。您可以++用来转到指针指向的下一个项目,并+ 4转到第五个元素。无论指针指向的对象大小是多少。

  6. 指针需要取消引用*才能访问其指向的内存位置,而引用可以直接使用。指向类/结构的指针用于->访问其成员,而引用则使用.

  7. 引用不能填充到数组中,而指针可以(由用户@litb提及)

  8. 常量引用可以绑定到临时对象。指针不能(并非没有间接):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.

    这样可以const&更安全地在参数列表等中使用。


23
...但是取消引用NULL是未定义的。例如,您不能测试引用是否为NULL(例如&ref == NULL)。
Pat Notz

69
2号不是真的。引用不只是“同一变量的另一个名称”。引用可以以非常类似于指针的方式传递给函数,存储在类中。它们独立于它们所指向的变量而存在。
德里克公园

31
Brian,堆栈无关紧要。引用和指针不必在堆栈上占用空间。它们都可以在堆上分配。
德里克公园

22
Brian,变量(在这种情况下为指针或引用)需要空间这一事实并不意味着它在堆栈上需要空间。指针和引用不仅可以指向堆,而且可以实际上在堆上分配
德里克公园

38
另一个重要的区别:引用不能填充到数组中
Johannes Schaub-litb

381

什么是C ++参考(适用于C程序员

一个参考,可以看作是一个常量指针自动间接(不要用一个指针指向一个恒定值混淆!),即编译器将应用*运营商为您服务。

必须使用非null值初始化所有引用,否则编译将失败。既不可能获得引用的地址-地址运算符将返回引用值的地址-也不可能对引用进行算术运算。

C程序员可能不喜欢C ++引用,因为在发生间接调用时,或者如果通过值或指针传递参数而不查看函数签名,它将不再显而易见。

C ++程序员可能不喜欢使用指针,因为它们被认为是不安全的-尽管除了在大多数琐碎的情况下,引用实际上并没有比常量指针更安全-缺少自动间接的便利,并且具有不同的语义含义。

请考虑以下C ++常见问题解答

即使参考使用底层汇编语言的地址经常被实现,请不要认为引用作为好笑的看着指针指向的对象。引用对象。它不是指向对象的指针,也不是对象的副本。它对象。

但是,如果引用确实是对象,那么怎么会有悬挂的引用呢?在非托管语言中,引用不可能比指针更“安全”-通常只有一种方法才能可靠地跨作用域边界使用别名!

为什么我认为C ++引用有用

来自C的背景,C ++引用可能看起来有点愚蠢,但是在可能的情况下,仍然应该使用它们而不是指针:自动间接访问方便,并且引用在处理RAII时特别有用-但不是因为任何可察觉的安全性优势,而是因为它们使编写惯用代码变得不那么尴尬。

RAII是C ++的核心概念之一,但它与复制语义非常重要地交互。通过引用传递对象避免了这些问题,因为不涉及复制。如果在语言中没有引用,则必须使用指针,因为指针使用起来比较麻烦,因此违反了语言设计原则,即最佳实践的解决方案应该比替代方法更容易。


17
@kriss:不,您还可以通过按引用返回自动变量来获得悬挂的引用。
Ben Voigt

12
@kriss:在一般情况下,编译器几乎无法检测到。考虑一个成员函数,该函数返回对类成员变量的引用:这是安全的,编译器不应禁止该函数。然后,具有该类的自动实例的调用者调用该成员函数,然后返回引用。Presto:悬空参考。是的,这会造成麻烦,@ kriss:这是我的观点。许多人声称引用相对于指针的优点是引用始终有效,但事实并非如此。
Ben Voigt 2010年

4
@kriss:不,对具有自动存储期限的对象的引用与临时对象有很大不同。无论如何,我只是为您的陈述提供了一个反例,即您只能通过取消引用无效的指针来获得无效的引用。Christoph是正确的-引用没有比指针更安全,仅使用引用的程序仍可能破坏类型安全性。
Ben Voigt 2010年

7
引用不是一种指针。它们是现有对象的新名称。
2011年

18
@catphive:如果使用语言语义,则为true;如果您实际查看实现,则为true;C ++是比C语言更“神奇”的语言,如果您从引用中删除魔法,最终将得到一个指针
Christoph

190

如果您想成为真正的书呆子,则可以使用引用做某件事,而不能使用指针做这件事:延长临时对象的寿命。在C ++中,如果将const引用绑定到临时对象,则该对象的生存期将成为引用的生存期。

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

在此示例中,s3_copy复制作为连接结果的临时对象。而s3_reference本质上成为临时对象。它实际上是对一个临时对象的引用,该对象现在具有与该引用相同的生存期。

如果您尝试不这样做,const它将无法编译。您不能将非常量引用绑定到临时对象,也不能使用它的地址。


5
但是用例呢?
艾哈迈德·穆什塔克

20
好吧,s3_copy将创建一个临时文件,然后将其复制构造到s3_copy中,而s3_reference直接使用该临时文件。然后,要成为真正的书呆子,您需要查看“返回值优化”,在第一种情况下,编译器将被允许取消复制构造。
马特·普赖斯2009年

6
@digitalSurgeon:那里的魔力非常强大。const &绑定的事实延长了对象的生存期,并且只有当引用超出范围时,才调用实际引用类型的析构函数(与引用类型相比,可以是基本类型)。由于它是参考,因此在它们之间不会进行切片。
DavidRodríguez-dribeas 2010年

9
C ++ 11的更新:最后一句应为“您不能将非常量左值引用绑定到临时”,因为您可以将非常量右值引用绑定到临时,并且具有相同的寿命扩展行为。
Oktalist 2013年

4
@AhmadMushtaq:此方法的主要用途是派生类。如果不涉及继承,则最好使用值语义,由于RVO / move的构造,这将是廉价的或免费的。但是,如果您Animal x = fast ? getHare() : getTortoise()那时x将面临经典的切片问题,则Animal& x = ...可以正常工作。
亚瑟塔卡

127

除语法糖外,引用是一个const指针(不是指向a的指针const)。您必须在声明引用变量时建立它所引用的内容,并且以后不能更改它。

更新:现在我考虑了更多,有一个重要的区别。

可以通过获取其地址并使用const强制替换const指针的目标。

引用目标不能用UB以外的任何方式替换。

这应该允许编译器对参考进行更多优化。


8
我认为这是迄今为止最好的答案。其他人则谈论引用和指针,就像它们是不同的野兽,然后列出它们在行为上如何不同。恕我直言,事情做起来并不容易。我一直都将引用理解为T* const带有不同语法糖的(这恰好消除了代码中的*和&)。
卡洛·伍德

2
“可以通过获取其地址并使用const强制替换const指针的目标。” 这样做是未定义的行为。有关详细信息,请参见stackoverflow.com/questions/25209838/…
dgnuff

1
尝试更改引用的参考对象或const指针(或任何const标量)的值是等同的非法行为。您可以做什么:删除由隐式转换添加的const限定符:int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);可以。
curiousguy18年

1
区别在于UB与实际上是不可能的。C ++中没有语法可以让您更改参考点。

并非没有可能,更困难的是,您仅可以访问正在建模的指针的内存区域,即可对其进行引用并更改其内容。当然可以做到。
Nicolas Bousquet

125

与流行观点相反,可能有一个为NULL的引用。

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

当然,使用参考要困难得多-但是如果您进行管理,就会发现自己的头发而无法找到它。在C ++中,引用并不是天生的安全!

从技术上讲,这是无效引用,而不是null引用。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);

我要重申的是,获取空引用的唯一方法是通过格式错误的代码,一旦获得该引用,您将获得未定义的行为。它从来没有有意义的检查空参考; 例如,您可以尝试,if(&bar==NULL)...但是编译器可能会优化该语句而不存在!有效引用永远不能为NULL,因此从编译器的角度来看,比较始终为false,可以随意将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的Null References

有关取消引用空指针的危险的另一个示例,请参阅Raymond Chen 试图将代码移植到另一个平台时公开未定义的行为


63
有问题的代码包含未定义的行为。从技术上讲,除了设置空指针并进行比较之外,您不能对空指针执行任何操作。程序调用未定义的行为后,它可以执行任何操作,包括在向大老板进行演示之前,看起来可以正常工作。
KeithB

9
标记具有有效的参数。指针可能为NULL,因此您必须检查的说法也不是真实的:如果您说一个函数需要非NULL,则调用者必须这样做。因此,如果调用者没有调用,他将调用未定义的行为。就像马克使用了不好的参考一样
约翰内斯·绍布

13
该描述是错误的。此代码可能会或可能不会创建为NULL的引用。其行为是不确定的。它可能会创建一个完全有效的参考。可能根本无法创建任何引用。
David Schwartz

7
@David Schwartz,如果我在谈论事情必须按照标准运行的方式,那您是正确的。但这不是我要说的-我是在谈论使用非常流行的编译器的实际行为,并根据我对典型编译器和CPU体系结构的了解推断可能发生的情况。如果您认为引用比指针更安全,因为它们更安全并且不认为引用可能很糟糕,那么就像我以前一样,有一天您会被一个简单的问题所困扰。
马克·兰瑟姆

6
取消引用空指针是错误的。任何这样做的程序甚至初始化引用都是错误的。如果要初始化指针的引用,则应始终检查指针是否有效。即使此操作成功,也可以随时删除基础对象,而保留引用以引用不存在的对象,对吗?你说的是好东西。我认为真正的问题是,当您看到一个引用并且指针至少应被断言时,不需要检查引用是否为“空”。
t0rakka

114

您忘记了最重要的部分:

成员访问与指针一起使用->
成员访问与引用一起使用.

foo.bar显然优于foo->bar以同样的方式,VI显然优于Emacs的 :-)


4
@Orion Edwards>具有指针的成员访问使用->>具有引用的成员访问使用。这不是100%正确。您可以引用一个指针。在这种情况下,您将使用-> struct Node {Node * next; }; 节点*第一;// p是对指针的引用void foo(Node *&p){p-> next = first; } Node * bar = new Node; foo(bar); -OP:您熟悉右值和左值的概念吗?

3
智能指针兼有。(有关智能指针类的方法)和->(有关基础类型的方法)。
JBRWilkinson 2014年

1
@ user6105 Orion Edwards语句实际上是100%正确的。“访问已取消引用的指针的成员”指针没有任何成员。指针所指的对象具有成员,对这些对象的访问正是->提供对指针的引用的方式,就像指针本身一样。
Max Truxa 2015年

1
这是为什么,.并且->与vi和emacs有关:)
artm 16'Aug

10
@artM-这是个玩笑,对于非英语母语者来说可能没有意义。我很抱歉。解释一下,vi是否比emacs更好是完全主观的。有些人认为vi优越得多,而另一些人则认为恰好相反。同样,我认为使用.优于使用->,但就像vi与emacs一样,它完全是主观的,您无法证明任何内容
Orion Edwards

73

引用与指针非常相似,但是它们经过专门设计,有助于优化编译器。

  • 设计引用时,使编译器更容易跟踪哪些引用别名哪些变量。两个主要功能非常重要:没有“引用算术”和没有重新分配引用。这些允许编译器找出哪些引用在编译时别名哪些变量。
  • 引用可以引用没有内存地址的变量,例如编译器选择放入寄存器的变量。如果使用局部变量的地址,则编译器很难将其放入寄存器中。

举个例子:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

一个优化的编译器可能意识到我们正在大量访问a [0]和a [1]。希望将算法优化为:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

为了进行这样的优化,它需要证明在调用过程中什么都不能改变array [1]。这很容易做到。我从不小于2,所以array [i]永远不能引用array [1]。mayModify()被赋予a0作为参考(别名为array [0])。因为没有“引用”算法,所以编译器只需要证明maynyModify永远不会获得x的地址,并且证明没有任何改变array [1]。

还必须证明,当我们在a0中有一个临时寄存器副本时,将来的调用将无法读取/写入a [0]。通常很难证明这一点,因为在许多情况下,很明显,引用从未存储在像类实例这样的永久性结构中。

现在用指针做同样的事情

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

行为是相同的。只是现在很难证明,也许Modify永远不会修改array [1],因为我们已经给了它一个指针。那只猫从书包里掏出来。现在,它必须做更加困难的证明:对maymayModify的静态分析,以证明它永远不会写入&x +1。它还必须证明,它永远不会保存可以引用array [0]的指针。一样棘手。

现代编译器在静态分析方面越来越好,但是帮助他们并使用引用总是很高兴。

当然,除非进行此类巧妙的优化,否则编译器的确会在需要时将引用转换为指针。

编辑:发布此答案的五年后,我发现了实际的技术差异,其中参考文献有所不同,而不仅仅是查看同一寻址概念的不同方式。引用可以通过指针无法修改的方式来修改临时对象的寿命。

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

通常,临时对象(例如通过调用创建的对象)createF(5)会在表达式末尾销毁。但是,通过将该对象绑定到引用refC,C ++将延长该临时对象的寿命,直到ref超出范围。


是的,身体必须可见。但是,确定maybeModify不占用任何与之相关的地址的过程x比证明没有发生一堆指针算术要容易得多。
Cort Ammon 2013年

我相信优化器已经做了“没有发生一堆指针运算”检查,原因还有很多。
Ben Voigt 2013年

“引用与指针非常相似”(在语义上,在适当的上下文中),但是就生成的代码而言,仅在某些实现中,而不是通过任何定义/要求。我知道您已经指出了这一点,并且我在实践上并不反对您的任何文章,但是我们已经遇到了很多问题,人们对速记描述的阅读过多,例如“引用就像/通常作为指针实现” 。
underscore_d

我觉得有人错误地将标记为已过时的评论void maybeModify(int& x) { 1[&x]++; },上面的其他评论正在讨论
Ben Voigt

67

实际上,引用不是真的像指针。

编译器保留对变量的“引用”,将名称与内存地址相关联。这是在编译时将任何变量名转换为内存地址的工作。

创建引用时,仅告诉编译器您已为指针变量分配了另一个名称。这就是为什么引用不能“指向null”的原因,因为变量不能是,也不能是。

指针是变量;它们包含其他一些变量的地址,或者可以为null。重要的是,指针具有一个值,而引用仅具有它所引用的变量。

现在对真实代码进行一些解释:

int a = 0;
int& b = a;

在这里,您没有创建另一个指向的变量a;您只是将另一个名称添加到具有值的内存内容中a。该内存现在有两个名称,ab,并且可以使用任一名称进行寻址。

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

调用函数时,编译器通常会为要复制到的参数生成内存空间。函数签名定义应创建的空间,并提供应用于这些空间的名称。将参数声明为引用只是告诉编译器在方法调用期间使用输入变量存储空间,而不是分配新的存储空间。说您的函数将直接操作在调用范围中声明的变量似乎很奇怪,但是请记住,在执行编译的代码时,不再有范围了。只有普通的平面内存,您的功能代码可以操纵任何变量。

现在,在某些情况下,例如在使用extern变量时,编译器可能无法知道引用。因此,引用可以或可以不被实现为基础代码中的指针。但是在我给您的示例中,很可能不会使用指针来实现它。


2
引用是对I值的引用,不一定是对变量的引用。因此,它比真正的别名(编译时构造)更接近指针。可以引用的表达式的示例为* p甚至* p ++

5
是的,我只是指出一个事实,即引用不一定总是像新指针那样将新变量压入堆栈。
文森特·罗伯特,

1
@VincentRobert:它的作用与指针相同...如果内联该函数,则将优化引用和指针。如果有函数调用,则需要将对象的地址传递给函数。
Ben Voigt '02

1
int * p = NULL; int&r = * p; 指向NULL的引用;如果(r)的{} - >动臂;)
SREE

2
这种对编译阶段的关注似乎很好,直到您记住可以在运行时传递引用时为止,此时静态别名不再存在。(然后,通常将引用实现为指针,但标准不需要此方法。)
underscore_d 2015年

44

引用永远不可能NULL


10
有关反例,请参阅Mark Ransom的答案。这是关于引用的最常断言的神话,但这是一个神话。根据标准,唯一的保证是,当您具有NULL引用时,您将立即拥有UB。但这就像在说:“这辆车很安全,永远不会下车。(无论如何,如果您将它转向越野,我们将不承担任何责任。它可能会爆炸。”)
cmaster -恢复莫妮卡

17
@cmaster:在有效程序中,引用不能为null。但是指针可以。这不是神话,这是事实。
user541686 2014年

8
@Mehrdad是的,有效的程序仍在路上。但是没有流量障碍可以强制您的程序实际执行。道路的大部分实际上缺少标记。因此,在夜间下车极为容易。它是用于调试这样的错误至关重要的,你知道会发生这种情况:它崩溃程序前,就像一个空指针可以空引用可以传播。而且,当您执行此操作时,您将拥有类似段错误的代码void Foo::bar() { virtual_baz(); }。如果您不知道引用可能为null,则无法将null追溯到其起源。
cmaster-恢复莫妮卡2014年

4
int * p = NULL; int&r = * p; 指向NULL的引用;如果(r)的{} - >动臂;) -
SREE

10
@sree int &r=*p;是未定义的行为。在这一点上,你没有一个“参考指向NULL,”你有一个程序可以不再思索可言
cdhowie

34

虽然引用和指针都用于间接访问另一个值,但是引用和指针之间有两个重要区别。首先是引用始终引用对象:定义引用而不初始化它是错误的。分配的行为是第二​​个重要区别:分配给引用会更改引用所绑定到的对象;它不会将引用重新绑定到另一个对象。初始化后,引用始终引用相同的基础对象。

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

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

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


“引用总是指对象”完全是错误的
Ben Voigt

31

如果您不熟悉以抽象甚至学术的方式学习计算机语言,则语义上的差异可能会显得深奥。

在最高级别上,引用的想法是它们是透明的“别名”。您的计算机可能使用地址来使它们工作,但是您不必担心:您应该将它们视为现有对象的“只是另一个名称”,并且语法反映了这一点。它们比指针严格,因此与要创建悬挂指针时相比,编译器可以在要创建悬挂引用时更可靠地警告您。

除此之外,指针和引用之间当然还有一些实际的区别。使用它们的语法显然是不同的,并且您不能“重新安置”引用,不能引用虚无或具有指向引用的指针。


26

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

    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. 

19

占用多少空间都没有关系,因为您实际上看不到它会占用任何空间的任何副作用(不执行代码)。

另一方面,引用和指针之间的主要区别是分配给const引用的临时对象将一直存在,直到const引用超出范围为止。

例如:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

将打印:

in scope
scope_test done!

这是允许ScopeGuard运行的语言机制。


1
您不能使用引用的地址,但这并不意味着它们实际上并不占用空间。除非进行优化,否则它们肯定可以。
Lightness Races in Orbit

2
尽管有影响,但“在堆栈上的引用根本不占用任何空间”显然是错误的。
Lightness Races in Orbit

1
@Tomalak好吧,这也取决于编译器。但是,是的,这有点令人困惑。我认为仅删除它就不会造成混乱。
MSN

1
在任何给定的特定情况下,它可能会也可能不会。因此,“没有”作为绝对断言是错误的。我就是这么说的 :) [我不记得标准在这个问题上说了什么;推荐成员的规则可能会赋予“推荐可能占用空间”的一般规则,但我在海滩上没有携带标准的副本:D]
轻轨比赛(

19

这是基于教程的。所写的内容更加清楚:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

只是要记住,

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

而且,正如我们几乎可以参考任何指针教程一样,指针是指针算术支持的对象,它使指针类似于数组。

看下面的语句,

int Tom(0);
int & alias_Tom = Tom;

alias_Tom可以理解为alias of a variable(与typedef,不同,alias of a typeTom。也可以忘记此类声明的术语是创建的引用Tom


1
并且,如果类具有引用变量,则应使用nullptr或初始化列表中的有效对象对其进行初始化。
Misgevolution

1
这个答案的措辞太混乱了,以至于没有太多实际用途。另外,@ Misgevolution,您是否认真建议读者使用来初始化参考nullptr?您是否实际阅读过该线程的其他部分,或者...?
underscore_d

1
我不好,对不起我说的那句话。到那时我一定已经被剥夺了睡眠。“使用nullptr初始化”是完全错误的。
Misgevolution,2015年

18

引用不是给某些内存提供的另一个名称。这是一个不变的指针,在使用时会自动取消引用。基本上可以归结为:

int& j = i;

在内部成为

int* const j = &i;

13
这不是C ++标准所说的,并且编译器不需要按照您的答案描述的方式实现引用。
jogojapan

@jogojapan:对于C ++编译器实现引用的任何有效方式,也是对它实现const指针的有效方式。这种灵活性并不能证明引用和指针之间存在差异。
Ben Voigt 2015年

2
@BenVoigt可能确实是其中一个的任何有效实现也是另一个的有效实现,但是从这两个概念的定义来看,这显然不是遵循的。一个很好的答案将从定义开始,并证明了为什么关于两者最终相同的主张是正确的。这个答案似乎是对其他一些答案的一种评论。
jogojapan 2015年

引用赋予对象的另一个名称。只要您不知道其中的区别,就可以使编译器具有任何类型的实现,这称为“按条件”规则。这里的重要部分是您无法分辨差异。如果发现指针没有存储空间,则说明编译器有错误。如果您发现引用没有存储空间,则说明编译器仍然符合要求。
sp2danny

17

直接答案

C ++中的参考是什么?类型的某些特定实例不是对象类型

C ++中的指针是什么?类型的某些特定实例是对象类型

根据对象类型的ISO C ++定义

一个对象类型是(可能CV -qualified)类型不函数类型,不是引用类型,而不是CV空隙。

可能很重要的一点是要知道,对象类型是C ++中Universe类型的顶级类别。参考也是顶级类别。但是指针不是。

复合类型的上下文中一起提到了指针和引用。这基本上是由于声明语语法的性质所继承的,该声明语语法是从(并扩展的)C继承的,它没有引用。(此外,自C ++ 11起,引用的声明符不止一种,而指针仍是“ unityped”的:&+ &&vs *。)因此,在这种情况下,用类似C的样式起草“扩展”特定的语言在某种程度上是合理的。(我仍然会争论说,声明符的语法在新的语言设计中浪费了很多,使人类用户和实现都感到沮丧。因此,它们都不具备内置的条件。语法表达能力。不过,这是关于PL设计的完全不同的主题。)

否则,将指针与引用一起限定为特定类型的指针就无关紧要了。除了语法相似性以外,它们仅共享很少的公共属性,因此在大多数情况下无需将它们放在一起。

请注意,以上声明仅提及“指针”和“引用”作为类型。关于它们的实例(例如变量),存在一些有趣的问题。还有太多的误解。

顶级类别的差异已经可以揭示许多不直接与指针相关的具体差异:

  • 对象类型可以具有顶级cv限定符。参考不能。
  • 根据抽象机器的语义,对象类型的变量确实会占用存储空间。引用不必占用存储空间(有关详细信息,请参见下面有关误解的部分)。
  • ...

关于引用的其他一些特殊规则:

  • 复合声明符对引用的限制更大。
  • 引用可能会崩溃
    • &&在模板参数推导过程中基于参考折叠的参数特殊规则(作为“转发参考”)允许参数“完美转发”
  • 引用在初始化时有特殊的规则。通过扩展,声明为引用类型的变量的生存期可以与普通对象不同。
    • 顺便说一句,其他std::initializer_list一些类似初始化的上下文也遵循一些类似的参考生存期扩展规则。这是另一种蠕虫。
  • ...

误解

句法糖

我知道引用是语法糖,因此代码更易于读写。

从技术上讲,这是完全错误的。引用不是C ++中任何其他功能的语法糖,因为在没有任何语义差异的情况下,引用不能被其他功能完全替代。

(类似地,λ-表达 s为在c任何其他特征++,因为它不能被精确地与像“未指定”属性模拟语法糖所捕获的变量的声明顺序,这可能是重要的,因为这样的变量的初始化顺序可以是重大。)

从严格的意义上讲,C ++只有几种语法糖。一个实例(从C继承)是内置的(非重载)运算符[],它的定义与内置运算符unary *和binary 完全具有特定组合形式的相同语义属性+

存储

因此,指针和引用都使用相同数量的内存。

上面的陈述是完全错误的。为避免这种误解,请改用ISO C ++规则:

来自[intro.object] / 1

...物体在其构造期间,整个生命周期和破坏期间都占据一个存储区域。...

来自[dcl.ref] / 4

未确定参考是否需要存储。

请注意,这些是语义属性。

语用学

即使在语言设计的意义上说指针没有足够的资格与引用放在一起,仍然有一些论点使其在其他上下文中(例如,在对参数类型进行选择时)在它们之间进行选择值得商bat。

但这还不是全部。我的意思是,除了要考虑的指针还是引用之外,还有更多的事情需要考虑。

如果您不必坚持这种过分具体的选择,那么在大多数情况下,答案很简单:您不必使用指针,而不必使用指针。指针通常很糟糕,因为它们暗示了太多您不期望的事情,并且它们将依赖太多隐式假设,从而破坏了代码的可维护性和(甚至)可移植性。不必要地依赖指针绝对是一种不好的样式,从现代C ++的意义上应该避免这种情况。重新考虑您的目的,您最终会发现在大多数情况下指针是最后一种功能

  • 有时,语言规则明确要求使用特定类型。如果要使用这些功能,请遵守规则。
    • 拷贝构造函数需要特定类型的简历 - &引用类型作为第一个参数的类型。(通常应该是const合格的。)
    • 移动构造函数需要特定类型的简历 - &&引用类型作为第一个参数的类型。(通常应该没有限定符。)
    • 运算符的特定重载需要引用或非引用类型。例如:
      • operator=作为特殊成员函数重载时,需要引用类型类似于copy / move构造函数的第一个参数。
      • 后缀++需要虚拟int
      • ...
  • 如果您知道值传递(即使用非引用类型)就足够了,请直接使用它,尤其是在使用支持C ++ 17强制复制省略的实现时。(警告:但是,要详尽地论证这种必要性可能会非常复杂。)
  • 如果要使用所有权来操作某些句柄,请使用unique_ptrshared_ptr,或者使用自制的智能指针(如果您要求它们不透明,也可以单独使用自制指针),而不要使用原始指针。
  • 如果要在某个范围内进行某些迭代,请使用迭代器(或标准库尚未提供的某些范围),而不要使用原始指针,除非您确信原始指针在特定情况下会做得更好(例如,减少标头依赖)案件。
  • 如果您知道传递值就足够了,并且想要一些显式的可为空的语义,请使用wrapper之类的std::optional,而不是原始指针。
  • 如果您知道由于上述原因传递值不理想,并且您不希望使用可为空的语义,请使用{lvalue,rvalue,forwarding} -references。
  • 即使您确实想要像传统指针这样的语义,也经常有一些更合适的东西,例如observer_ptr在图书馆基础TS中。

唯一的例外无法使用当前语言解决:

  • 当您在上面实现智能指针时,您可能必须处理原始指针。
  • 特定的语言互操作例程需要指针,例如operator new。(然而,CV - void*仍然是相当不同的,比普通的对象指针更安全,因为它排除了意外的指针算术,除非你是靠一些非符合扩展上void*像GNU的。)
  • 可以从lambda表达式转换函数指针而无需捕获,而函数引用则不能。对于这种情况,即使您故意不希望使用可为空的值,也必须在非通用代码中使用函数指针。

因此,在实践中,答案是如此明显:如有疑问,请避免使用指针。仅在出于非常明显的原因没有其他更合适的理由时才需要使用指针。除了上面提到的一些例外情况外,此类选择几乎总是不完全是特定于C ++的(但可能是特定于语言实现的)。这样的实例可以是:

  • 您必须使用旧式(C)API。
  • 您必须满足特定C ++实现的ABI要求。
  • 您必须基于特定实现的假设,在运行时与不同的语言实现(包括各种程序集,语言运行时和某些高级客户端语言的FFI)进行互操作。
  • 在某些极端情况下,您必须提高翻译(编译和链接)的效率。
  • 在某些极端情况下,您必须避免符号膨胀。

语言中立警告

如果您通过某些Google搜索结果(不是特定于C ++)来查看问题,则很可能是错误的位置。

在C ++中引用是很“奇怪”,因为它本质上不是一流的:他们将被视为对对象或功能被称为所以他们就没有机会来支持喜欢成为左操作数的一些一流的操作的成员访问运算符独立于所引用对象的类型。其他语言对其参考文献可能有也可能没有类似的限制。

C ++中的引用可能不会保留不同语言之间的含义。例如,引用通常并不暗示像C ++那样的值具有非null属性,因此此类假设可能不适用于某些其他语言(并且您会很容易找到反例,例如Java,C#,...)。

通常,在不同编程语言中的引用之间仍然存在一些公共属性,但让我们将其留给SO中的其他问题。

(附带说明:这个问题可能比涉及任何“ C样”语言的问题早,例如ALGOL 68 vs. PL / I。


16

在C ++中可以对指针进行引用,但不可能相反,这意味着对引用的指针不可行。对指针的引用提供了一种更干净的语法来修改指针。看这个例子:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

并考虑上述程序的C版本。在C语言中,您必须使用指向指针的指针(多个间接),这会导致混乱,并且程序可能看起来很复杂。

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

请访问以下内容,以获取有关指针引用的更多信息:

就像我说的那样,指向引用的指针是不可能的。尝试以下程序:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}

15

我使用引用,除非我需要以下任何一种:

  • 空指针可以用作标记值,通常是避免函数重载或使用布尔值的廉价方法。

  • 您可以对指针进行算术运算。例如,p += offset;


5
您可以写&r + offset在哪里r被声明为参考
MM

14

指针和引用之间有一个根本的区别,我没有看到有人提到过:引用在函数参数中启用了按引用传递语义。指针虽然一开始不可见,但它却没有:它们仅提供按值传递语义。这已经非常漂亮描述这篇文章

问候,&rzej


1
引用和指针都是句柄。它们都为您提供了对象通过引用传递的语义,但是句柄已被复制。没有不同。(还有其他处理方式,例如在字典中查找的键)
Ben Voigt

我也曾经这样想过。但是请参阅链接的文章,说明为什么不是这样。
Andrzej

2
@Andrzj:那只是我评论中单句的很长的版本:句柄被复制了。
Ben Voigt 2013年

我需要有关此“句柄已复制”的更多说明。我了解一些基本概念,但实际上我认为引用和指针都指向变量的内存位置。就像别名存储值变量并随着变量的值更改或其他更新它一样更新吗?我是新手,请不要将其标记为一个愚蠢的问题。
阿西姆(Asim)2013年

1
@Andrzej错误。在这两种情况下,都会发生值传递。引用按值传递,指针按值传递。否则会使新手感到困惑。
Miles Rout 2014年

13

冒着增加混乱的风险,我想输入一些信息,我确定它主要取决于编译器如何实现引用,但是在gcc的情况下,引用只能指向堆栈上的变量实际上是不正确的,例如:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

输出以下内容:

THIS IS A STRING
0xbb2070 : 0xbb2070

如果您发现甚至内存地址都完全相同,则表示引用已成功指向堆上的变量!现在,如果您真的想变得怪异,这也可以:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

输出以下内容:

THIS IS A STRING

因此,引用是一个隐藏的指针,它们都只是存储一个内存地址,该地址指向的地址无关紧要,如果我调用std :: cout << str_ref;会发生什么呢?调用delete&str_ref之后?好吧,显然它可以很好地编译,但是在运行时会导致分段错误,因为它不再指向有效变量,我们实质上有一个残破的引用仍然存在(直到超出范围),但是没有用。

换句话说,引用不过是一种指针,它抽象了指针机制,使其更安全,更易于使用(没有意外的指针数学,不会混淆'。'和'->'等),假设您不要像我上面的例子一样尝试任何废话;)

现在,无论编译器如何处理引用,它都将始终具有某种内在的指针,因为引用必须引用特定内存地址处的特定变量才能使其按预期工作,因此无法避免(因此)术语“参考”)。

使用引用要记住的唯一重要规则是,必须在声明时定义它们(除了标头中的引用之外,在这种情况下,必须在构造函数中定义引用,然后将其包含在其中)构造,现在定义它为时已晚)。

请记住,我上面的示例仅仅是说明引用是什么的示例,您永远都不想以这种方式使用引用!为了正确使用参考,这里已经有很多答案,直指头绪


13

另一个区别是您可以具有指向void类型的指针(这意味着指向任何内容的指针),但禁止引用void。

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

我不能说我对这种特殊的差异感到非常满意。我更希望对带有地址的任何内容进行含义引用,否则对引用的行为相同。它将允许使用引用来定义一些等效的C库函数,例如memcpy。


12

另外,可以以不同于指针的方式处理作为内联函数的参数的引用。

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

许多编译器在内联指针版本1时实际上会强制写入内存(我们正在显式获取地址)。但是,它们会将参考保留在最佳寄存器中。

当然,对于未内联的函数,指针和引用会生成相同的代码,并且如果函数未修改和返回内联函数,则按值传递内在函数总是比按引用传递内在函数更好。


10

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

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;
}

默认风格使用引用的“将const引用绑定到临时”方面。


10

该程序可能有助于理解问题的答案。这是引用“ j”和指向变量“ x”的指针“ ptr”的简单程序。

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

运行该程序,看看输出,您将了解。

另外,请保留10分钟并观看以下视频:https : //www.youtube.com/watch?v=rlJrrGV0iOg


10

我觉得这里还没有提到另一点。

与指针不同,引用在语法上等同于它们引用的对象,即,可以应用于对象的任何操作都可以使用引用进行操作,并且语法完全相同(当然是初始化)。

尽管这可能看起来很肤浅,但我相信此属性对于许多C ++功能至关重要,例如:

  • 模板。由于模板参数是鸭子类型的,因此所有类型的语法属性都很重要,因此通常同一模板可以与T和一起使用T&
    (或std::reference_wrapper<T>仍然依赖于隐式转换为T&
    模板盖二者T&T&&甚至更常见。

  • 左值。考虑一下str[0] = 'X';没有引用的语句,该语句仅适用于c字符串(char* str)。通过引用返回字符允许用户定义的类具有相同的符号。

  • 复制构造函数。从语法上讲,将对象传递给复制构造函数是有意义的,而不是将指针传递给对象。但是复制构造函数无法按值获取对象-它将导致对同一复制构造函数的递归调用。这将引用作为唯一的选择。

  • 操作员重载。通过引用,可以将间接引入到操作员调用中,例如,operator+(const T& a, const T& b)同时保留相同的中缀表示法。这也适用于常规的重载函数。

这些要点使C ++和标准库有相当大的一部分,因此这是引用的主要属性。


隐式强制转换 ”强制转换是一种语法构造,它存在于语法中;演员表总是很明确的
Curiousguy

9

指针和引用之间存在非常重要的非技术区别:通过指针传递给函数的参数比通过非常量引用传递给函数的参数更明显。例如:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

回到C中,看起来像的调用fn(x)只能按值传递,因此它绝对不能修改x;要修改参数,您需要传递一个指针fn(&x)。因此,如果某个参数前面没有一个参数,&您将知道它不会被修改。(相反,&意味着已修改,这是不正确的,因为有时您必须通过const指针传递大型只读结构。)

有人认为这是读取代码时非常有用的功能const,即使函数从不期望使用a ,指针参数也应始终用于可修改的参数,而不是非引用nullptr。就是说,那些人争辩fn3()说不应该像上面那样使用函数签名。Google的C ++风格指南就是一个例子。


8

也许一些隐喻会有所帮助。在桌面屏幕空间的上下文中-

  • 参考要求您指定一个实际的窗口。
  • 指针需要在屏幕上留出一定空间,以确保它包含零个或多个该窗口类型的实例。

6

指针和参考之间的区别

指针可以初始化为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 "="

1
我们仍然可以const int& i = 0
Revolver_Ocelot

1
在这种情况下,引用将仅用于读取,即使使用“ const_cast”,我们也无法修改此const引用,因为“ const_cast”仅接受指针而不是引用。
dhokar.w

1
const_cast可以很好地与引用配合使用:coliru.stacked-crooked.com/a/eebb454ab2cfd570
Revolver_Ocelot

1
您要强制引用而不是强制引用,请尝试此操作;const int&i =; const_cast <int>(i); 我尝试丢弃引用的常数,以使可以为引用写入和分配新值,但这是不可能的。请集中!
dhokar.w

5

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


4

简而言之,我们可以说引用是变量的替代名称,而指针是保存另一个变量地址的变量。例如

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.