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



14 Answers

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

一个引用可以被认为是一个常量指针 (不要与指向一个常量值的指针混淆),也就是说编译器会为你应用*运算符。

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

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

C ++程序员可能不喜欢使用指针,因为它们被认为是不安全的 - 尽管除了最琐碎的情况之外,引用实际上并不比常量指针更安全 - 缺乏自动间接的便利性并带有不同的语义内涵。

考虑C ++ FAQ中的以下陈述:

尽管通常使用底层汇编语言中的地址来实现引用,但请不要将引用看作指向对象的有趣的指针。 参考对象。 它不是指向对象的指针,也不是对象的副本。 这对象。

但是如果一个参考文献真的是这个客体,怎么会有悬而未决的参考? 在非托管语言中,引用不可能比指针更“安全” - 通常不会在范围边界上可靠地别名化值的方法!

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

从C背景来看,C ++引用可能看起来像一个有点愚蠢的概念,但仍应该在可能的情况下使用它们而不是指针:自动间接方便,并且引用在处理RAII时特别有用 - 但不是因为任何安全感优点,而是因为他们使写惯用代码不那么笨拙。

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

Question

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

但有什么区别?

下面的答案和链接摘要:

  1. 指针可以重新分配任意次数,而绑定后不能重新引用引用。
  2. 指针可以指向无处( NULL ),而引用始终指向一个对象。
  3. 你不能像指针那样使用引用的地址。
  4. 没有“引用算术”(但是你可以把一个引用指向的对象的地址作为&obj + 5指针算术)。

澄清一种误解:

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

int &ri = i;

如果没有完全优化请将相同数量的存储分配给指针,并将i的地址放入该存储中。

所以,一个指针和一个引用都占用相同数量的内存。

作为基本规则,

  • 在函数参数和返回类型中使用引用来定义有用和自我记录的接口。
  • 使用指针来实现算法和数据结构。

有趣的阅​​读:




Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain. Despite above similarities, there are following differences between references and pointers.

References are less powerful than pointers

1)一旦创建了一个引用,就不能再引用另一个对象; 它不能被重新安装。 这通常是用指针完成的。

2)引用不能为NULL。 指针通常被设置为NULL来表示它们没有指向任何有效的东西。

3)声明时必须初始化引用。 指针没有这样的限制

Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don't have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn't need pointers.

参考文献更安全,更易于使用:

1)更安全:由于引用必须被初始化,像野指针这样的野生引用不太可能存在。 仍然可以提供不涉及有效位置的引用

2)易于使用:引用不需要取消引用操作符来访问该值。 它们可以像正常变量一样使用。 只有在申报时才需要'&'运营商。 另外,对象引用的成员可以使用点运算符('。')来访问,与需要使用箭头运算符( - >)来访问成员的指针不同。

Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++ .




The difference is that non-constant pointer variable(not to be confused with a pointer to constant) may be changed at some time during program execution, requires pointer semantics to be used(&,*) operators, while references can be set upon initialization only(that's why you can set them in constructor initializer list only, but not somehow else) and use ordinary value accessing semantics. Basically references were introduced to allow support for operators overloading as I had read in some very old book. As somebody stated in this thread - pointer can be set to 0 or whatever value you want. 0(NULL, nullptr) means that the pointer is initialized with nothing. It is an error to dereference null pointer. But actually the pointer may contain a value that doesn't point to some correct memory location. References in their turn try not to allow a user to initialize a reference to something that cannot be referenced due to the fact that you always provide rvalue of correct type to it. Although there are a lot of ways to make reference variable be initialized to a wrong memory location - it is better for you not to dig this deep into details. On machine level both pointer and reference work uniformly - via pointers. Let's say in essential references are syntactic sugar. rvalue references are different to this - they are naturally stack/heap objects.




除了语法糖之外,引用是一个const指针( 不是指向const指针)。 您必须在声明参考变量时确定其引用的内容,并且以后不能再更改它。

更新:现在我再想一想,有一个重要的区别。

一个const指针的目标可以被取代它的地址并使用const转换。

参考目标不能以UB以外的任何方式替换。

这应该允许编译器对引用做更多的优化。




A reference is an alias for another variable whereas a pointer holds the memory address of a variable. References are generally used as function parameters so that the passed object is not the copy but the object itself.

    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. 



It doesn't matter how much space it takes up since you can't actually see any side effect (without executing code) of whatever space it would take up.

On the other hand, one major difference between references and pointers is that temporaries assigned to const references live until the const reference goes out of scope.

例如:

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!

This is the language mechanism that allows ScopeGuard to work.




与流行观点相反,可能有一个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);

我想重申,获取空引用的唯一方法是通过格式错误的代码,一旦你拥有它,就会得到未定义的行为。 检查空引用是毫无意义的; 例如,你可以尝试if(&bar==NULL)...但编译器可能优化语句不存在! 有效的引用永远不能为NULL,所以从编译器的角度来看,比较总是假的,并且可以自由地将if子句作为死代码去除 - 这是未定义行为的本质。

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

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 在尝试将代码移植到另一个平台时暴露未定义的行为




I feel like there is yet another point that hasn't been covered here.

Unlike the pointers, references are syntactically equivalent to the object they refer to, ie any operation that can be applied to an object works for a reference, and with the exact same syntax (the exception is of course the initialization).

