指针变量和引用变量之间的区别是什么?
当前回答
为了避免混淆,我想输入一些输入,我确信这主要取决于编译器如何实现引用,但在gcc的情况下,引用只能指向堆栈上的变量的想法实际上并不正确,例如:
#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;
}
其输出如下:
THIS IS A STRING
0xbb2070 : 0xbb2070
如果您注意到甚至内存地址都完全相同,这意味着引用成功地指向了堆上的一个变量!现在,如果你真的想变得古怪,这也很有效:
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).*/
}
其输出如下:
THIS IS A STRING
因此,引用是引擎盖下的指针,它们都只是存储一个内存地址,地址指向的位置是不相关的,如果我调用std::cout<<str_ref;调用delete str_ref后?很明显,它编译得很好,但在运行时会导致分段错误,因为它不再指向有效变量,我们本质上有一个中断的引用仍然存在(直到它超出范围),但没有用。
换句话说,引用只是一个指针,它抽象了指针机制,使其更安全、更容易使用(没有意外的指针数学,没有混淆“.”和“->”等),假设您没有像上面的例子那样尝试任何废话;)
现在,不管编译器如何处理引用,它总是有某种指针,因为引用必须引用特定内存地址处的特定变量,才能按预期工作,因此无法绕过这一点(因此称为“引用”)。
对于引用,唯一需要记住的重要规则是必须在声明时定义它们(头中的引用除外,在这种情况下,必须在构造函数中定义引用,在构造包含引用的对象之后,再定义它就太晚了)。
请记住,我上面的例子只是说明引用是什么的例子,你永远不想以这些方式使用引用!为了正确使用参考文献,这里已经有很多答案,这些答案一针见血
其他回答
如果你真的想变得迂腐,有一件事你可以用指针做,但不能用指针做:延长临时对象的生命周期。在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本质上成为临时对象。它实际上是对临时对象的引用,该对象现在与引用具有相同的生存期。
如果您尝试在没有常量的情况下执行此操作,它将无法编译。不能将非常量引用绑定到临时对象,也不能为此获取其地址。
与流行观点相反,引用可能为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);
我想重申,获得空引用的唯一方法是通过格式错误的代码,一旦获得了它,就会得到未定义的行为。检查空引用是没有意义的;例如,您可以尝试如果(&bar==NULL)。。。但是编译器可能会优化不存在的语句!有效引用永远不能为NULL,因此从编译器的角度来看,比较总是错误的,并且可以自由地将if子句作为死代码来消除-这是未定义行为的本质。
避免麻烦的正确方法是避免取消引用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));
要从具有更好写作技巧的人那里了解这个问题,请参阅Jim Hyslop和Herb Sutter的空引用。
有关取消引用空指针的危险的另一个示例,请参见Raymond Chen在尝试将代码移植到另一个平台时暴露未定义的行为。
如果遵循传递给函数的参数的约定,则可以使用引用和指针之间的差异。Const引用用于传递到函数中的数据,指针用于传递出函数的数据。在其他语言中,您可以使用诸如In和out之类的关键字来明确表示这一点。在C++中,您可以(按照约定)声明等价的。例如
void DoSomething(const Foo& thisIsAnInput, Foo* thisIsAnOutput)
{
if (thisIsAnOuput)
*thisIsAnOutput = thisIsAnInput;
}
使用引用作为输入和指针作为输出是Google风格指南的一部分。
引用不是给某些内存的另一个名称。它是一个不可变的指针,在使用时会自动取消引用。基本上归结为:
int& j = i;
它内部变成
int* const j = &i;
这是基于教程。所写内容更清楚:
>>> The address that locates a variable within memory is
what we call a reference to that variable. (5th paragraph at page 63)
>>> The variable that stores the reference to another
variable is what we call a pointer. (3rd paragraph at page 64)
简单地记住,
>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)
此外,我们可以参考几乎任何指针教程,指针是指针算术支持的对象,它使指针类似于数组。
看看下面的陈述,
int Tom(0);
int & alias_Tom = Tom;
alias_Tom可以理解为变量的别名(与typedef不同,typedef是一种类型的别名)Tom。忘记这种说法的术语也是可以的,即创建一个对汤姆的引用。