我可以做x = y = z。在C ++中为什么不允许x <y <z?

17 c++

我是编程新手,并且有关于在一行上使用多个运算符的问题。

说,我有

int x = 0;
int y = 1;
int z = 2;
Run Code Online (Sandbox Code Playgroud)

在此示例中,我可以使用分配操作符链: x = y = z;

然而,我怎么就不能使用:x < y < z;

Yks*_*nen 19

您可以这样做,但结果将达不到您的期望。

bool可以隐式转换为int。在这种情况下,falsevalue将是0truevalue将是1

假设我们有以下内容:

int x = -2;
int y = -1;
int z = 0;
Run Code Online (Sandbox Code Playgroud)

表达式x < y < z将按以下方式求值:

x < y < z
(x < y) < z
(-2 < -1) < 0
(true) < 0
1 < 0
false
Run Code Online (Sandbox Code Playgroud)

运算符=不同,因为它的工作方式不同。它返回其左侧操作数(在赋值操作之后),因此您可以将其链接:

x = y = z
x = (y = z)
//y holds the value of z now
x = (y)
//x holds the value of y now
Run Code Online (Sandbox Code Playgroud)

gcc尝试使用后给我以下警告x < y < z

prog.cc:18:3: warning: comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
   18 | x < y < z;
      | ~~^~~
Run Code Online (Sandbox Code Playgroud)

这是不言自明的。它有效,但是却不如预期。



注意:类可以定义自己的类operator=,当被链接时,类也可能会做意外的事情(没有什么比不遵循基本规则和惯用法的运算符更好的表达了“我恨你”)。幸运的是,对于像int

class A
{
public:
    A& operator= (const A& other) 
    {
        n = other.n + 1;
        return *this;
    }

    int n = 0;
};

int main()
{
    A a, b, c;
    a = b = c;
    std::cout << a.n << ' ' << b.n << ' ' << c.n; //2 1 0, these objects are not equal!
}
Run Code Online (Sandbox Code Playgroud)

甚至更简单:

class A
{
public:
    void operator= (const A& other) 
    {
    }

    int n = 0;
};

int main()
{
    A a, b, c;
    a = b = c; //doesn't compile
}
Run Code Online (Sandbox Code Playgroud)

  • “ =”运算符不会返回“提供”给它的相同类型和相同值。赋值表达式的结果是一个指向左操作数的左值。当左操作数不是类类型时,分配的值是转换为左操作数类型的右操作数。例如,给定“ int y”,“ y = 3.5”的值为3,而不是3.5。 (2认同)

Ted*_*gmo 13

x = y = z

您可以考虑内置的赋值运算符,=对于基本类型返回对要分配给该对象的引用。这就是为什么上面的工作并不奇怪。

y = z返回对的引用y,然后
x = y

x < y < z

“小于”运算符会<返回truefalse,它将使比较之一与truefalse而不是实际变量进行比较。

x < y返回truefalse,然后
truefalse< z其中布尔被提升到int在其结果
1 or 0 < z


解决方法:

x < y < z 应该写成:
x < y && y < z

如果您经常进行这种手动BinaryPredicate链接,或者有很多操作数,则很容易犯错误并忘记了链接中某个位置的条件。在这种情况下,您可以创建辅助函数来为您进行链接。例:

// matching exactly two operands
template<class BinaryPredicate, class T>
inline bool chain_binary_predicate(BinaryPredicate p, const T& v1, const T& v2)
{
    return p(v1, v2);
}

// matching three or more operands
template<class BinaryPredicate, class T, class... Ts>
inline bool chain_binary_predicate(BinaryPredicate p, const T& v1, const T& v2,
                                   const Ts&... vs)
{
    return p(v1, v2) && chain_binary_predicate(p, v2, vs...);
}
Run Code Online (Sandbox Code Playgroud)

这是使用std :: less的示例:

// bool r = 1<2 && 2<3 && 3<4 && 4<5 && 5<6 && 6<7 && 7<8
bool r = chain_binary_predicate(std::less<int>{}, 1, 2, 3, 4, 5, 6, 7, 8); // true
Run Code Online (Sandbox Code Playgroud)


