提问人:prakash 提问时间:9/12/2008 最后编辑:Mateen Ulhaqprakash 更新时间:10/5/2023 访问量:1229401
指针变量和引用变量之间有什么区别?
What are the differences between a pointer variable and a reference variable?
答:
除了句法糖之外,引用是指针(而不是指向 的指针)。在声明引用变量时,必须确定它所引用的内容,并且以后无法更改它。const
const
更新:现在我再想一想,有一个重要的区别。
const 指针的目标可以通过获取其地址并使用 const 强制转换来替换。
引用的目标不能以任何方式替换,除非 UB。
这应该允许编译器对引用进行更多优化。
评论
T* const
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
可以重新分配指针:
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 &q; // error int &r = x;
指针变量有自己的标识:一个不同的、可见的内存地址,可以用一元运算符获取,也可以用运算符测量一定的空间量。在引用上使用这些运算符将返回与引用绑定到的任何内容相对应的值;引用本身的地址和大小是不可见的。由于引用以这种方式假定原始变量的标识,因此将引用视为同一变量的另一个名称是方便的。
&
sizeof
int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2); // &x == &r assert(&p != &p2);
可以创建指向指针的指针,但不能创建指向引用的指针。
int **pp; // OK, pointer to pointer int &*pr; // ill-formed, pointer to reference
可以创建指针数组,但不能创建引用数组。
int *ap[]; // OK, array of pointers int &ar[]; // ill-formed, array of references
您可以任意嵌套指向指针的指针,以提供额外的间接级别。引用仅提供一种间接级别,因为对引用的引用会折叠。
int x = 0; int y = 0; int *p = &x; int *q = &y; int **pp = &p; **pp = 2; pp = &q; // *pp is now q **pp = 4; assert(y == 4); assert(x == 2);
指针可以被赋值,而引用必须绑定到现有对象。如果您足够努力,则可以将引用绑定到 ,但这是未定义的,并且行为不会一致。
nullptr
nullptr
/* the code below is undefined; your compiler may optimise it * differently, emit warnings, or outright refuse to compile it */ int &r = *static_cast<int *>(nullptr); // prints "null" under GCC 10 std::cout << (&r != nullptr ? "not null" : "null") << std::endl; bool f(int &r) { return &r != nullptr; } // prints "not null" under GCC 10 std::cout << (f(*static_cast<int *>(nullptr)) ? "not null" : "null") << std::endl;
但是,您可以引用值为 的指针。
nullptr
指针是 ContiguousIterators(数组)。您可以使用它转到指针指向的下一个项目,以及转到第 5 个元素。
++
+ 4
需要取消引用指针才能访问它指向的对象,而引用可以直接使用。指向类/结构的指针用于访问其成员,而引用使用 .
*
->
.
常量引用和右值引用可以绑定到临时引用(请参见临时具体化)。指针不能(并非没有一些间接):
const int &x = int(12); // legal C++ int *y = &int(12); // illegal to take the address of a temporary.
这使得在参数列表等中使用更加方便。
const &
引用永远不能是 。NULL
评论
void Foo::bar() { virtual_baz(); }
int &r=*p;
与流行观点相反,有可能有一个 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);
我想重申,获得 null 引用的唯一方法是通过格式错误的代码,一旦你有了它,你就会得到未定义的行为。检查空引用是没有意义的;例如,您可以尝试,但编译器可能会优化不存在的语句!有效的引用永远不能为 NULL,因此从编译器的角度来看,比较总是错误的,并且可以自由地将子句作为死代码消除 - 这是未定义行为的本质。if(&bar==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 的 Null 引用。
有关取消引用空指针的危险的另一个示例,请参阅 Raymond Chen 撰写的 Exposing undefined behavior when trying to port code to another platform(尝试将代码移植到另一个平台时公开未定义的行为)。
评论
如果你想变得非常迂腐,那么你可以用引用做一件事,而你不能用指针做:延长临时对象的生存期。在 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
评论
const &
Animal x = fast ? getHare() : getTortoise()
x
Animal& x = ...
你忘记了最重要的部分:
带指针的 member-access 使用
带引用的 member-access 用途->
.
foo.bar
显然优于 vi 明显优于 Emacs :-)foo->bar
评论
->
.
->
.
->
除非我需要以下任何一项,否则我使用参考文献:
Null 指针可以用作 哨兵价值,通常是一种廉价的方式 避免函数重载或使用 一个布尔值。
您可以在指针上进行算术运算。 例如
p += offset;
评论
&r + offset
r
引用的另一个有趣的用法是提供用户定义类型的默认参数:
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;
}
默认风格使用引用的“bind const reference to a temporary”方面。
它占用多少空间并不重要,因为您实际上看不到它占用的任何空间的任何副作用(不执行代码)。
另一方面,引用和指针之间的一个主要区别是,分配给 const 引用的临时引用一直有效,直到 const 引用超出范围。
例如:
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!
这是允许 ScopeGuard 工作的语言机制。
评论
实际上,引用并不像指针。
编译器保留对变量的“引用”,将名称与内存地址相关联;它的工作是在编译时将任何变量名称转换为内存地址。
创建引用时,只需告诉编译器为指针变量分配了另一个名称;这就是为什么引用不能“指向 null”的原因,因为变量不能是,也不是。
指针是变量;它们包含某些其他变量的地址,也可以为 null。重要的是指针有一个值,而引用只有一个它所引用的变量。
现在对真实代码进行一些解释:
int a = 0;
int& b = a;
在这里,您不是在创建另一个指向 的变量;您只是在内存内容中添加另一个名称,其值为 。此内存现在有两个名称,和 ,并且可以使用任一名称对其进行寻址。a
a
a
b
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
调用函数时,编译器通常会为要复制到的参数生成内存空间。函数签名定义应创建的空间,并给出应用于这些空间的名称。将参数声明为引用只是告诉编译器使用输入变量内存空间,而不是在方法调用期间分配新的内存空间。说你的函数将直接操作调用作用域中声明的变量可能看起来很奇怪,但请记住,在执行编译后的代码时,没有更多的作用域;只有普通的平面内存,您的函数代码可以操作任何变量。
现在,在某些情况下,编译器在编译时可能无法知道引用,例如使用 extern 变量时。因此,引用可以也可以不作为基础代码中的指针实现。但在我给你的示例中,它很可能不会用指针实现。
评论
什么是 C++ 参考(针对 C 程序员))
引用可以被认为是一个常量指针(不要与指向常量值的指针混淆!),具有自动间接性,即编译器将为您应用运算符。*
所有引用都必须使用非 null 值进行初始化,否则编译将失败。既不可能获取引用的地址 - 地址运算符将返回引用值的地址 - 也无法对引用进行算术运算。
C 程序员可能不喜欢 C++ 引用,因为当间接发生时,或者参数在不查看函数签名的情况下通过值或指针传递时,它不再明显。
C++ 程序员可能不喜欢使用指针,因为它们被认为是不安全的——尽管引用并不比常量指针更安全,除非在最微不足道的情况下——缺乏自动间接的便利性,并且带有不同的语义内涵。
请考虑 C++ FAQ 中的以下语句:
即使引用通常是使用 底层汇编语言,请不要将引用视为 指向对象的滑稽指针。引用是对象。是的 不是指向对象的指针,也不是对象的副本。它是 对象。
但是,如果引用真的是对象,怎么会有悬空的引用呢?在非托管语言中,引用不可能比指针“更安全”——通常没有办法跨范围边界可靠地为值添加别名!
为什么我认为 C++ 引用有用
来自 C 背景的 C++ 引用可能看起来有点愚蠢,但仍然应该尽可能使用它们而不是指针:自动间接很方便,并且在处理 RAII 时引用变得特别有用——但不是因为任何感知到的安全优势,而是因为它们使编写惯用代码变得不那么尴尬。
RAII 是 C++ 的核心概念之一,但它与复制语义的交互非常重要。通过引用传递对象可以避免这些问题,因为不涉及复制。如果语言中不存在引用,则必须改用指针,因为指针使用起来更麻烦,从而违反了语言设计原则,即最佳实践解决方案应该比替代方案更容易。
评论
此外,作为内联函数的参数的引用的处理方式可能与指针不同。
void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
int testptr=0;
increment(&testptr);
}
void increftest()
{
int testref=0;
increment(testref);
}
许多编译器在内联指针版本时,实际上会强制写入内存(我们显式获取地址)。但是,他们会将引用保留在更优化的寄存器中。
当然,对于未内联的函数,指针和引用会生成相同的代码,如果函数未修改和返回内部函数,则按值传递内部函数总是比按引用传递内部函数更好。
另一个区别是,您可以有指向 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
此赋值更改 ival,即 ri 引用的值,而不是引用本身。赋值后,两个引用仍引用其原始对象,并且这些对象的值现在也相同。
评论
指针和引用之间有一个根本区别,我没有看到任何人提到过:引用在函数参数中启用按引用传递语义。指针虽然一开始不可见,但并不可见:它们只提供按值传递的语义。这在本文中已经很好地描述过。
问候 &rzej
评论
引用是另一个变量的别名,而指针保存变量的内存地址。引用通常用作函数参数,因此传递的对象不是副本,而是对象本身。
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.
引用不是为某些内存赋予的另一个名称。它是一个不可变的指针,在使用时自动取消引用。基本上可以归结为:
int& j = i;
它在内部变成
int* const j = &i;
评论
const
该程序可能有助于理解问题的答案。这是一个由引用“j”和指向变量“x”的指针“ptr”组成的简单程序。
#include<iostream>
using namespace std;
int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"
cout << "x=" << x << endl;
cout << "&x=" << &x << endl;
cout << "j=" << j << endl;
cout << "&j=" << &j << endl;
cout << "*ptr=" << *ptr << endl;
cout << "ptr=" << ptr << endl;
cout << "&ptr=" << &ptr << endl;
getch();
}
运行程序并查看输出,您就会明白。
另外,请抽出 10 分钟观看此视频: https://www.youtube.com/watch?v=rlJrrGV0iOg
引用与指针非常相似,但它们经过专门设计,有助于优化编译器。
- 引用的设计使得编译器更容易跟踪哪些引用别名哪些变量。有两个主要功能非常重要:没有“参考算术”和没有重新分配参考。这些允许编译器在编译时确定哪些引用别名哪些变量。
- 引用允许引用没有内存地址的变量,例如编译器选择放入寄存器中的变量。如果获取局部变量的地址,编译器很难将其放入寄存器中。
举个例子:
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];
}
}
优化编译器可能会意识到我们正在访问相当多的 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
}
要进行这样的优化,它需要证明在调用期间没有任何东西可以更改 array[1]。这很容易做到。 i 永远不会小于 2,因此 array[i] 永远不能引用 array[1]。maybeModify() 被赋予 a0 作为引用(别名数组 [0])。因为没有“引用”算术,编译器只需要证明 maybeModify 永远不会得到 x 的地址,并且它已经证明了没有任何东西改变 array[1]。
它还必须证明,当我们在 a0 中有一个临时寄存器副本时,未来的调用无法读取/写入 a[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];
}
}
行为是一样的;只是现在要证明 maybeModify 永远不会修改 array[1] 要困难得多,因为我们已经给了它一个指针;猫从袋子里出来了。现在它必须做一个更困难的证明:对 maybeModify 进行静态分析,以证明它从不写入 &x + 1。它还必须证明它永远不会保存可以引用 array[0] 的指针,这同样棘手。
现代编译器在静态分析方面越来越好,但帮助他们并使用引用总是很好的。
当然,除非有如此巧妙的优化,否则编译器确实会在需要时将引用转换为指针。
编辑:在发布这个答案五年后,我发现了一个实际的技术差异,其中引用不同,而不仅仅是看待同一寻址概念的不同方式。引用可以修改临时对象的生存期,而指针则无法修改。
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
通常,临时对象(例如由 调用 创建的对象)会在表达式末尾销毁。但是,通过将该对象绑定到引用,C++ 将延长该临时对象的生命周期,直到超出范围。createF(5)
ref
ref
评论
maybeModify
x
void maybeModify(int& x) { 1[&x]++; }
这是基于教程。所写的内容使其更清楚:
>>> 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
可以理解为一个(与 不同,这是)。也可以忘记这种语句的术语是创建 的引用。alias of a variable
typedef
alias of a type
Tom
Tom
评论
nullptr
冒着增加混淆的风险,我想加入一些输入,我敢肯定这主要取决于编译器如何实现引用,但在 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会发生什么;调用删除后&str_ref?好吧,显然它编译得很好,但在运行时会导致分段错误,因为它不再指向一个有效的变量,我们基本上有一个仍然存在的损坏引用(直到它超出范围),但毫无用处。
换句话说,引用只不过是一个指针,它抽象了指针机制,使其更安全、更易于使用(没有意外的指针数学,没有混淆“.”和“->”等),假设你没有尝试任何废话,就像我上面的例子一样;)
现在,无论编译器如何处理引用,它总是在后台有某种指针,因为引用必须引用特定内存地址上的特定变量才能按预期工作,因此无法绕过这一点(因此称为“引用”)。
对于引用,唯一需要记住的主要规则是,它们必须在声明时定义(除了标头中的引用,在这种情况下,它必须在构造函数中定义,在构造它所包含的对象之后,定义它为时已晚)。
请记住,我上面的例子就是这样,举例说明什么是引用,你永远不会想以这些方式使用引用!为了正确使用参考资料,这里已经有很多答案,一针见血
如果您不熟悉以抽象甚至学术的方式研究计算机语言,那么语义差异可能会显得深奥。
在最高层次上,引用的想法是它们是透明的“别名”。您的计算机可能会使用地址来使它们工作,但您不应该担心这一点:您应该将它们视为现有对象的“另一个名称”,语法反映了这一点。它们比指针更严格,因此编译器可以在您将要创建悬空引用时比在即将创建悬空指针时更可靠地向您发出警告。
除此之外,指针和引用之间当然存在一些实际差异。使用它们的语法显然不同,你不能“重新放置”引用、引用虚无或指向引用的指针。
也许一些比喻会有所帮助; 在您的桌面屏幕空间的上下文中 -
- 引用要求您指定实际窗口。
- 指针需要屏幕上一段空间的位置,以确保它将包含该窗口类型的零个或多个实例。
在 C++ 中可以引用指针,但反之则不可能,这意味着指向引用的指针是不可能的。对指针的引用提供了更简洁的语法来修改指针。 请看这个例子:
#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;
}
并考虑上述程序的 C 版本。在 C 语言中,您必须使用指针到指针(多个间接),这会导致混淆,并且程序可能看起来很复杂。
#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;
}
有关指针引用的详细信息,请访问以下内容:
正如我所说,指向引用的指针是不可能的。请尝试以下程序:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
不同之处在于,非常量指针变量(不要与指向常量的指针混淆)可能会在程序执行期间的某个时间更改,需要使用指针语义(&,*)运算符,而引用只能在初始化时设置(这就是为什么您只能在构造函数初始值设定项列表中设置它们,但不能以某种方式设置它们)并使用普通值访问语义。基本上,引入了引用,以允许支持运算符重载,正如我在一些非常古老的书中读到的那样。正如有人在此线程中所说 - 指针可以设置为 0 或您想要的任何值。0(NULL, nullptr) 表示指针初始化时不包含任何内容。取消引用 null 指针是错误的。但实际上,指针可能包含一个值,该值不指向某个正确的内存位置。反过来,引用会尽量不允许用户初始化对无法引用的内容的引用,因为您始终为其提供正确类型的右值。虽然有很多方法可以使引用变量初始化到错误的内存位置 - 但你最好不要深入挖掘细节。在机器级别上,指针和参考都统一工作 - 通过指针。假设在基本参考文献中是句法糖。右值引用与此不同 - 它们自然是堆栈/堆对象。
指针和引用之间的区别
指针可以初始化为 0,引用不能初始化。事实上,引用也必须引用对象,但指针可以是空指针:
int* p = 0;
但是我们不能拥有而且.int& p = 0;
int& p=5 ;
事实上,要正确地做到这一点,我们必须首先声明并定义一个对象,然后我们才能引用该对象,因此前面代码的正确实现将是:
Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;
另一个重要的一点是,我们可以在不初始化的情况下声明指针,但是在引用的情况下不能做这样的事情,因为引用必须始终引用变量或对象。但是,这种指针的使用是有风险的,因此通常我们会检查指针是否真的指向某些内容。在引用的情况下,不需要进行此类检查,因为我们已经知道在声明期间引用对象是强制性的。
另一个区别是指针可以指向另一个对象,但引用始终引用同一个对象,让我们举个例子:
Int a = 6, b = 5;
Int& rf = a;
Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.
rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased
另一点:当我们有一个像 STL 模板这样的模板时,这种类模板将始终返回引用,而不是指针,以便于使用运算符 [] 读取或分配新值:
Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
评论
const int& i = 0
我觉得这里还有一点没有涉及。
与指针不同,引用在语法上等同于它们所引用的对象,即任何可以应用于对象的操作都适用于引用,并且具有完全相同的语法(当然,初始化除外)。
虽然这可能看起来很肤浅,但我相信这个属性对于许多 C++ 功能至关重要,例如:
模板。由于模板参数是鸭型的,因此类型的句法属性才是最重要的,因此通常可以将同一模板与 和 一起使用。
(或者仍然依赖于隐式转换 到 )
涵盖两者的模板,甚至更常见。T
T&
std::reference_wrapper<T>
T&
T&
T&&
左值。考虑语句 Without references it will only for c-strings ()。通过引用返回字符允许用户定义的类具有相同的表示法。
str[0] = 'X';
char* str
复制构造函数。从语法上讲,将对象传递给复制构造函数而不是指向对象的指针是有意义的。但是复制构造函数无法按值获取对象 - 这将导致对同一复制构造函数的递归调用。这样一来,引用就是这里唯一的选择。
运算符重载。使用引用,可以在保留相同中缀表示法的同时,将间接引入运算符调用。这也适用于常规重载函数。
operator+(const T& a, const T& b)
这些点赋予了 C++ 和标准库相当一部分功能,因此这是引用的一个主要属性。
评论
我总是按照 C++ 核心指南中的这条规则来决定:
当“无参数”是有效选项时,首选 T* 而不是 T&
评论
nullptr
nullptr
指针和引用之间有一个非常重要的非技术性区别:通过指针传递给函数的参数比通过非常量引用传递给函数的参数更明显。例如:
void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);
void bar() {
std::string x;
fn1(x); // Cannot modify x
fn2(x); // Cannot modify x (without const_cast)
fn3(x); // CAN modify x!
fn4(&x); // Can modify x (but is obvious about it)
}
回到 C 语言中,一个看起来像的调用只能按值传递,所以它肯定不能修改;要修改参数,您需要传递指针。因此,如果一个参数前面没有,你知道它不会被修改。(反之,表示修改,是不正确的,因为有时必须通过指针传递大型只读结构。fn(x)
x
fn(&x)
&
&
const
一些人认为,在阅读代码时,这是一个非常有用的功能,指针参数应该始终用于可修改的参数,而不是非引用,即使函数从不期望 .也就是说,这些人认为不应该允许像上面这样的函数签名。Google的C++风格指南就是一个例子。const
nullptr
fn3()
我用引用和指针做类比,将引用视为对象的另一个名称,将指针视为对象的地址。
// 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
}
Taryn♦ 说:
您不能像使用指针那样获取引用的地址。
其实你可以。
我引用了另一个问题的答案:
C++ FAQ说得最好:
与指针不同,一旦引用绑定到一个对象,它就不能“重新驻插”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址会给你引用者的地址;记住:引用是它的引用)。
评论
如果遵循传递给函数的参数的约定,则可以使用引用和指针之间的差异。常量引用用于传递到函数中的数据,指针用于传递到函数中的数据。在其他语言中,您可以使用关键字(如 和 )显式表示这一点。在 C++ 中,您可以(按照约定)声明等效项。例如in
out
void DoSomething(const Foo& thisIsAnInput, Foo* thisIsAnOutput)
{
if (thisIsAnOuput)
*thisIsAnOutput = thisIsAnInput;
}
使用引用作为输入,使用指针作为输出是 Google 风格指南的一部分。
评论
const
除了这里的所有答案,
您可以使用引用实现运算符重载:
my_point operator+(const my_point& a, const my_point& b)
{
return { a.x + b.x, a.y + b.y };
}
使用参数作为值将创建原始参数的临时副本,并且由于指针算术的原因,使用指针不会调用此函数。
评论
直接答案
什么是 C++ 中的引用?不是对象类型的某个特定实例。
C++ 中的指针是什么?作为对象类型的某个特定实例。
对象类型是(可能是 cv 限定的)类型,它不是函数类型,不是引用类型,也不是 cv void。
重要的是要知道,对象类型是 C++ 中 universe 类型的顶级类别。引用也是一个顶级类别。但指针不是。
指针和引用在复合类型的上下文中一起提及。这基本上是由于从C继承(并扩展)的声明器语法的性质,C没有引用。(此外,自 C++ 11 以来,有不止一种引用的声明器,而指针仍然是“统一的”:+ vs. 。因此,在这种情况下,用类似风格的 C 来起草一种特定于“扩展”的语言在某种程度上是合理的。(我仍然会争辩说,声明符的语法浪费了很多语法表达能力,使人类用户和实现都令人沮丧。因此,它们都不符合在新语言设计中内置的条件。不过,这是一个关于PL设计的完全不同的话题。&
&&
*
否则,指针可以被限定为具有引用的特定类型的类型是无关紧要的。除了语法相似性之外,它们共享的共同属性太少,因此在大多数情况下没有必要将它们放在一起。
请注意,上面的语句仅提及“指针”和“引用”作为类型。关于它们的实例(如变量),有一些有趣的问题。也出现了太多的误解。
顶级类别的差异已经可以揭示许多与指针没有直接关联的具体差异:
- 对象类型可以具有顶级限定符。引用不能。
cv
- 根据抽象机器语义,对象类型的变量确实占用了存储空间。参考不一定占用存储空间(有关详细信息,请参阅下面有关误解的部分)。
- ...
关于参考文献的更多特殊规则:
- 复合声明符对引用的限制性更强。
- 引用可能会折叠。
- 基于模板参数推导过程中引用折叠的参数(作为“转发参考”)的特殊规则允许参数的“完美转发”。
&&
- 基于模板参数推导过程中引用折叠的参数(作为“转发参考”)的特殊规则允许参数的“完美转发”。
- 引用在初始化时具有特殊规则。通过扩展,声明为引用类型的变量的生存期可以与普通对象不同。
- 顺便说一句,其他一些上下文(如初始化)涉及遵循一些类似的引用生存期扩展规则。这是另一罐蠕虫。
std::initializer_list
- 顺便说一句,其他一些上下文(如初始化)涉及遵循一些类似的引用生存期扩展规则。这是另一罐蠕虫。
- ...
误解
句法糖
我知道引用是语法糖,所以代码更容易阅读和编写。
从技术上讲,这是完全错误的。引用不是 C++ 中任何其他功能的语法糖,因为它们不能在没有任何语义差异的情况下被其他功能完全替换。
(类似地,lambda-expressions 不是 C++ 中任何其他功能的语法糖,因为它不能使用“未指定”属性(如捕获变量的声明顺序)进行精确模拟,这可能很重要,因为此类变量的初始化顺序可能很重要。
C++ 在这个严格意义上只有几种句法糖。一个实例是(继承自 C)内置(非重载)运算符,它被定义为与内置运算符一元 *
和二进制 +
完全相同,具有特定组合形式的相同语义属性。[]
存储
因此,指针和引用都使用相同的内存量。
上面的说法是完全错误的。为了避免这种误解,请查看 ISO C++ 规则:
...一个物体在其建造期间、整个生命周期和破坏期间都占据了一个存储区域。...
从 [dcl.ref]/4:
未指定引用是否需要存储。
请注意,这些是语义属性。
语用学
即使指针不够限定,无法与语言设计意义上的引用放在一起,但仍然存在一些论点,使得在其他一些上下文中在它们之间做出选择是值得商榷的,例如,在对参数类型进行选择时。
但这还不是全部。我的意思是,除了指针与引用之外,还有更多的事情需要考虑。
如果你不必坚持这种过于具体的选择,在大多数情况下,答案是简短的:你没有必要使用指针,所以你不需要。指针通常很糟糕,因为它们暗示了太多你意想不到的事情,并且它们将依赖于太多隐含的假设,从而破坏了代码的可维护性(甚至)可移植性。不必要地依赖指针绝对是一种糟糕的风格,在现代 C++ 的意义上应该避免。重新考虑你的目的,你最终会发现指针在大多数情况下是最后排序的功能。
- 有时,语言规则明确要求使用特定类型。如果您想使用这些功能,请遵守规则。
- 复制构造函数需要特定类型的 cv- 引用类型作为第一个参数类型。(通常它应该是合格的。
&
const
- 移动构造函数需要特定类型的 cv- 引用类型作为第一个参数类型。(通常不应该有限定词。
&&
- 运算符的特定重载需要引用或非引用类型。例如:
- 重载为特殊成员函数需要类似于复制/移动构造函数的第一个参数的引用类型。
operator=
- Postfix 需要虚拟 .
++
int
- ...
- 重载为特殊成员函数需要类似于复制/移动构造函数的第一个参数的引用类型。
- 复制构造函数需要特定类型的 cv- 引用类型作为第一个参数类型。(通常它应该是合格的。
- 如果您知道按值传递(即使用非引用类型)就足够了,请直接使用它,尤其是在使用支持 C++17 强制复制省略的实现时。(警告:但是,要详尽地推理必要性可能非常复杂。
- 如果你想操作一些具有所有权的句柄,请使用智能指针,如 and(如果你要求它们不透明,甚至可以自己使用自制指针),而不是原始指针。
unique_ptr
shared_ptr
- 如果你在一个范围内进行一些迭代,请使用迭代器(或标准库尚未提供的一些范围),而不是原始指针,除非你确信原始指针在非常特定的情况下会做得更好(例如,对于更少的标头依赖)。
- 如果您知道按值传递就足够了,并且想要一些显式的可为 null 的语义,请使用包装器,例如 ,而不是原始指针。
std::optional
- 如果您知道由于上述原因,按值传递并不理想,并且您不希望使用可为 null 的语义,请使用 {lvalue, rvalue, forwarding}-references。
- 即使你确实需要像传统指针这样的语义,通常也有更合适的东西,比如在 Library Fundamental TS 中。
observer_ptr
在当前语言中无法解决唯一的例外情况:
- 在实现上述智能指针时,可能需要处理原始指针。
- 特定的语言互操作例程需要指针,如 .(然而,与普通的对象指针相比,cv- 仍然有很大的不同和更安全,因为它排除了意外的指针算术,除非你依赖于一些不符合要求的扩展,比如 GNU。
operator new
void*
void*
- 函数指针可以在没有捕获的情况下从 lambda 表达式转换,而函数引用则不能。在这种情况下,您必须在非泛型代码中使用函数指针,即使您故意不希望出现空值。
因此,在实践中,答案是如此明显:当有疑问时,请避免指针。只有当有非常明确的原因表明没有其他更合适的理由时,才必须使用指针。除了上面提到的少数例外情况,这些选择几乎总是不是纯粹特定于 C++ 的(但可能是特定于语言实现的)。此类实例可以是:
- 您必须为旧式 (C) API 提供服务。
- 您必须满足特定 C++ 实现的 ABI 要求。
- 您必须根据特定实现的假设,在运行时与不同的语言实现(包括各种程序集、语言运行时和某些高级客户端语言的 FFI)进行互操作。
- 在某些极端情况下,您必须提高翻译(编译和链接)的效率。
- 在某些极端情况下,您必须避免符号膨胀。
语言中立性注意事项
如果你通过一些谷歌搜索结果(不是特定于C++)来看到这个问题,这很可能是错误的地方。
C++ 中的引用非常“奇怪”,因为它本质上不是第一类的:它们将被视为被引用的对象或函数,因此它们没有机会支持一些第一类操作,例如作为独立于引用对象类型的成员访问运算符的左操作数。其他语言的引用可能有也可能没有类似的限制。
C++ 中的引用可能不会保留不同语言的含义。例如,引用通常不会像 C++ 中那样对值表示非空属性,因此此类假设在其他一些语言中可能不起作用(并且您很容易找到反例,例如 Java、C# 等)。
一般来说,不同编程语言的引用之间仍然可以有一些共同的属性,但让我们把它留给 SO 中的其他一些问题。
(旁注:这个问题可能比任何“类C”语言都重要,比如ALGOL 68 vs.PL/I。
评论
std::vector<T>::iterator
T*
*(a + b)
有关引用和指针的一些关键相关详细信息
指针
- 指针变量是使用一元后缀声明器运算符声明的 *
- 指针对象被分配一个地址值,例如,通过赋值给数组对象,使用&一元前缀运算符赋值对象的地址,或赋值给另一个指针对象的值
- 指针可以重新分配任意次数,指向不同的对象
- 指针是保存分配地址的变量。它占用的内存存储量等于目标计算机体系结构的地址大小
- 指针可以进行数学操作,例如,通过递增或加法运算符进行操作。因此,可以使用指针等进行迭代。
- 若要获取或设置指针引用的对象的内容,必须使用一元前缀运算符 * 来取消引用它
引用
- 声明引用时必须对其进行初始化。
- 引用使用一元后缀声明符运算符 & 进行声明。
- 初始化引用时,使用它们将直接引用的对象的名称,而无需一元前缀运算符 &
- 初始化后,无法通过赋值或算术操作将引用指向其他内容
- 无需取消引用即可获取或设置它所引用的对象的内容
- 对引用的赋值操作它指向的对象的内容(初始化后),而不是引用本身(不更改它指向的位置)
- 对引用的算术运算操作操作它指向的对象的内容,而不是引用本身(不更改它指向的位置)
- 在几乎所有的实现中,引用实际上都作为地址存储在被引用对象的内存中。因此,它占用的内存存储量等于目标计算机体系结构的地址大小,就像指针对象一样
尽管指针和引用的实现方式与“后台”大致相同,但编译器对它们的处理方式不同,从而导致上述所有差异。
品
我最近写的一篇文章比我在这里展示的要详细得多,应该对这个问题非常有帮助,尤其是关于记忆中的事情是如何发生的:
Arrays, Pointers and References Under the Hood 深入文章
评论
“我知道引用是语法糖,所以代码更容易阅读和编写”
这。引用不是实现指针的另一种方式,尽管它涵盖了一个巨大的指针用例。指针是一种数据类型,通常指向实际值的地址。但是,它可以设置为零,或者使用地址算术等设置为地址后面的几个位置。引用是具有自身值的变量的“语法糖”。
C 只有传递值语义。获取变量所引用的数据的地址并将其发送到函数是一种通过“引用”传递的方法。引用通过“引用”原始数据位置本身在语义上对此进行了快捷方式。所以:
int x = 1;
int *y = &x;
int &z = x;
Y 是一个 int 指针,指向 x 的存储位置。 X 和 Z 表示相同的存储位置(堆栈或堆)。
很多人都谈到了两者(指针和引用)之间的区别,就好像它们是同一回事,但用法不同。它们根本不一样。
1)“指针可以重新赋值任意次数,而引用在绑定后不能重新赋值”——指针是指向数据的地址数据类型。引用是数据的另一个名称。因此,您可以“重新分配”引用。您只是无法重新分配它所引用的数据位置。就像你不能改变“x”所指的数据位置一样,你不能对“z”这样做。
x = 2;
*y = 2;
z = 2;
一样。这是一次重新分配。
2)“指针可以无处指向(NULL),而引用总是引用一个对象”——再次令人困惑。引用只是对象的另一个名称。空指针意味着(语义上)它没有引用任何内容,而引用是通过说它是“x”的另一个名称来创建的。因为
3)“你不能像使用指针那样获取引用的地址”——是的,你可以。再次令人困惑。如果试图查找用作引用的指针的地址,那就是一个问题 -- 因为引用不是指向对象的指针。他们是对象。所以你可以得到对象的地址,你可以得到指针的地址。因为它们都获取数据的地址(一个是对象在内存中的位置,另一个是指向内存中对象位置的指针)。
int *yz = &z; -- legal
int **yy = &y; -- legal
int *yx = &x; -- legal; notice how this looks like the z example. x and z are equivalent.
4)“没有”参考算术“” - 再次令人困惑 - 因为上面的例子有z是对x的引用,因此两者都是整数,“参考”算术意味着例如将1添加到x引用的值。
x++;
z++;
*y++; // what people assume is happening behind the scenes, but isn't. it would produce the same results in this example.
*(y++); // this one adds to the pointer, and then dereferences it. It makes sense that a pointer datatype (an address) can be incremented. Just like an int can be incremented.
指针(*)的基本含义是“地址值”,这意味着无论您提供什么地址,它都会在该地址上提供值。更改地址后,它将给出新值,而引用变量用于引用任何特定变量,并且将来不能更改以引用任何其他变量。
以下答案和链接摘要:
- 指针可以重新赋值任意次数,而引用在绑定后不能重新赋值。
- 指针可以不指向任何地方(),而引用始终引用对象。
NULL
- 您不能像使用指针那样获取引用的地址。
- 没有“引用算术”(但您可以获取引用指向的对象的地址,并对其执行指针算术,如 )。
&obj + 5
澄清误解:
C++ 标准非常小心地避免规定编译器如何 实现引用,但每个 C++ 编译器都实现 引用作为指针。也就是说,声明如下:
int &ri = i;
如果它没有完全优化,则分配相同数量的存储 作为指针,并放置地址
的 i
进入那个存储。
因此,指针和引用都使用相同的内存量。
作为一般规则,
- 在函数参数和返回类型中使用引用来提供有用的自记录接口。
- 使用指针实现算法和数据结构。
有趣的阅读:
- 我一直以来最喜欢的 C++ FAQ lite。
- 引用与指针。
- 参考文献简介。
- 引用和常量。
简单来说,我们可以说引用是变量的替代名称,而 指针是一个变量,用于保存另一个变量的地址。 例如
int a = 20;
int &r = a;
r = 40; /* now the value of a is changed to 40 */
int b =20;
int *ptr;
ptr = &b; /*assigns address of b to ptr not the value */
引用是常量指针。 与 相同。这就是为什么他们的不是 const 引用,因为它已经是 const,而对 const 的引用是 .当您使用 -O0 进行编译时,编译器会在这两种情况下将 b 的地址放在堆栈上,并且作为类的成员,它也将出现在堆栈/堆上的对象中,与您声明 const 指针时相同。使用 -Ofast,可以自由地优化这一点。常量指针和引用都进行了优化。int * const a = &b
int& a = b
const int * const a
与 const 指针不同,没有办法获取引用本身的地址,因为它将被解释为它所引用的变量的地址。因此,在 -Ofast 上,表示引用的 const 指针(被引用变量的地址)将始终在堆栈外进行优化,但是如果程序绝对需要实际 const 指针的地址(指针本身的地址,而不是它指向的地址),即打印 const 指针的地址, 然后 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 上,您传递被引用变量的地址,因此它与通过指针传递相同,即常量指针包含的地址。在 -Ofast 上,如果函数可以内联,则编译器会在内联调用中对其进行优化,因为动态范围是已知的,但在函数定义中,参数始终作为指针被取消引用(期望引用引用的变量的地址),其中它可能被另一个翻译单元使用,并且编译器不知道动态范围, 当然,除非该函数被声明为静态函数,否则它不能在转换单元之外使用,然后它按值传递,只要它没有通过引用在函数中修改,那么它将传递你正在传递的引用所引用的变量的地址,在 -Ofast 上,这将在寄存器中传递,如果有足够的,则将其保留在堆栈之外调用约定中的易失性寄存器。
指针是一个变量,它保存另一个变量的内存地址,其中作为引用的是现有变量的别名。(已存在变量的另一个名称)
1. 指针可以初始化为:
int b = 15;
int *q = &b;
或
int *q;
q = &b;
其中作为参考,
int b=15;
int &c=b;
(一步声明和初始化)
- 可以将指针分配给 null,但不能将引用指定为 null
- 可以对指针执行各种算术运算,而没有所谓的参考算术。
- 指针可以重新赋值,但引用不能
- 指针在堆栈上有自己的内存地址和大小,而引用共享相同的内存地址
评论
不能像指针那样取消引用,当取消引用时,指针会在该位置提供值,
不过,引用和指针都可以通过地址工作......
所以
你可以这样做
int* val = 0xDEADBEEF; *val 是 0xDEADBEEF 的东西。
你不能这样做 int& val = 1;
*不允许使用val。
把指针想象成一张名片:
- 它让您有机会联系某人
- 它可以是空的
- 它可能包含错误或过时的信息
- 你不确定上面提到的某个人是否还活着
- 你不能直接对着卡片说话,你只能用它来给别人打电话
- 也许有很多这样的卡片存在
将参考视为与某人的主动通话:
- 你很确定你联系过的人还活着
- 您可以直接通话,无需额外通话
- 你很确定你不会对一个空旷的地方或一块垃圾说话
- 你不能确定你是唯一一个正在与这个物体交谈的人
评论
上面有很多有价值的信息,但我对新手 C++ 程序员的建议。
避免使用指针,仅在别无选择的情况下使用指针(因为您正在与仅接受指针的库进行交互)。 我知道很多书籍和教程都是这样做的,但是让新手编写代码来操作原始指针并不是向新程序员介绍该语言的好方法。
如果不需要能够更改为所指向的对象,请使用引用。 如果确实需要更改指向的内容,请使用智能指针。
虽然从“技术上讲”你可以构造一个指向虚假数据的引用,但这要求你已经做了一些糟糕的指针操作,或者你通过引用传递了一些没有立即使用的东西,而是在对象被销毁后存储和使用。 假设引用指向有效构造的对象是完全可以接受的(也是很好的形式)。您不应该编写一堆代码来测试引用是否指向有效对象。
指针具有“指向无”(NULL 或 nullptr)的额外复杂性 您可能需要检查。 指针增加了“谁(以及如何)负责清理指针指向的数据”的复杂性。
下一个:显式关键字是什么意思?
评论
int &x = *(int*)0;