C++中的指针变量和引用变量有什么区别?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/57483/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What are the differences between a pointer variable and a reference variable in C++?
提问by prakash
I know references are syntactic sugar, so code is easier to read and write.
我知道引用是语法糖,所以代码更容易阅读和编写。
But what are the differences?
但有什么区别呢?
回答by Brian R. Bondy
A pointer can be re-assigned:
int x = 5; int y = 6; int *p; p = &x; p = &y; *p = 10; assert(x == 5); assert(y == 10);
A reference cannot, and must be assigned at initialization:
int x = 5; int y = 6; int &r = x;
A pointer has its own memory address and size on the stack (4 bytes on x86), whereas a reference shares the same memory address (with the original variable) but also takes up some space on the stack. Since a reference has the same address as the original variable itself, it is safe to think of a reference as another name for the same variable. Note: What a pointer points to can be on the stack or heap. Ditto a reference. My claim in this statement is not that a pointer must point to the stack. A pointer is just a variable that holds a memory address. This variable is on the stack. Since a reference has its own space on the stack, and since the address is the same as the variable it references. More on stack vs heap. This implies that there is a real address of a reference that the compiler will not tell you.
int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2);
You can have pointers to pointers to pointers offering extra levels of indirection. Whereas references only offer one level of indirection.
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);
A pointer can be assigned
nullptr
directly, whereas reference cannot. If you try hard enough, and you know how, you can make the address of a referencenullptr
. Likewise, if you try hard enough, you can have a reference to a pointer, and then that reference can containnullptr
.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
Pointers can iterate over an array; you can use
++
to go to the next item that a pointer is pointing to, and+ 4
to go to the 5th element. This is no matter what size the object is that the pointer points to.A pointer needs to be dereferenced with
*
to access the memory location it points to, whereas a reference can be used directly. A pointer to a class/struct uses->
to access it's members whereas a reference uses a.
.References cannot be stuffed into an array, whereas pointers can be (Mentioned by user @litb)
Const references can be bound to temporaries. Pointers cannot (not without some indirection):
const int &x = int(12); //legal C++ int *y = &int(12); //illegal to dereference a temporary.
This makes
const&
safer for use in argument lists and so forth.
可以重新分配指针:
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;
指针在堆栈上有自己的内存地址和大小(x86 上为 4 个字节),而引用共享相同的内存地址(与原始变量)但也占用堆栈上的一些空间。由于引用与原始变量本身具有相同的地址,因此可以安全地将引用视为同一变量的另一个名称。注意:指针指向的内容可以在堆栈或堆上。同上参考。我在此声明中的主张并不是指针必须指向堆栈。指针只是一个保存内存地址的变量。这个变量在堆栈上。由于引用在堆栈上有自己的空间,并且地址与它引用的变量相同。有关堆栈与堆的更多信息. 这意味着编译器不会告诉您引用的真实地址。
int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2);
您可以拥有指向提供额外间接级别的指针的指针。而引用仅提供一级间接。
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);
指针可以直接赋值
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
指针可以遍历数组;您可以使用
++
转到指针指向的下一个项目,并+ 4
转到第 5 个元素。这与指针指向的对象大小无关。指针需要取消引用
*
才能访问它指向的内存位置,而引用可以直接使用。指向类/结构的指针用于->
访问其成员,而引用使用.
.引用不能填充到数组中,而指针可以(由用户@litb 提及)
常量引用可以绑定到临时对象。指针不能(不是没有一些间接性):
const int &x = int(12); //legal C++ int *y = &int(12); //illegal to dereference a temporary.
这使得
const&
在参数列表等中使用更安全。
回答by Christoph
What's a C++ reference (for C programmers)
什么是 C++ 参考(对于 C 程序员)
A referencecan be thought of as a constant pointer(not to be confused with a pointer to a constant value!) with automatic indirection, ie the compiler will apply the *
operator for you.
一个参考,可以看作是一个常量指针自动间接(不要用一个指针指向一个恒定值混淆!),即编译器将应用*
运营商为您服务。
All references must be initialized with a non-null value or compilation will fail. It's neither possible to get the address of a reference - the address operator will return the address of the referenced value instead - nor is it possible to do arithmetics on references.
所有引用都必须用非空值初始化,否则编译将失败。获取引用的地址既不可能——地址运算符将返回引用值的地址——也不可能对引用进行算术运算。
C programmers might dislike C++ references as it will no longer be obvious when indirection happens or if an argument gets passed by value or by pointer without looking at function signatures.
C 程序员可能不喜欢 C++ 引用,因为当间接发生时,或者如果参数通过值或指针传递而不查看函数签名时,引用将不再明显。
C++ programmers might dislike using pointers as they are considered unsafe - although references aren't really any safer than constant pointers except in the most trivial cases - lack the convenience of automatic indirection and carry a different semantic connotation.
C++ 程序员可能不喜欢使用指针,因为它们被认为是不安全的——尽管引用实际上并不比常量指针更安全,除非在最琐碎的情况下——缺乏自动间接的便利性并带有不同的语义内涵。
Consider the following statement from the C++ FAQ:
考虑C++ FAQ 中的以下语句:
Even though a reference is often implemented using an address in the underlying assembly language, please do notthink of a reference as a funny looking pointer to an object. A reference isthe object. It is not a pointer to the object, nor a copy of the object. It isthe object.
即使参考使用底层汇编语言的地址经常被实现,请不要不认为引用作为好笑的看着指针指向的对象。引用是对象。它不是指向对象的指针,也不是对象的副本。它是对象。
But if a reference reallywere the object, how could there be dangling references? In unmanaged languages, it's impossible for references to be any 'safer' than pointers - there generally just isn't a way to reliably alias values across scope boundaries!
但是,如果引用确实是对象,那么怎么会有悬空引用呢?在非托管语言中,引用不可能比指针“更安全”——通常没有一种方法可以跨范围边界可靠地对值进行别名!
Why I consider C++ references useful
为什么我认为 C++ 引用很有用
Coming from a C background, C++ references may look like a somewhat silly concept, but one should still use them instead of pointers where possible: Automatic indirection isconvenient, and references become especially useful when dealing with RAII- but not because of any perceived safety advantage, but rather because they make writing idiomatic code less awkward.
来自 C 背景,C++ 引用可能看起来有点愚蠢的概念,但仍然应该尽可能使用它们而不是指针:自动间接很方便,并且在处理RAII时引用变得特别有用- 但不是因为任何感知安全优势,而是因为它们使编写惯用代码不那么尴尬。
RAII is one of the central concepts of C++, but it interacts non-trivially with copying semantics. Passing objects by reference avoids these issues as no copying is involved. If references were not present in the language, you'd have to use pointers instead, which are more cumbersome to use, thus violating the language design principle that the best-practice solution should be easier than the alternatives.
RAII 是 C++ 的核心概念之一,但它与复制语义的交互非常重要。通过引用传递对象避免了这些问题,因为不涉及复制。如果语言中不存在引用,则必须改用指针,这使用起来更麻烦,从而违反了最佳实践解决方案应该比替代方案更容易的语言设计原则。
回答by Matt Price
If you want to be really pedantic, there is one thing you can do with a reference that you can't do with a pointer: extend the lifetime of a temporary object. In C++ if you bind a const reference to a temporary object, the lifetime of that object becomes the lifetime of the reference.
如果你真的想学究,你可以用引用做一件事,而你不能用指针做:延长临时对象的生命周期。在 C++ 中,如果将 const 引用绑定到临时对象,则该对象的生命周期将成为引用的生命周期。
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
In this example s3_copy copies the temporary object that is a result of the concatenation. Whereas s3_reference in essence becomes the temporary object. It's really a reference to a temporary object that now has the same lifetime as the reference.
在这个例子中 s3_copy 复制了作为连接结果的临时对象。而 s3_reference 本质上变成了临时对象。它实际上是对现在与引用具有相同生命周期的临时对象的引用。
If you try this without the const
it should fail to compile. You cannot bind a non-const reference to a temporary object, nor can you take its address for that matter.
如果你在没有const
它的情况下尝试它应该无法编译。您不能将非常量引用绑定到临时对象,也不能为此获取其地址。
回答by Matt Price
Apart from syntactic sugar, a reference is a const
pointer (notpointer to a const
). You must establish what it refers to when you declare the reference variable, and you cannot change it later.
除了语法糖,引用是一个const
指针(不是指向a 的指针const
)。您必须在声明引用变量时确定它所指的对象,并且以后不能更改它。
Update: now that I think about it some more, there is an important difference.
更新:现在我想多了,有一个重要的区别。
A const pointer's target can be replaced by taking its address and using a const cast.
const 指针的目标可以通过获取其地址并使用 const 转换来替换。
A reference's target cannot be replaced in any way short of UB.
参考的目标不能以任何方式代替 UB。
This should permit the compiler to do more optimization on a reference.
这应该允许编译器对引用进行更多优化。
回答by Mark Ransom
Contrary to popular opinion, it is possible to have a reference that is NULL.
与流行观点相反,可能有一个 NULL 引用。
int * p = NULL;
int & r = *p;
r = 1; // crash! (if you're lucky)
Granted, it is much harder to do with a reference - but if you manage it, you'll tear your hair out trying to find it. References are notinherently safe in C++!
诚然,使用参考文献要困难得多——但如果你管理它,你会费力去寻找它。引用在 C++中并不是本质安全的!
Technically this is an invalid reference, not a null reference. C++ doesn't support null references as a concept as you might find in other languages. There are other kinds of invalid references as well. Anyinvalid reference raises the spectre of undefined behavior, just as using an invalid pointer would.
从技术上讲,这是一个无效的引用,而不是一个空引用。C++ 不支持将空引用作为您在其他语言中可能会发现的概念。还有其他类型的无效引用。任何无效的引用都会引发未定义行为的幽灵,就像使用无效指针一样。
The actual error is in the dereferencing of the NULL pointer, prior to the assignment to a reference. But I'm not aware of any compilers that will generate any errors on that condition - the error propagates to a point further along in the code. That's what makes this problem so insidious. Most of the time, if you dereference a NULL pointer, you crash right at that spot and it doesn't take much debugging to figure it out.
实际错误在于在分配给引用之前取消引用 NULL 指针。但我不知道任何编译器会在这种情况下产生任何错误 - 错误会传播到代码中更远的一点。这就是使这个问题如此阴险的原因。大多数情况下,如果您取消引用一个 NULL 指针,就会在那个位置崩溃,并且不需要太多调试就可以搞清楚。
My example above is short and contrived. Here's a more real-world example.
我上面的例子简短而做作。这是一个更真实的例子。
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);
I want to reiterate that the only way to get a null reference is through malformed code, and once you have it you're getting undefined behavior. It nevermakes sense to check for a null reference; for example you can try if(&bar==NULL)...
but the compiler might optimize the statement out of existence! A valid reference can never be NULL so from the compiler's view the comparison is always false, and it is free to eliminate the if
clause as dead code - this is the essence of undefined behavior.
我想重申一下,获得空引用的唯一方法是通过格式错误的代码,一旦拥有它,就会得到未定义的行为。它从来没有有意义的检查空参考; 例如,您可以尝试,if(&bar==NULL)...
但编译器可能会优化该语句不存在!有效的引用永远不能为 NULL,因此从编译器的角度来看,比较总是错误的,并且可以自由地将if
子句消除为死代码 - 这是未定义行为的本质。
The proper way to stay out of trouble is to avoid dereferencing a NULL pointer to create a reference. Here's an automated way to accomplish this.
避免麻烦的正确方法是避免取消引用 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));
For an older look at this problem from someone with better writing skills, see Null Referencesfrom Jim Hyslop and Herb Sutter.
要从具有更好写作技巧的人那里更早地看到这个问题,请参阅Jim Hyslop 和 Herb Sutter 的Null References。
For another example of the dangers of dereferencing a null pointer see Exposing undefined behavior when trying to port code to another platformby Raymond Chen.
有关取消引用空指针的危险的另一个示例,请参阅Raymond Chen在尝试将代码移植到另一个平台时暴露未定义的行为。
回答by Orion Edwards
回答by Cort Ammon
References are very similar to pointers, but they are specifically crafted to be helpful to optimizing compilers.
引用与指针非常相似,但它们专门用于帮助优化编译器。
- References are designed such that it is substantially easier for the compiler to trace which reference aliases which variables. Two major features are very important: no "reference arithmetic" and no reassigning of references. These allow the compiler to figure out which references alias which variables at compile time.
- References are allowed to refer to variables which do not have memory addresses, such as those the compiler chooses to put into registers. If you take the address of a local variable, it is very hard for the compiler to put it in a register.
- 引用的设计使得编译器可以更轻松地跟踪哪些引用为哪些变量设置了别名。两个主要特征非常重要:没有“引用算法”和没有引用的重新分配。这些允许编译器在编译时找出哪些引用对哪些变量进行了别名。
- 允许引用引用没有内存地址的变量,例如编译器选择放入寄存器的变量。如果获取局部变量的地址,编译器很难将其放入寄存器中。
As an example:
举个例子:
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];
}
}
An optimizing compiler may realize that we are accessing a[0] and a[1] quite a bunch. It would love to optimize the algorithm to:
优化编译器可能会意识到我们正在访问大量的 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
}
To make such an optimization, it needs to prove that nothing can change array[1] during the call. This is rather easy to do. i is never less than 2, so array[i] can never refer to array[1]. maybeModify() is given a0 as a reference (aliasing array[0]). Because there is no "reference" arithmetic, the compiler just has to prove that maybeModify never gets the address of x, and it has proven that nothing changes array[1].
要进行这样的优化,需要证明在调用过程中没有任何东西可以改变 array[1]。这很容易做到。i 永远不会小于 2,所以 array[i] 永远不能引用 array[1]。mayModify() 被给定 a0 作为参考(别名数组 [0])。因为没有“引用”算法,编译器只需要证明maybeModify 永远不会得到x 的地址,并且已经证明没有任何改变array[1]。
It also has to prove that there are no ways a future call could read/write a[0] while we have a temporary register copy of it in a0. This is often trivial to prove, because in many cases it is obvious that the reference is never stored in a permanent structure like a class instance.
它还必须证明,当我们在 a0 中有一个临时寄存器副本时,未来的调用无法读/写 a[0]。这通常很容易证明,因为在许多情况下,很明显引用永远不会存储在像类实例这样的永久结构中。
Now do the same thing with pointers
现在用指针做同样的事情
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];
}
}
The behavior is the same; only now it is much harder to prove that maybeModify does not ever modify array[1], because we already gave it a pointer; the cat is out of the bag. Now it has to do the much more difficult proof: a static analysis of maybeModify to prove it never writes to &x + 1. It also has to prove that it never saves off a pointer that can refer to array[0], which is just as tricky.
行为是一样的;只是现在要证明maybeModify 从来没有修改过array[1] 就难多了,因为我们已经给了它一个指针;猫从袋子里出来了。现在它必须做更困难的证明:对maybeModify 进行静态分析以证明它永远不会写入&x + 1。它还必须证明它永远不会保存可以引用数组[0] 的指针,这只是一样棘手。
Modern compilers are getting better and better at static analysis, but it is always nice to help them out and use references.
现代编译器在静态分析方面越来越好,但帮助他们并使用引用总是很好的。
Of course, barring such clever optimizations, compilers will indeed turn references into pointers when needed.
当然,除非进行如此巧妙的优化,否则编译器确实会在需要时将引用转换为指针。
EDIT: Five years after posting this answer, I found an actual technical difference where references are different than just a different way of looking at the same addressing concept. References can modify the lifespan of temporary objects in a way that pointers cannot.
编辑:发布此答案五年后,我发现了一个实际的技术差异,其中引用不同于查看相同寻址概念的不同方式。引用可以以指针不能的方式修改临时对象的生命周期。
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
Normally temporary objects such as the one created by the call to createF(5)
are destroyed at the end of the expression. However, by binding that object to a reference, ref
, C++ will extend the lifespan of that temporary object until ref
goes out of scope.
通常,临时对象(例如通过调用创建的对象)createF(5)
在表达式结束时被销毁。但是,通过将该对象绑定到引用,ref
C++ 将延长该临时对象的生命周期,直到ref
超出范围。
回答by Vincent Robert
Actually, a reference is not really like a pointer.
实际上,引用并不像指针。
A compiler keeps "references" to variables, associating a name with a memory address; that's its job to translate any variable name to a memory address when compiling.
编译器保留对变量的“引用”,将名称与内存地址相关联;它的工作是在编译时将任何变量名转换为内存地址。
When you create a reference, you only tell the compiler that you assign another name to the pointer variable; that's why references cannot "point to null", because a variable cannot be, and not be.
创建引用时,您只告诉编译器您为指针变量分配了另一个名称;这就是引用不能“指向空”的原因,因为变量不能是,也不是。
Pointers are variables; they contain the address of some other variable, or can be null. The important thing is that a pointer has a value, while a reference only has a variable that it is referencing.
指针是变量;它们包含一些其他变量的地址,或者可以为空。重要的是一个指针有一个值,而一个引用只有一个它所引用的变量。
Now some explanation of real code:
现在对真实代码的一些解释:
int a = 0;
int& b = a;
Here you are not creating another variable that points to a
; you are just adding another name to the memory content holding the value of a
. This memory now has two names, a
and b
, and it can be addressed using either name.
在这里,您不是在创建另一个指向 的变量a
;您只是向包含a
. 该内存现在有两个名称a
和b
,并且可以使用任一名称寻址。
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
When calling a function, the compiler usually generates memory spaces for the arguments to be copied to. The function signature defines the spaces that should be created and gives the name that should be used for these spaces. Declaring a parameter as a reference just tells the compiler to use the input variable memory space instead of allocating a new memory space during the method call. It may seem strange to say that your function will be directly manipulating a variable declared in the calling scope, but remember that when executing compiled code, there is no more scope; there is just plain flat memory, and your function code could manipulate any variables.
调用函数时,编译器通常会为要复制到的参数生成内存空间。函数签名定义应该创建的空间并给出应该用于这些空间的名称。将参数声明为引用只是告诉编译器使用输入变量内存空间,而不是在方法调用期间分配新的内存空间。说你的函数将直接操作在调用范围内声明的变量可能看起来很奇怪,但请记住,当执行编译代码时,没有更多的范围;只有普通的平面内存,您的函数代码可以操作任何变量。
Now there may be some cases where your compiler may not be able to know the reference when compiling, like when using an extern variable. So a reference may or may not be implemented as a pointer in the underlying code. But in the examples I gave you, it will most likely not be implemented with a pointer.
现在,在某些情况下,您的编译器可能无法在编译时知道引用,例如使用 extern 变量时。因此,引用可能会或可能不会被实现为底层代码中的指针。但是在我给你的例子中,它很可能不会用指针实现。
回答by Vincent Robert
A reference can never be NULL
.
引用永远不可能是NULL
。
回答by Kunal Vyas
While both references and pointers are used to indirectly access another value, there are two important differences between references and pointers. The first is that a reference always refers to an object: It is an error to define a reference without initializing it. The behavior of assignment is the second important difference: Assigning to a reference changes the object to which the reference is bound; it does not rebind the reference to another object. Once initialized, a reference always refers to the same underlying object.
虽然引用和指针都用于间接访问另一个值,但引用和指针之间有两个重要区别。第一个是引用总是指向一个对象:定义引用而不初始化它是错误的。赋值的行为是第二个重要的区别:赋值给引用会改变引用绑定的对象;它不会重新绑定对另一个对象的引用。一旦初始化,一个引用总是指向同一个底层对象。
Consider these two program fragments. In the first, we assign one pointer to another:
考虑这两个程序片段。首先,我们将一个指针分配给另一个:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
After the assignment, ival, the object addressed by pi remains unchanged. The assignment changes the value of pi, making it point to a different object. Now consider a similar program that assigns two references:
在赋值 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.
此赋值更改 ival,即 ri 引用的值,而不是引用本身。赋值后,这两个引用仍然指向它们原来的对象,这些对象的值现在也一样。