指针变量和引用变量之间的区别是什么?
当前回答
引用是另一个变量的别名,而指针保存变量的内存地址。引用通常用作函数参数,因此传递的对象不是副本而是对象本身。
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.
其他回答
指针是保存另一个变量的内存地址的变量,其中引用是现有变量的别名。(已存在变量的另一个名称)
1.指针可以初始化为:
int b = 15;
int *q = &b;
OR
int *q;
q = &b;
其中作为参考,
int b=15;
int &c=b;
(在一个步骤中声明和初始化)
指针可以分配给null,但引用不能可以对指针执行各种算术运算,而没有所谓的参考算术。指针可以重新分配,但引用不能指针在堆栈上有自己的内存地址和大小,而引用共享相同的内存地址
我觉得还有一点还没有在这里讨论。
与指针不同,引用在语法上等同于它们所引用的对象,即可以应用于对象的任何操作都适用于引用,并且具有完全相同的语法(当然,初始化是例外)。
虽然这可能看起来很肤浅,但我认为这一特性对于一些C++特性来说至关重要,例如:
模板。因为模板参数是鸭子类型的,所以类型的语法财产才是最重要的,所以通常同一个模板可以同时用于T和T&。(或std::reference_wrapper<T>,它仍然依赖于隐式转换至T&)覆盖T&和T&&的模板更为常见。L值。考虑语句str[0]=“X”;如果没有引用,它只适用于c字符串(char*str)。通过引用返回字符允许用户定义的类具有相同的符号。复制构造函数。从语法上讲,将对象传递给复制构造函数是有意义的,而不是传递给对象的指针。但复制构造函数无法按值获取对象,这将导致对同一复制构造函数的递归调用。这将引用作为此处的唯一选项。操作员过载。通过引用,可以在保留相同的中缀符号的同时,将间接指向引入运算符调用,例如运算符+(const T&a,const T&b)。这也适用于常规重载函数。
这些点赋予了C++和标准库相当大的一部分权力,因此这是参考文献的一个重要属性。
除了语法糖,引用是常量指针(而不是指向常量的指针)。在声明引用变量时,必须确定它所指的内容,以后不能更改它。
更新:现在我再考虑一下,有一个重要的区别。
常量指针的目标可以通过获取其地址并使用常量转换来替换。
引用的目标不能以UB以外的任何方式替换。
这应该允许编译器对引用进行更多优化。
塔林♦ 说:
不能像使用指针那样获取引用的地址。
事实上你可以。
我引用了另一个问题的答案:
C++常见问题解答说得最好:与指针不同,一旦引用绑定到对象,就不能将其“重新放置”到另一个对象。引用本身不是一个对象(它没有标识;获取引用的地址可以获得引用的地址;记住:引用是它的引用)。
引用与指针非常相似,但它们是专门设计的,有助于优化编译器。
引用的设计使得编译器更容易跟踪哪些引用别名哪些变量。两个主要特性非常重要:没有“引用算术”,也没有重新分配引用。这些允许编译器在编译时找出哪些引用别名哪些变量。允许引用没有内存地址的变量,例如编译器选择放入寄存器的变量。如果获取局部变量的地址,编译器很难将其放入寄存器中。
例如:
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
}
要进行这样的优化,需要证明在调用期间没有任何东西可以改变数组[1]。这很容易做到。i永远不小于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];
}
}
行为是相同的;直到现在,要证明maybeModify从未修改过数组[1]要困难得多,因为我们已经给了它一个指针;猫从袋子里出来了。现在它必须做更困难的证明:对maybeModify进行静态分析,以证明它从未写入&x+1。它还必须证明它从未保存过可以引用数组[0]的指针,这同样棘手。
现代编译器在静态分析方面越来越好,但帮助他们并使用引用总是很好的。
当然,除非进行这种巧妙的优化,编译器确实会在需要时将引用转换为指针。
编辑:在发布这个答案五年后,我发现了一个实际的技术差异,即引用不同于看待相同寻址概念的不同方式。引用可以以指针无法修改的方式修改临时对象的寿命。
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
通常,临时对象(例如通过调用createF(5)创建的对象)会在表达式末尾被销毁。然而,通过将该对象绑定到引用ref,C++将延长该临时对象的寿命,直到ref超出范围。