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


当前回答

引用是常量指针。int*const a=&b与int&a=b相同。这就是为什么没有const引用,因为它已经是const,而const的引用是const int*consta。当使用-O0编译时,编译器将在这两种情况下将b的地址放在堆栈上,并且作为类的成员,它也将出现在堆栈/堆上的对象中,与您声明了常量指针时相同。使用-Ofast,可以免费优化此功能。常量指针和引用都被优化了。

与常量指针不同,无法获取引用本身的地址,因为它将被解释为它引用的变量的地址。因此,在Ofast上,表示引用的常量指针(被引用变量的地址)将始终在堆栈外进行优化,但如果程序绝对需要实际常量指针的地址(指针本身的地址,而不是指针指向的地址),即您打印常量指针的位置,那么const指针将被放置在堆栈上,以便它有一个地址。

否则它是相同的,即当您打印它指向的地址时:

#include <iostream>

int main() {
  int a =1;
  int* b = &a;
  std::cout << b ;
}

int main() {
  int a =1;
  int& b = a;
  std::cout << &b ;
}
they both have the same assembly output
-Ofast:
main:
        sub     rsp, 24
        mov     edi, OFFSET FLAT:_ZSt4cout
        lea     rsi, [rsp+12]
        mov     DWORD PTR [rsp+12], 1
        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<void const*>(void const*)
        xor     eax, eax
        add     rsp, 24
        ret
--------------------------------------------------------------------
-O0:
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-12], 1
        lea     rax, [rbp-12]
        mov     QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        mov     rsi, rax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
        mov     eax, 0
        leave
        ret

指针已经在堆栈外进行了优化,在这两种情况下,指针甚至都没有在-Ofast上取消引用,而是使用编译时值。

作为对象的成员,它们在-O0到-Ofast上是相同的。

#include <iostream>
int b=1;
struct A {int* i=&b; int& j=b;};
A a;
int main() {
  std::cout << &a.j << &a.i;
}

The address of b is stored twice in the object. 

a:
        .quad   b
        .quad   b
        mov     rax, QWORD PTR a[rip+8] //&a.j
        mov     esi, OFFSET FLAT:a //&a.i

当通过引用传递时,在-O0上,传递被引用变量的地址,因此它与通过指针传递相同,即常量指针包含的地址。On Ofast如果函数可以内联,则编译器会在内联调用中对其进行优化,因为动态范围是已知的,但在函数定义中,参数总是作为指针(期望引用引用的变量的地址)被解引用,其中它可能被另一个转换单元使用,而编译器不知道动态范围,当然,除非函数声明为静态函数,否则它不能在转换单元之外使用,然后它通过值传递,只要它没有在函数中通过引用进行修改,那么它将传递您传递的引用所引用的变量的地址,如果调用约定中有足够多的易失性寄存器,则将在一个寄存器中传递,并保持在堆栈之外。

其他回答

除非我需要以下任何一项,否则我会使用参考资料:

空指针可以用作哨兵价值,通常是一种廉价的方式避免函数重载或使用嘘声。你可以在指针上做算术。例如,p+=偏移量;

也许一些隐喻会有所帮助;在桌面屏幕空间中-

引用要求您指定实际窗口。指针需要屏幕上一块空间的位置,确保它将包含该窗口类型的零个或多个实例。

指针是保存另一个变量的内存地址的变量,其中引用是现有变量的别名。(已存在变量的另一个名称)

1.指针可以初始化为:

int b = 15;
int *q = &b;

OR

int *q;
q = &b;

其中作为参考,

int b=15;
int &c=b;

(在一个步骤中声明和初始化)

指针可以分配给null,但引用不能可以对指针执行各种算术运算,而没有所谓的参考算术。指针可以重新分配,但引用不能指针在堆栈上有自己的内存地址和大小,而引用共享相同的内存地址

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

指针

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

工具书类

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

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

文章

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

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

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

    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.