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将是0,truevalue将是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)
Ted*_*gmo 13
x = y = z
您可以考虑内置的赋值运算符,=对于基本类型返回对要分配给该对象的引用。这就是为什么上面的工作并不奇怪。
y = z返回对的引用y,然后
x = y
x < y < z
“小于”运算符会<返回true或false,它将使比较之一与true或false而不是实际变量进行比较。
x < y返回true或false,然后
true或false< 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)
这是因为您将这些表达式视为“运算符链”,而 C++没有这样的概念。C++ 将分别执行每个运算符,按照由它们的优先级和关联性确定的顺序 ( https://en.cppreference.com/w/cpp/language/operator_precedence )。
(在 C Perkins 的评论后扩展)
詹姆斯,你的困惑来自于x = y = z;将链式运算符视为一些特殊情况。事实上,它遵循与所有其他案例相同的规则。
这个表达式的行为就像它一样,因为赋值=是从右到左关联的,并返回它的右手操作数。没有特殊规则,不要指望它们x < y < z。
顺便说一句,x == y == z也不会像您期望的那样工作。
另请参阅此答案。
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 次 |
| 最近记录: |