c - C 中的指针变量和引用变量 之间的区别是什么

  显示原文与译文双语对照的内容

我知道引用是语法糖,所以代码更易于阅读和写入。

但有什么区别?


从下面的答案和链接总结:

  1. 一个指针可以是re-assigned任意次数,而引用不能在绑定后为 re-seated 。
  2. 指针不能指向( NULL ),而引用总是引用一个对象。
  3. 不能获取指向指针的引用地址。
  4. 没有"参考算法"( 但你可以获取引用的对象的地址,并在 &obj + 5 上执行指针算法) 。

要澄清误解:

每 C++ C++的标准是非常小心,以避免指定一个编译器必须实现如何引用,但编译器实现定义为指针的引用。 也就是说,声明如下:


int &ri = i;

如果不是完全优化掉,分配相同数量的存储为指针,并将我的地址放到该存储。

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

作为一般规则

  • 使用函数参数和返回类型中的引用定义有用的和self-documenting接口。
  • 使用指针实现算法和数据结构。

有趣的阅读:

时间:

  1. 指针可以是 re-assigned:

    
    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字节) 上有自己的内存地址和大小,而引用共享相同的内存地址( 使用原始变量),同时占用堆栈上的一些空间。 由于引用具有与原始变量本身相同的地址,所以可以安全地将引用视为相同变量的另一个名称。 注意:指针指向的指针可以在堆栈或者堆上。 同样的引用。我在这里声明中的声明不是指针必须指向堆栈。 指针只是保存内存地址的变量。 这里变量位于堆栈上。 因为引用在堆栈上有自己的空间,并且因为地址与它引用的变量相同。 更多关于堆栈 vs 堆列表的信息。 这意味着有一个引用的真实地址,编译器不会告诉你。

    
    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. 指针可以直接指定为空,而引用不能。 如果你努力的努力,你知道如何,你可以使引用的地址为空。 同样,如果你努力的努力,你可以有一个指向指针的引用,然后引用可以包含 NULL 。

    
    int *p = NULL;
    int &r = NULL; <--- compiling error
    
    
  5. 指针可以遍历一个数组,你可以使用 ++ 转到指针指向的下一个项目,+ 4 指向 5元素。 无论对象指向的大小是多大。

  6. 指针需要用 * 取消引用,以访问它指向的内存位置,而引用可以直接使用。 指向类/结构的指针使用 -> 来访问它,而引用使用 .

  7. 指针是保存内存地址的变量。 不管引用如何实现,引用都具有与其引用的项相同的内存地址。

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

  9. 可以将常数引用绑定到 temporaries 。 指针不能( 没有间接的间接性):

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

    这使得 const& 在参数列表等方面更安全。

什么是 C++ 参考( C 程序员的 )

于you,一个相关参考可以视为一个常量指针 ( 不要与指向常量值的指针混淆) 具有自动功能,由 IE 编译器将应用该 * operator. !

所有引用必须用non-null值初始化,否则编译将失败。 不可能获取引用的地址- 地址运算符将返回引用值的地址,也不可能在引用上执行算法。

在函数C的程序员可能不喜欢 C++ signatures,引用,因为它将不再是明显的间接寻址时发生的或者如果参数获取通过值或者通过指针而不 looking.

使用指针,因为它们被认为是不安全的- 虽然引用都是没有真正 C++ 程序员可能不喜欢任何更安全比为常量指针,不过在最繁琐的情况下- 缺少的便利性,自动间接寻址,并执行不同的语义内涵。

请考虑以下来自 C++ FAQ的声明:

尽管在底层的汇编语言,请经常通过使用地址的引用并不 想到作为一个看起来很滑稽的指针,该指针指向对象的引用。 一个参考在物体上。 它不是指向对象的指针,也不是对象的副本。 它在物体上。

但是如果一个参考真的是对象,那么它怎么会有悬空引用? 在非托管语言中,引用是不可能是指针的任何'更安全'- 通常没有一种方法可以在范围范围内可靠地输入值 !

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

从C 背景来看,C++ 引用可能看起来有点傻,但仍然应该使用它们而不是指针: 自动间接寻址是方便,和引用变得特别有用当处理 RAII - 但也可以因感知到的安全优势,而是因为它使书写惯用代码得方便些。

