指针变量和引用变量之间的区别是什么?
当前回答
“我知道引用是语法糖,所以代码更容易读写”
这引用不是实现指针的另一种方式,尽管它涵盖了大量的指针用例。指针是一种数据类型——通常指向实际值的地址。然而,它可以设置为零,或者使用地址算术等设置在地址之后的几个位置。对于具有自己值的变量,引用是“语法糖”。
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的引用,因此两者都是整数,所以“引用”算术意味着例如将x引用的值加1。
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。
我觉得还有一点还没有在这里讨论。
与指针不同,引用在语法上等同于它们所引用的对象,即可以应用于对象的任何操作都适用于引用,并且具有完全相同的语法(当然,初始化是例外)。
虽然这可能看起来很肤浅,但我认为这一特性对于一些C++特性来说至关重要,例如:
模板。因为模板参数是鸭子类型的,所以类型的语法财产才是最重要的,所以通常同一个模板可以同时用于T和T&。(或std::reference_wrapper<T>,它仍然依赖于隐式转换至T&)覆盖T&和T&&的模板更为常见。L值。考虑语句str[0]=“X”;如果没有引用,它只适用于c字符串(char*str)。通过引用返回字符允许用户定义的类具有相同的符号。复制构造函数。从语法上讲,将对象传递给复制构造函数是有意义的,而不是传递给对象的指针。但复制构造函数无法按值获取对象,这将导致对同一复制构造函数的递归调用。这将引用作为此处的唯一选项。操作员过载。通过引用,可以在保留相同的中缀符号的同时,将间接指向引入运算符调用,例如运算符+(const T&a,const T&b)。这也适用于常规重载函数。
这些点赋予了C++和标准库相当大的一部分权力,因此这是参考文献的一个重要属性。
这个程序可能有助于理解问题的答案。这是一个引用“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];
}
}
优化编译器可能会意识到,我们正在访问一个[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超出范围。
以下答案和链接的摘要:
指针可以被重新分配任意次数,而引用在绑定后不能被重新分配。指针可以指向任何地方(NULL),而引用总是指向对象。不能像使用指针那样获取引用的地址。没有“引用算术”(但您可以获取引用指向的对象的地址,并对其进行指针算术,如&obj+5中所示)。
澄清误解:
C++标准非常小心,避免规定编译器如何实现引用,但每个C++编译器都实现引用作为指针。即,声明如下:int&ri=i;如果没有完全优化,则分配相同数量的存储作为指针,并放置地址把我的东西放进那个储藏室。
因此,指针和引用都使用相同的内存量。
作为一般规则,
使用函数参数和返回类型中的引用来提供有用的自记录接口。使用指针实现算法和数据结构。
有趣的阅读:
我最喜欢的C++常见问题解答。参考与指针。参考文献简介。参考和常量。