While this may appear superficial, I believe this property is crucial for a number of C++ features, for example:

  • Templates . Since template parameters are duck-typed, syntactic properties of a type is all that matters, so often the same template can be used with both T and T& .
    (or std::reference_wrapper<T> which still relies on an implicit cast to T& )
    Templates that cover both T& and T&& are even more common.

  • Lvalues . Consider the statement str[0] = 'X'; Without references it would only work for c-strings ( char* str ). Returning the character by reference allows user-defined classes to have the same notation.

  • Copy constructors . Syntactically it makes sense to pass objects to copy constructors, and not pointers to objects. But there is just no way for a copy constructor to take an object by value - it would result in a recursive call to the same copy constructor. This leaves references as the only option here.

  • Operator overloads . With references it is possible to introduce indirection to an operator call - say, operator+(const T& a, const T& b) while retaining the same infix notation. This also works for regular overloaded functions.

These points empower a considerable part of C++ and the standard library so this is quite a major property of references.




Another difference is that you can have pointers to a void type (and it means pointer to anything) but references to void are forbidden.

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

I can't say I'm really happy with this particular difference. I would much prefer it would be allowed with the meaning reference to anything with an address and otherwise the same behavior for references. It would allow to define some equivalents of C library functions like memcpy using references.




Another interesting use of references is to supply a default argument of a user-defined type:

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

The default flavor uses the 'bind const reference to a temporary' aspect of references.




A reference to a pointer is possible in C++, but the reverse is not possible means a pointer to a reference isn't possible. A reference to a pointer provides a cleaner syntax to modify the pointer. 看看这个例子:

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

And consider the C version of the above program. In C you have to use pointer to pointer (multiple indirection), and it leads to confusion and the program may look complicated.

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

Visit the following for more information about reference to pointer:

As I said, a pointer to a reference isn't possible. Try the following program:

#include <iostream>
using namespace std;

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



尽管引用和指针都用于间接访问另一个值,但引用和指针之间有两个重要区别。 首先是引用始终引用对象:定义引用而不初始化引用是错误的。 赋值的行为是第二​​个重要区别:赋值给引用会改变引用绑定到的对象; 它不重新绑定对另一个对象的引用。 一旦初始化,引用总是指向相同的基础对象。

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

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

This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.




引用与指针非常相似,但它们专门用于优化编译器。

  • 引用的设计使编译器能够更容易地跟踪哪些引用别名使用哪个变量。 两个主要特征非常重要:没有“参考算术”,也没有重新分配参考。 这些允许编译器确定哪些引用在编译时别名了哪些变量。
  • 允许引用指代没有内存地址的变量,例如编译器选择将其放入寄存器的变量。 如果你使用局部变量的地址,编译器很难将它放入寄存器。

举个例子:

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

一个优化编译器可能会意识到我们正在访问一个[0]和一个[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]。 maybeModify()被赋予a0作为参考(别名数组[0])。 因为没有“引用”算法,编译器只需证明MaybeModify永远不会获得x的地址,并且已经证明没有任何更改数组[1]。

它还必须证明,当我们在a0中有一个临时寄存器副本时,未来的调用将无法读取/写入[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];
    }
}

行为是一样的; 只是现在证明可能修改数组[1]是很难的,因为我们已经给它一个指针了; 猫在袋子外面。 现在它必须做更加困难的证明:一个对MaybeModify的静态分析,以证明它永远不会写入&x + 1。它还必须证明它永远不会保存可以引用array [0]的指针,这只是如棘手。

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

当然,除了这种聪明的优化之外,编译器确实会在需要时将引用转换为指针。




I have an analogy for references and pointers, think of references as another name for an object and pointers as the address of an object.

// receives an alias of an int, an address of an int and an int value
public void my_function(int& a,int* b,int c){
    int d = 1; // declares an integer named d
    int &e = d; // declares that e is an alias of d
    // using either d or e will yield the same result as d and e name the same object
    int *f = e; // invalid, you are trying to place an object in an address
    // imagine writting your name in an address field 
    int *g = f; // writes an address to an address
    g = &d; // &d means get me the address of the object named d you could also
    // use &e as it is an alias of d and write it on g, which is an address so it's ok
}



At the risk of adding to confusion, I want to throw in some input, I'm sure it mostly depends on how the compiler implements references, but in the case of gcc the idea that a reference can only point to a variable on the stack is not actually correct, take this for example:

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

Which outputs this:

THIS IS A STRING
0xbb2070 : 0xbb2070

If you notice even the memory addresses are exactly the same, meaning the reference is successfully pointing to a variable on the heap! Now if you really want to get freaky, this also works:

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).*/
}

Which outputs this:

THIS IS A STRING

Therefore a reference IS a pointer under the hood, they both are just storing a memory address, where the address is pointing to is irrelevant, what do you think would happen if I called std::cout << str_ref; AFTER calling delete &str_ref? Well, obviously it compiles fine, but causes a segmentation fault at runtime because it's no longer pointing at a valid variable, we essentially have a broken reference that still exists (until it falls out of scope), but is useless.

In other words, a reference is nothing but a pointer that has the pointer mechanics abstracted away, making it safer and easier to use (no accidental pointer math, no mixing up '.' and '->', etc.), assuming you don't try any nonsense like my examples above ;)

Now regardless of how a compiler handles references, it will always have some kind of pointer under the hood, because a reference must refer to a specific variable at a specific memory address for it to work as expected, there is no getting around this (hence the term 'reference').

The only major rule that's important to remember with references is that they must be defined at the time of declaration (with the exception of a reference in a header, in that case it must be defined in the constructor, after the object it's contained in is constructed it's too late to define it).

Remember, my examples above are just that, examples demonstrating what a reference is, you would never want to use a reference in those ways! For proper usage of a reference there are plenty of answers on here already that hit the nail on the head




Related