柏林是 C++的核心概念之一,但它与复制语义交互。 通过引用传递对象避免了不涉及复制的问题。 如果引用的图标并不语言,他们一定得亲自来使用指针代替,这是更不便于使用,从而违反语言设计原理,采用best-practice解决方案应该是对方案要容易得多。

如果你想成为 pedantic,你可以用一个不能用指针做的引用做一件事: 延长临时对象的生存期。 在 C++ 中,如果将一个常数引用绑定到一个临时对象,则该对象的生存期将成为引用的生存期。


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 来尝试它,它就会失败。 你不能将non-const引用绑定到临时对象,也不能将它的地址用于。

流行的观点相反,有一个引用是空的。


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

毫无疑问,这样就更难执行的任何操作参考- 但如果你的对它的进行管理,你将撕裂你的头发,想要找到它。

编辑:一些说明。

从技术上讲,这是一个无效引用,不是空引用。 C++ 不支持空引用作为概念,你可以在其他语言中找到。 还有其他种类的无效引用。

实际的错误是在赋值到引用之前取消引用空指针。 但是我不知道任何编译器会在那个条件下产生任何错误- 错误会沿着代码进一步传播到一个点。 这个问题使得这个问题变得如此的隐蔽。 大多数情况下,如果你取消引用一个空指针,就会在那个位置崩溃,并不需要太多的调试来解决它。

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


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

void Foo(const MyClass & bar)
{
. . .
 bar.DoSomething(a,Long,list,of,parameters);//crash occurs here - obvious why?
}

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

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

的编辑: 一些进一步的想法。

我想重申的是,获取空引用的唯一方法是通过格式不正确的代码,一旦有了它,你就得到了未定义的行为。 它从不就很有意义了( 对于空引用检查;例如你可以尝试 if(&bar==NULL)... 但编译器可能会优化该语句出的存在 ! 有效的引用永远不能为空,因此从编译器的角度来看,比较总是假的- 这是未定义行为的本质。

避免麻烦的正确方法是避免引用空指针来创建引用。 这是一个自动完成这个任务的方法。


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

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

你忘记了最重要的部分

带指针的member-access使用->
带引用的member-access 。

foo.bar 清楚是否具备合格 foo->bar 六清楚是否具备合格 emacs: - ) 以同样的方式,

实际上,引用并不是一个指针。

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

创建句柄时,它仅仅是告诉编译器我们分配到指针变量,这就是为什么引用不能"指向空",另一个名称,因为一个变量不能,而不会和。

指针是变量,它们包含其他变量的地址,或者可以为空。 重要的是指针有一个值,而引用只有一个变量,它正在引用。

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


int a = 0;
int& b = a;

在这里你不是正在创建另一个变量,它指向,你只是将另一个名称添加到内存内容持有的值。 这个内存现在有两个名字,a 和b,可以用名字来称呼。


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

int a;
increment(a);

调用函数时,编译器通常为要复制的参数生成内存空间。 函数签名定义应该创建的空格,并给出这些空间应该使用的名称。 仅仅将参数声明为引用就会告诉编译器在方法调用期间使用输入变量内存空间而不是分配新的内存空间。 似乎有些奇怪是为了表示你的函数将直接的维护在调用作用域中声明的变量的,但请记住,当执行已经编译的代码,并没有其他用途范围,这里只是普通的平面内存和你的函数代码能操控任何变量。

使用外部问题得到解决variable,现在可能会有一些情况下,你的编译器在编译时可能无法知道该引用,就像 when. 因此,引用可能在底层代码中作为指针实现,也可能不能实现。 但是在我给你的例子中,它很可能不是用指针实现的。

虽然引用和指针被用来间接访问另一个值,但引用和指针之间有两个重要的区别。 第一个是引用总是引用一个对象: 定义引用而不初始化它是一个错误。 赋值行为是第二个重要区别: 给引用赋值将引用绑定到的对象;它不重新绑定对另一个对象的引用。 初始化后,引用总是引用相同的底层对象。

考虑这两个程序 Fragment 。 在第一种情况下,我们将一个指针分配给另一个指针:


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

赋值后,ival,由寻址的对象保持不变。 赋值更改了圆周率的值,使它的指向另一个对象。 现在考虑一个分配两个引用的类似程序:


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

这里赋值更改 ival,由ri引用的值,而不是引用本身。 在赋值之后,两个引用仍然引用它们的原始对象,这些对象的值现在相同。

...