#include<iostream>
using namespace std;
int &fun()
{
static int x = 10;
return x;
}
int main()
{
fun() = 30;
cout << fun();
return 0;
}
函数fun()通过引用返回值,但是在main()方法中我将一些int赋给函数。理想情况下,编译器应该显示像左值所需的错误但在上面的情况下程序工作正常。为什么会这样?
答案 0 :(得分:7)
说“一个函数返回一些东西”是松散而草率的语言。如果你知道如何使用它,那么它可以作为速记,但在这种情况下你会感到困惑。
更正确的思考方式是评估函数调用表达式。这样做会为您提供值。值可以是右值或左值(模数详细信息)。
当T
是对象类型并且您评估具有返回类型T
的函数时,您将获得类型为T
的值,这是一个右值。另一方面,如果函数具有返回类型T &
,则会得到类型T
的值,这是一个左值(值是绑定到return
中引用的值语句)。
答案 1 :(得分:4)
返回引用非常有用。
例如std::map::operator[]
就是这样。我希望你喜欢写my_map[key] = new_value;
的可能性。
如果常规(非操作员)函数返回一个引用,那么可以分配给它,我没有看到任何理由应该禁止它。
如果您真的想要,可以通过返回const X&
或返回X
来阻止分配。
答案 2 :(得分:2)
这是因为该函数的结果 是一个左值。参考文献是左值。基本上,在从函数返回非const引用的整个过程中,能够分配给它(或对引用对象执行其他修改)。
答案 3 :(得分:2)
除了其他答案,请考虑以下代码:
SomeClass& func() { ... }
func().memberFunctionOfSomeClass(value);
这是一件非常自然的事情,如果你希望编译器给你一个错误,我会非常惊讶。
现在,当你写some_obj = value;
幕后真正发生的事情是你打电话给some_obj.operator =(value);
。 operator =()
只是您班级的另一个成员函数,与memberFunctionOfSomeClass()
没有区别。
总而言之,归结为:
func() = value;
// equivalent to
func().operator =(value);
// equivalent to
func().memberFunctionOfSomeClass(value);
当然这是过于简单的,这种表示法不适用于内置类型,如int
(但使用相同的机制)。
希望这可以帮助您更好地理解其他人已经根据 lvalue 解释的内容。
答案 4 :(得分:1)
我也被类似的代码所打动 - 一拳。这是“为什么我为函数调用分配值,为什么编译器对它感到满意?”我质问自己。但是当你看到“背后”发生的事情时,它确实有意义。
正如cpp和其他人一样,左值是具有地址的“记忆位置”,我们可以为它们赋值。您可以找到有关左值和右值on the internet主题的更多信息。
当我们看看这个功能时:
int& fun()
{
static int x = 10;
return x;
}
我感动了&amp;对于类型,所以我们更明显地返回对int的引用 我们看到我们有 x ,这是左值 - 它有地址,我们可以分配给它。它也是静态的,这使它变得特殊 - 如果它不是静态的,变量的生命周期(范围)将在离开函数时以堆栈展开结束,然后引用可以指向宇宙中存在的任何黑洞。但是由于 x 是静态的,即使在我们离开函数之后(当我们再次返回函数时)它也会存在,并且我们可以在函数外部访问它。
我们正在返回对int的引用,因为我们返回 x ,所以它引用了 x 。然后我们可以使用引用来改变函数外部的 x 。所以:
int main()
{
fun();
我们只是调用该函数。创建变量 x (在趣味函数范围内),其值为10。即使在离开函数之后,它的地址和价值仍然存在 - 但我们不能使用它的值,因为我们没有它的地址。
fun() = 30;
我们调用该函数,然后更改 x 的值。 x 值通过函数返回的引用进行更改。 注意:该函数名为 first ,只有在函数调用完成后,才会进行赋值。
int& reference_to_x = fun(); // note the &
现在我们(最后)保持对函数返回的 x 的引用。现在我们可以在不先调用函数的情况下更改 x 。 ( reference_to_x 可能与乐趣函数中的 x 具有相同的地址)
int copy_of_x = fun(); // no & this time
这次我们创建新的int,我们只是复制 x 的值(通过引用)。这个新的int有自己的地址,它没有指向像 reference_to_x 那样的 x 。
reference_to_x = 5;
我们通过引用为 x 指定了值5,我们甚至没有调用该函数。 copy_of_x 不会更改。
copy_of_x = 15;
我们将新的int更改为值15. x 未更改,因为 copy_of_x 有自己的地址。
}
正如6502和其他人指出的那样,我们使用类似的方法,在容器和自定义覆盖中大量返回引用。
std::map<std::string, std::string> map = {};
map["hello"] = "Ahoj";
// is equal to
map.operator[]("hello") = "Ahoj"; // returns reference to std::string
// could be done also this way
std::string& reference_to_string_in_map = map.operator[]("hello");
reference_to_string_in_map = "Ahoj";
我们使用的地图功能可以声明如下:
std::string& map::operator[]( const std::string& key ); // returns reference
我们没有地址到我们在地图中“存储”的字符串,所以我们调用map的这个重写函数,传递它的键,以便map知道我们想要访问哪个字符串,并返回我们对它的引用string,我们可以用来改变这个值。 注意:再次调用该函数,并且只有在它完成后才会调用(映射找到正确的字符串并返回对它的引用)。这就像有趣()= 10,只有更美好......
希望这有助于那些即使在阅读其他答案之后仍然无法理解所有内容的人......
答案 5 :(得分:0)
L值是 locator-value 。这意味着它有地址。参考文献清楚地有一个地址。如果您从fun()返回值,则可以获得左值:
#include<iostream>
using namespace std;
int fun()
{
static int x = 10;
return x;
}
int main()
{
fun() = 30;
cout << fun();
return 0;
}
答案 6 :(得分:0)
您可以使用指针重写代码,这可能更容易理解:
#include<iostream>
using namespace std;
int *fun() //fun defined to return pointer to int
{
static int x = 10;
return &x; // returning address of static int
}
int main()
{
*fun() = 30; //execute fun(), take its return value and dereference it,
//yielding an lvalue, which you can assign to.
cout << *fun(); //you also need to dereference here
return 0;
}
从语法的角度来看,引用可能会非常混乱,因为基础“指针”的取消引用是由编译器为您隐式完成的。指针版本看起来更复杂,但其符号更清晰或更明确。
PS:在有人反对我将引用视为一种指针之前,两种代码版本的反汇编都是100%相同的。
PPS:当然,这种方法是对封装的非常隐蔽的违反。正如其他人指出的那样,该技术有 种用法,但是在没有充分理由的情况下,您绝对不能做类似的事情。