鉴于:
int& foo(); // don't care what the reference is to
int intVal;
在以下两种情况下,右手边是同一个函数调用
int& intRef = foo();
intVal = foo(); // a reference is returned... a value is assigned.
在第二种情况下,返回的引用如何“转换”为一个值?
它是由 int 的赋值运算符完成的吗?
在语言级别,没有“取消引用”这样的概念。引用实现了左值的概念。变量和引用基本上是一回事。变量和引用之间的唯一区别是变量由编译器自动绑定到其在存储中的位置,而引用通常在运行时通过用户操作绑定。
intRef
在您的示例中, and之间没有概念上的区别intVal
。两者都是 type 的左值int
。在概念层面,两者都是通过相同的机制访问的。您甚至可以将程序中的所有变量视为引用,编译器已为您隐式预绑定。这基本上就是 Bjarne Stroustrup 在 TC++PL 中的意思,他说(不是逐字)人们可以将引用视为现有变量的替代名称。
唯一可以察觉两者之间差异的时刻是创建这些实体并初始化它们时。引用的初始化是将其绑定到存储中某个位置的行为。变量的初始化是将初始值复制到现有存储中的行为。
但是一旦一个引用被初始化,它就像一个普通的变量:读/写一个引用的行为就是读/写它所绑定的存储位置的行为。获取引用的地址会评估它绑定到的存储位置的地址。等等。
众所周知,在许多情况下,引用在内部实现为变相的指针,即作为一个不可见的指针,每次访问它时都会为您隐式取消引用。在这种情况下(当它真正通过指针实现时),每次访问它时都会再次完成取消引用。因此,正如您在问题中所问的那样,不是赋值运算符执行此操作。正是您在代码中提到了该引用的名称导致不可见指针被取消引用。
然而,实现“现有变量的替代名称”的实体不一定需要对其自身进行存储,即在编译语言中,它不需要由任何材料(如隐藏指针)表示。这就是语言标准在 8.3.2 中声明“未指定引用是否需要存储”的原因。
foo
正在返回对“int”类型对象的一些引用。我们不会关心那个“int”是从哪里来的,我们只是假设它存在。
第一行,int& intRef = foo()
创建intRef
它也引用了与返回值所引用的“int”类型完全相同的对象foo
。
第二行,将 的值intVal
替换为返回的引用所引用的对象的值。
回应您的评论:
您似乎对指针和引用感到非常困惑。引用就像一个对象的别名。对引用做任何事情实际上都会影响它所引用的对象。
没有取消引用引用这样的事情。您只能取消引用指针。取消引用是使用一元运算*
符获取点指向的对象的行为。例如,如果你有一个int* p
,你可以做得到*p
它指向的对象。这是取消引用 p
。
您唯一可以*
对引用执行的操作是它所引用的对象是指针(或者它是否重载operator*
)。在您的情况下,由于foo
返回一个int&
,我们不能取消引用它。该表达式*foo()
将无法编译。那是因为返回值的foo
类型是“int”,它不是指针,也不会重载operator*
。
出于所有意图和目的,您可以将返回的引用foo
简单地视为它所引用的对象。将此值分配给intVal
实际上与在以下代码中分配x
给没有什么不同:intVal
int intVal;
int x = 5;
intVal = x;
正如我确定你理解的那样,intVal
被赋予 的值x
。这是由标准简单定义的:
在简单赋值 (
=
) 中,表达式的值替换左操作数引用的对象的值。
根本不需要进行转换,因为运算符的两边都是相同的类型。
这和你的情况真的没有什么不同。你只有:
intVal = some_ref_to_int;
some_ref_to_int
表达式在哪里foo()
。它是参考的事实并不重要。intVal
接收引用表示的对象的值。
赋值给intVal
是标准中 5.17 [exp.ass] 中定义的赋值表达式。赋值表达式的语法规则相当复杂,取决于其他几个语法规则,但基本上你需要在运算符左侧有一个可修改的左值,在右侧=
有一个纯右值表达式。
如果是
intVal = foo();
RHS 上的表达式是 type 的左值int
,因此会发生内置的左值到右值的转换......这几乎不是一种转换,因为值不会改变,类型也不会改变(除了基本类型 cv-qualifiers 被删除,所以如果左值是 type const int
prvalue 将是 type int
)。[conv.lval] 说
非函数、非数组类型的 glvalue (3.10)
T
可以转换为纯右值。[...] 如果T
是非类类型,则纯右值的类型是T
. 否则,纯右值的类型是T
。[...]glvalue 指示的对象中包含的值是纯右值结果。
所以prvalue的类型int
和值foo()
与返回的引用绑定到的变量的值相同。
赋值表达式的规则说:
在简单赋值 (
=
) 中,表达式的值替换左操作数引用的对象的值。
所以 的值intVal
将被prvalue的值代替。规则继续:
如果左操作数不是类类型,则表达式被隐式转换(第 4 条)为左操作数的 cv 非限定类型。
因此,因为int
不是类类型(因此没有重载operator=
,它只使用内置的赋值运算符),赋值会将 RHS 转换为int
,这是它在您的情况下已经具有的类型。
所以 的值intVal
被设置为prvalue的值,我们说的就是glvalue表达式foo()
的值,即引用绑定到的变量的值。
请注意,左值到右值的转换与 RHS 作为参考无关。同样的事情发生在这里:
int val = 0;
intVal = val;
val
是类型的左值,int
因此它被转换为类型的纯右int
值,并且 的值intVal
被设置为该纯右值的值。
规则是根据表达式的“值类别”(即左值或右值)来表达的,而不是它是否是引用。为了实现所需的行为,编译器会隐式且不可见地对所需的引用进行任何“取消引用”。