g.k*_*esz 5

这是因为您将这些表达式视为“运算符链”,而 C++没有这样的概念。C++ 将分别执行每个运算符,按照由它们的优先级和关联性确定的顺序 ( https://en.cppreference.com/w/cpp/language/operator_precedence )。

(在 C Perkins 的评论后扩展)

詹姆斯,你的困惑来自于x = y = z;将链式运算符视为一些特殊情况。事实上,它遵循与所有其他案例相同的规则。

这个表达式的行为就像它一样,因为赋值=是从右到左关联的,并返回它的右手操作数。没有特殊规则,不要指望它们x < y < z

顺便说一句,x == y == z也不会像您期望的那样工作。

另请参阅此答案


Eas*_*sPi 5

C 和 C++ 实际上没有“链式”操作的概念。每个操作都有一个优先级,它们只是使用最后一个操作的结果来遵循优先级,就像数学问题一样。

注意:我进行了一个认为有帮助的低级解释。

如果您想阅读历史解释,Davislor 的回答可能对您有所帮助。

我还在底部放了一个 TL;DR。


例如,std::cout实际上并未链接:

std::cout << "Hello!" << std::endl;
Run Code Online (Sandbox Code Playgroud)

实际上是使用<<从左到右求值的属性并重用*this返回值,所以它实际上是这样做的:

std::ostream &tmp = std::ostream::operator<<(std::cout, "Hello!");
tmp.operator<<(std::endl);
Run Code Online (Sandbox Code Playgroud)

(这就是为什么printf通常比std::cout非平凡输出更快,因为它不需要多个函数调用)。

您实际上可以在生成的程序集中看到这一点(使用正确的标志):

#include <iostream>

int main(void)
{
    std::cout << "Hello!" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

clang++ --target=x86_64-linux-gnu -Oz -fno-exceptions -fomit-frame-pointer -fno-unwind-tables -fno-PIC -masm=intel -S

我在下面展示了 x86_64 程序集,但别担心,我记录了它,解释了每条指令,所以任何人都应该能够理解。

我对符号进行了破坏和简化。没有人愿意读std::basic_ostream<char, std::char_traits<char> >50遍。

    # Logically, read-only code data goes in the .text section. :/
    .globl main
main:
    # Align the stack by pushing a scratch register.
    # Small ABI lesson:
    # Functions must have the stack 16 byte aligned, and that
    # includes the extra 8 byte return address pushed by
    # the call instruction.
    push   rax

    # Small ABI lesson:
    # On the System-V (non-Windows) ABI, the first two
    # function parameters go in rdi and rsi. 
    # Windows uses rcx and rdx instead.
    # Return values go into rax.

    # Move the reference to std::cout into the first parameter (rdi)

    # "offset" means an offset from the current instruction,
    # but for most purposes, it is used for objects and literals
    # in the same file.
    mov    edi, offset std::cout

    # Move the pointer to our string literal into the second parameter (rsi/esi)
    mov    esi, offset .L.str

    # rax = std::operator<<(rdi /* std::cout */, rsi /* "Hello!" */);
    call   std::operator<<(std::ostream&, const char*)

    # Small ABI lesson:
    # In almost all ABIs, member function calls are actually normal
    # functions with the first argument being the 'this' pointer, so this:
    #   Foo foo;
    #   foo.bar(3);
    # is actually called like this:
    #   Foo::bar(&foo /* this */, 3);

    # Move the returned reference to the 'this' pointer parameter (rdi).
    mov     rdi, rax

    # Move the address of std::endl to the first 'real' parameter (rsi/esi).
    mov     esi, offset std::ostream& std::endl(std::ostream&)

    # rax = rdi.operator<<(rsi /* std::endl */)
    call    std::ostream::operator<<(std::ostream& (*)(std::ostream&))

    # Zero out the return value.
    # On x86, `xor dst, dst` is preferred to `mov dst, 0`.
    xor     eax, eax

    # Realign the stack by popping to a scratch register.
    pop     rcx

    # return eax
    ret

    # Bunch of generated template code from iostream

    # Logically, text goes in the .rodata section. :/
    .rodata
.L.str:
    .asciiz "Hello!"
Run Code Online (Sandbox Code Playgroud)

无论如何,= 运算符是从右到左的运算符。

struct Foo {
    Foo();
    // Why you don't forget Foo(const Foo&);
    Foo& operator=(const Foo& other);
    int x; // avoid any cheating
};

void set3Foos(Foo& a, Foo& b, Foo& c)
{
    a = b = c;
}
Run Code Online (Sandbox Code Playgroud)
void set3Foos(Foo& a, Foo& b, Foo& c)
{
    // a = (b = c)
    Foo& tmp = b.operator=(c);
    a.operator=(tmp);
}
Run Code Online (Sandbox Code Playgroud)

注意:这就是为什么 3 规则/5 规则很重要,以及为什么内联这些也很重要:

set3Foos(Foo&, Foo&, Foo&):
    # Align the stack *and* save a preserved register
    push    rbx
    # Backup `a` (rdi) into a preserved register.
    mov     rbx, rdi
    # Move `b` (rsi) into the first 'this' parameter (rdi)
    mov     rdi, rsi
    # Move `c` (rdx) into the second parameter (rsi)
    mov     rsi, rdx
    # rax = rdi.operator=(rsi)
    call    Foo::operator=(const Foo&)
    # Move `a` (rbx) into the first 'this' parameter (rdi)
    mov     rdi, rbx
    # Move the returned Foo reference `tmp` (rax) into the second parameter (rsi)
    mov     rsi, rax
    # rax = rdi.operator=(rsi)
    call    Foo::operator=(const Foo&)
    # Restore the preserved register
    pop     rbx
    # Return
    ret
Run Code Online (Sandbox Code Playgroud)

这些“链”因为它们都返回相同的类型。

<返回bool

set3Foos(Foo&, Foo&, Foo&):
    # Align the stack *and* save a preserved register
    push    rbx
    # Backup `a` (rdi) into a preserved register.
    mov     rbx, rdi
    # Move `b` (rsi) into the first 'this' parameter (rdi)
    mov     rdi, rsi
    # Move `c` (rdx) into the second parameter (rsi)
    mov     rsi, rdx
    # rax = rdi.operator=(rsi)
    call    Foo::operator=(const Foo&)
    # Move `a` (rbx) into the first 'this' parameter (rdi)
    mov     rdi, rbx
    # Move the returned Foo reference `tmp` (rax) into the second parameter (rsi)
    mov     rsi, rax
    # rax = rdi.operator=(rsi)
    call    Foo::operator=(const Foo&)
    # Restore the preserved register
    pop     rbx
    # Return
    ret
Run Code Online (Sandbox Code Playgroud)

它从左到右计算:

bool isInRange(int x, int y, int z)
{
    return x < y < z;
}
Run Code Online (Sandbox Code Playgroud)
isInRange(int, int, int):
    # ret = 0 (we need manual zeroing because setl doesn't zero for us)
    xor    eax, eax
    # (compare x, y)
    cmp    edi, esi
    # ret = ((x < y) ? 1 : 0);
    setl   al
    # (compare ret, z)
    cmp    eax, edx
    # ret = ((ret < z) ? 1 : 0);
    setl   al
    # return ret
    ret
Run Code Online (Sandbox Code Playgroud)

特尔;博士:

x < y < z 很没用。

如果您想检查和,可能需要&&运算符。x < y y < z

bool isInRange(int x, int y, int z)
{
    bool tmp = x < y;
    bool ret = (tmp ? 1 : 0) < z;
    return ret;
}
Run Code Online (Sandbox Code Playgroud)
isInRange(int, int, int):
    # ret = 0 (we need manual zeroing because setl doesn't zero for us)
    xor    eax, eax
    # (compare x, y)
    cmp    edi, esi
    # ret = ((x < y) ? 1 : 0);
    setl   al
    # (compare ret, z)
    cmp    eax, edx
    # ret = ((ret < z) ? 1 : 0);
    setl   al
    # return ret
    ret
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

1666 次

最近记录:

6 年,2 月 前