指针变量和引用变量之间的区别是什么?
当前回答
直接答案
C++中的引用是什么?不是对象类型的特定类型实例。
C++中的指针是什么?某个特定的对象类型实例。
根据ISO C++对对象类型的定义:
对象类型是一种(可能是cv限定的)类型,它不是函数类型,不是引用类型,也不是cv void。
可能需要知道的是,对象类型是C++中类型宇宙的顶级类别。引用也是一个顶级类别。但指针不是。
指针和引用在复合类型的上下文中一起提到。这基本上是由于从(和扩展的)C继承的声明器语法的性质,它没有引用。(此外,自从C++11以来,有不止一种类型的引用声明器,而指针仍然是“unityped”:&+&&vs.*。)因此,在这种情况下,用类似C风格的“扩展”来起草一种特定于语言的语言是有一定道理的。(我仍然认为,声明器的语法浪费了大量的语法表达能力,使人类用户和实现都感到沮丧。因此,它们都不适合内置于新的语言设计中。不过,这是PL设计的一个完全不同的主题。)
否则,指针可以被限定为具有引用的特定类型是无关紧要的。除了语法相似性之外,它们共享的公共财产太少了,所以在大多数情况下没有必要将它们放在一起。
注意,上面的语句只提到“指针”和“引用”作为类型。关于它们的实例(如变量),有一些有趣的问题。还有太多的误解。
顶级类别的差异已经揭示了许多与指针无关的具体差异:
对象类型可以具有顶级cv限定符。引用不能。根据抽象机器语义,对象类型的变量确实占用了存储空间。引用不必占用存储空间(有关详细信息,请参阅下面的误解部分)。...
关于引用的其他一些特殊规则:
复合声明符对引用的限制更大。引用可以折叠。基于模板参数推导过程中引用折叠的&&参数特殊规则(作为“转发引用”)允许参数的“完美转发”。引用在初始化时有特殊规则。声明为引用类型的变量的生存期可以通过扩展与普通对象不同。顺便说一句,其他一些上下文(如涉及std::initializer_list的初始化)遵循引用生命周期扩展的一些类似规则。这是另一罐蠕虫。...
误解
语法糖
我知道引用是语法糖,所以代码更容易读写。
从技术上讲,这显然是错误的。引用不是C++中任何其他特性的语法糖,因为它们不能被没有任何语义差异的其他特性完全替换。
(类似地,lambda-expressions不是C++中任何其他功能的语法糖,因为它不能用捕获变量的声明顺序这样的“未指定”财产精确模拟,这可能很重要,因为这些变量的初始化顺序可能很重要。)
在严格意义上,C++只有几种语法糖。一个实例是(继承自C)内置(非重载)运算符[],它的定义与内置运算符unary*和binary+的特定组合形式具有相同的语义财产。
存储
因此,指针和引用都使用相同的内存量。
上面的说法完全错误。为了避免这种误解,请查看ISO C++规则:
来自[intro.object]/1:
……一个物体在其建造期间、在其整个生命周期和在其毁灭期间占据一个存储区域。。。
来自[dcl.ref]/4:
未指定引用是否需要存储。
请注意,这些是语义财产。
语用学
即使在语言设计的意义上,指针不足以与引用放在一起,但仍有一些争论使得在某些其他上下文中(例如,在对参数类型进行选择时)在它们之间进行选择是有争议的。
但这并不是全部。我的意思是,你需要考虑的不仅仅是指针和引用。
如果你不必坚持这种过于具体的选择,在大多数情况下,答案很简单:你没有必要使用指针,所以你不需要。指针通常很糟糕,因为它们暗示了太多你不期望的东西,而且它们依赖于太多的隐含假设,破坏了代码的可维护性和(甚至)可移植性。不必要地依赖指针绝对是一种糟糕的风格,在现代C++的意义上应该避免。重新考虑一下你的目的,你最终会发现在大多数情况下,指针是最后一种功能。
有时语言规则明确要求使用特定类型。如果您想使用这些功能,请遵守规则。复制构造函数需要特定类型的cv-引用类型作为第一个参数类型。(通常它应该是常量限定的。)移动构造函数需要特定类型的cv-&&引用类型作为第一个参数类型。(通常不应有限定符。)运算符的特定重载需要引用或非引用类型。例如:重载运算符=作为特殊成员函数需要类似于复制/移动构造函数的第一个参数的引用类型。后缀++需要伪int。...如果您知道传递值(即使用非引用类型)就足够了,请直接使用它,特别是在使用支持C++17强制复制省略的实现时。(警告:然而,详尽地解释必要性可能非常复杂。)如果您想使用所有权操作一些句柄,请使用unique_ptr和shared_ptr之类的智能指针(如果您需要自制指针不透明,甚至可以使用它们),而不是原始指针。如果您在一个范围内进行一些迭代,请使用迭代器(或标准库尚未提供的一些范围),而不是原始指针,除非您确信原始指针在非常特定的情况下会做得更好(例如,对于较少的头部依赖性)。如果您知道通过值传递就足够了,并且需要一些显式的可空语义,请使用包装器(如std::optional),而不是原始指针。如果您知道由于上述原因,传递值并不理想,并且您不希望使用可为null的语义,请使用{lvalue,rvalue,forward}-引用。即使您确实需要像传统指针那样的语义,也通常有更合适的方法,例如库基础TS中的observer_ptr。
在当前语言中无法解决以下唯一的例外:
当您在上面实现智能指针时,可能必须处理原始指针。特定的语言互操作例程需要指针,如运算符new。(然而,cv void*与普通对象指针相比仍有很大的不同和安全性,因为它排除了意外的指针算法,除非您依赖于void*上的一些非一致扩展,如GNU的。)函数指针可以从lambda表达式转换而不需要捕获,而函数引用则不能。对于这种情况,您必须在非泛型代码中使用函数指针,即使您故意不希望值为空。
因此,在实践中,答案是显而易见的:当有疑问时,避免使用指针。只有在有非常明确的理由认为没有其他更合适的时候,才必须使用指针。除了上面提到的一些例外情况外,这些选择几乎总是不是纯C++特定的(但可能是特定于语言实现的)。此类实例可以是:
您必须为旧式(C)API服务。您必须满足特定C++实现的ABI要求。您必须基于特定实现的假设,在运行时与不同的语言实现(包括各种程序集、语言运行时和某些高级客户端语言的FFI)进行互操作。在某些极端情况下,您必须提高翻译(编译和链接)的效率。在某些极端情况下,您必须避免符号膨胀。
语言中立警告
如果你通过谷歌搜索结果(不是C++特有的)看到这个问题,这很可能是错误的地方。
C++中的引用相当“奇怪”,因为它本质上不是一级的:它们将被视为被引用的对象或函数,因此它们没有机会支持一些一级操作,例如独立于被引用对象的类型而成为成员访问运算符的左操作数。其他语言可能对其引用有类似的限制,也可能没有。
C++中的引用可能不会保留不同语言之间的含义。例如,引用通常并不意味着像C++中那样的值具有非空财产,因此这种假设在某些其他语言中可能不起作用(并且很容易找到反例,例如Java、C#…)。
一般来说,在不同编程语言中的引用之间仍然可以有一些常见的财产,但让我们把它留给SO中的其他一些问题。
(附带说明:这个问题可能比任何“类C”语言都要早,比如ALGOL 68与PL/I。)
其他回答
虽然引用和指针都用于间接访问另一个值,但引用和指针之间有两个重要的区别。第一个是引用总是引用一个对象:在没有初始化引用的情况下定义引用是错误的。赋值行为是第二个重要区别:赋值给引用会更改引用绑定的对象;它不会将引用重新绑定到另一个对象。初始化后,引用始终引用同一基础对象。
考虑这两个程序片段。首先,我们将一个指针分配给另一个指针:
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
此赋值更改了ri引用的值ival,而不是引用本身。赋值后,两个引用仍然引用其原始对象,并且这些对象的值现在也相同。
这个程序可能有助于理解问题的答案。这是一个引用“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
简单地说,我们可以说引用是变量的替代名称,指针是保存另一个变量地址的变量。例如
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 */
另一个区别是,可以有指向void类型的指针(这意味着指向任何对象的指针),但禁止引用void。
int a;
void * p = &a; // ok
void & p = a; // forbidden
我不能说我真的很满意这种特殊的差异。我更希望它能被允许有意义地引用任何有地址的东西,否则引用行为相同。它将允许使用引用定义一些C库函数的等价物,如memcpy。
不能像指针一样取消引用,当取消引用时,
引用和指针都是通过地址工作的。。。
so
你可以这样做
int*val=0xDEADBEEF;*val是0xDEADBEEF的值。
你不能这样做int&V=1;
*不允许使用val。
推荐文章
- cplusplus.com给出的错误、误解或坏建议是什么?
- 找出质数最快的算法是什么?
- c++枚举类可以有方法吗?
- 格式化IO函数(*printf / *scanf)中的转换说明符%i和%d之间的区别是什么?
- 将析构函数设为私有有什么用?
- main()中的Return语句vs exit()
- 为什么c#不提供c++风格的'friend'关键字?
- 如果不是内存地址,C指针到底是什么?
- 在函数的签名中添加关键字
- 我如何在Visual Studio中预处理后看到C/ c++源文件?
- 为什么在标准容器中使用std::auto_ptr<>是错误的?
- 用比较double和0
- 在c#中创建一个对象的副本
- 保护可执行文件不受逆向工程的影响?
- 在c++中字符串前面的“L”是什么意思?