【问题标题】:What does the "=" operator return?“=”运算符返回什么?
【发布时间】:2017-01-02 18:24:38
【问题描述】:

据我了解,C++ 是从左到右工作的。例如,如果我这样做:

std::cout << "output" << "stream";

C++ 首先从最左边的东西(std::cout)开始,接下来是

“=”运算符返回什么?

如果我这样做

double wage = 5;
double salary = wage = 9999.99;

我认为“=”运算符只会返回“=”的左操作数或右操作数。所以,按照我的逻辑,在薪水初始化行中,薪水用工资的值初始化,然后“=”运算符返回薪水或工资(比方说薪水),然后将 9999.99 分配给薪水,但工资是消失了,它应该保持 5 的值。

但是当我在“salary”初始化后检查“salary”和“wage”的值时,两者的值都是9999.99。如果我应用与上述 std::cout 相同的逻辑,则应该只有一个变量,“salary”或“wage”,其值为 9999.99

【问题讨论】:

  • “C++ 从左到右工作” 这取决于操作员。 = 特别是右结合;它从右到左工作。
  • @Igor:double salary = wage = 9999.99; 中只有一个赋值运算符,因此关联性甚至无关紧要。

标签: c++


【解决方案1】:

赋值运算符是从右到左结合的,一般来说通过引用返回它的左操作数。一般而言,这适用于我能想到的所有内置类型、库类型,这就是您编写赋值运算符的方式。

也就是说

double salary = wage = 9999.99;

完全一样

wage = 9999.99;
double salary = wage;

请注意,在此处的第二行中,salary 设置为 wage,而不是 9999.99。区别在这里并不重要,但在某些情况下可能。例如(感谢 Justin Time):

double salary = --(wage = 9999.99);

salary 显然得到了 9998.99 的值,但重要的是要注意 wage 也是如此;如果 assignment 返回了正确的参数,那么 wage 最终仍将是 9999.99,因为在分配到工资后临时值会递减。

正如 Ben Voigt 在下面的 cmets 中指出的那样,虽然我的回答是正确的,但我使用的问题中的示例有点误导。这是因为尽管= 标记在相关代码中出现了两次,double salary = wage = 9999.99 实际上并没有调用两次赋值,而是调用赋值然后构造一个新的双精度。这个例子最好继续如下:

double salary = 0.0;
salary = wage = 9999.99;

现在我们是真正的链式赋值运算符,我之前所有关于优先级和返回的 cmet 都适用于这里的第二行代码。

【讨论】:

  • The std::atomic template is an exceptionoperator= 通过引用返回 *this 的规则。
  • @BenVoigt 这是一个有趣的例子。然而值得注意的是,这是一个赋值运算符,但它不是类的复制赋值运算符。相反,它是异构分配。我在考虑真正的复制赋值运算符,但没有写进正文。
  • 顺便说一句,通过包含一个关联性很重要的示例,您的答案将得到增强。问题中的代码仅包含赋值运算符的一种用法,尽管标记 = 出现了 3 次。
  • 区分很重要的一种情况,例如,the reference returned by the right = is modified before the left = is reached。例如,在此处显示的第三种情况下,salary == 9998.99 因为我预先递减了(wage = 9999.99)。 [注意:理想情况下,这样的事情只会出现在混淆代码竞赛中。不过,以防万一,了解这些信息仍然很有用。]
  • @BenVoigt 你是对的,让我修改我的答案以使其更准确。
【解决方案2】:

咨询operator precedence table。赋值运算符= 是“从右到左”的关联性。所以表达式被有效地评估为:

double salary = (wage = 9999.99);

wage = 9999.99 首先出现。

【讨论】:

  • 这与关联性无关,而与初始化语句的语法有关。
  • 如果这不是初始化,而是已经声明的变量的赋值语句,例如x= 1; x = y = z; 那么关联性会变得相关吗?
  • −1 关联性与单个分配无关。
【解决方案3】:

在你的例子中

double wage = 5;
double salary = wage = 9999.99;

只有一个作业,即

wage = 9999.99

= 的其他实例是初始化语法的一部分。

分配返回对所分配对象的引用,在本例中为 wage。所以例子等价于

double wage = 5;
wage = 9999.99;
double salary = wage;

如果您的示例被重写为使用多个分配,

double wage;
double salary;
wage = 5;
salary = wage = 9999.99;

... 那么赋值运算符是右关联的就变得很重要了。因此最后一行相当于

salary = (wage = 9999.99);

...括号中的表达式返回对wage的引用。


标准容器要求用户定义的赋值运算符返回对已分配对象的引用。核心语言没有这样的要求,所以人们可能会想使用void。 IE。保证效率,不支持基于不良副作用的代码,更简洁的实现,这些都很好:

struct S
{
    void operator=( S );    // OK with respect to core language rules.
};

但是,为了给deletedefault 赋值运算符,运算符的声明必须给它引用返回类型:

struct T
{
    auto operator=( T const& ) -> T&    // But `void` won't work here.
        = delete;
};

在第三只手上,冗长的、无保证的效率和基于不良支持的副作用的代码operator= 可以用单独的void 成员函数表示,它甚至可以是运算符:

struct U
{
    void assign( U other );

    auto operator=( U const& other )
        -> U&
    {
        assign( other );
        return *this;
    }
};

【讨论】:

    【解决方案4】:

    表达式有一个和一个副作用。该值(您称之为“返回”)将用于该表达式作为其操作数的另一个运算符。 (即使没有这样的运算符,它仍然会被计算出来)。查找表达式值的过程称为值计算

    该值可以是任何value category。副作用是发生的任何其他事情,例如更新内存位置或调用函数。

    一般来说,在值用于更大的子表达式之前,可能会或可能不会出现副作用。众所周知,后缀-++ 运算符可能在值和副作用之间的时间上有很大差异。

    对于赋值表达式,值是左操作数(值类别为“左值”),副作用是左操作数指定的对象被更新为保存右操作数的值;并且副作用是在值计算之前排序,这保证了如果更大的表达式使用该值,那么它会指定一个已经将新值存储在其中的内存位置。

    如果代码是:

    salary = wage = 9999.0;
    

    那么操作符关联规则将意味着它是salary = (wage = 9999.0);。内部表达式有值wage,值类别lvalue,内部表达式的副作用是名为wage的变量在其中存储了9999.0,并且赋值运算符的顺序保证了结果在我们进入下一个最外层表达式之前,它已经存储在那里。

    那么我们有salary = X,其中X是上一段中描述的值,即它等价于salary = wage;


    请注意,您的实际代码是一个声明,声明中的第一个 = 符号不是赋值运算符。相反,它是一个初始化器即将到来的语法标记;你的代码是一样的:

    double salary { wage = 9999.0 };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 2012-08-23
      • 2021-04-28
      • 2013-11-21
      • 2017-11-03
      • 2014-08-03
      相关资源
      最近更新 更多