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


当前回答

简言之

指针:指针是保存另一个变量的内存地址的变量。指针需要使用*运算符解引用以访问它所指向的内存位置。-摘自Geeks for Geeks

引用:引用变量是别名,即现有变量的另一个名称。引用(如指针)也通过存储对象的地址来实现。-摘自极客对极客

另一张图片了解更多详情:

其他回答

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

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

我不能说我真的很满意这种特殊的差异。我更希望它能被允许有意义地引用任何有地址的东西,否则引用行为相同。它将允许使用引用定义一些C库函数的等价物,如memcpy。

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

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

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,而不是引用本身。赋值后,两个引用仍然引用其原始对象,并且这些对象的值现在也相同。

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

我觉得还有一点还没有在这里讨论。

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

虽然这可能看起来很肤浅,但我认为这一特性对于一些C++特性来说至关重要,例如:

模板。因为模板参数是鸭子类型的,所以类型的语法财产才是最重要的,所以通常同一个模板可以同时用于T和T&。(或std::reference_wrapper<T>,它仍然依赖于隐式转换至T&)覆盖T&和T&&的模板更为常见。L值。考虑语句str[0]=“X”;如果没有引用,它只适用于c字符串(char*str)。通过引用返回字符允许用户定义的类具有相同的符号。复制构造函数。从语法上讲,将对象传递给复制构造函数是有意义的,而不是传递给对象的指针。但复制构造函数无法按值获取对象,这将导致对同一复制构造函数的递归调用。这将引用作为此处的唯一选项。操作员过载。通过引用,可以在保留相同的中缀符号的同时,将间接指向引入运算符调用,例如运算符+(const T&a,const T&b)。这也适用于常规重载函数。

这些点赋予了C++和标准库相当大的一部分权力,因此这是参考文献的一个重要属性。

塔林♦ 说:

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

事实上你可以。

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

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