我最近刚刚花了一些时间来找出我的代码中由错字引起的错误:
if (a=b)
代替:
if (a==b)
我想知道是否有任何特殊情况要在if
语句中为变量赋值,或者如果没有,为什么编译器不抛出警告或错误?
我最近刚刚花了一些时间来找出我的代码中由错字引起的错误:
if (a=b)
代替:
if (a==b)
我想知道是否有任何特殊情况要在if
语句中为变量赋值,或者如果没有,为什么编译器不抛出警告或错误?
if (Derived* derived = dynamic_cast<Derived*>(base)) {
// do stuff with `derived`
}
尽管这经常被引用为反模式(“使用虚拟调度!”),但有时该Derived
类型具有Base
根本不具备的功能(因此,不同的功能),这是打开语义差异的好方法.
这是有关语法的一些历史记录。
在经典 C 中,错误处理通常通过编写如下代码来完成:
int error;
...
if(error = foo()) {
printf("An error occured: %s\nBailing out.\n", strerror(error));
abort();
}
或者,只要有可能返回空指针的函数调用,就会反过来使用该成语:
Bar* myBar;
... //in old C variables had to be declared at the start of the scope
if(myBar = getBar()) {
//do something with myBar
}
但是,这种语法危险地接近于
if(myValue == bar()) ...
这就是为什么许多人认为在条件不好的样式中进行赋值,并且编译器开始警告它(至少使用-Wall
)。一些编译器允许通过添加一组额外的括号来避免此警告:
if((myBar = getBar())) { //tells the compiler: Yes, I really want to do that assignment!
然而,这很丑陋,而且有点像 hack,所以最好避免编写这样的代码。
然后 C99 出现了,允许你混合定义和语句,所以很多开发人员会经常写类似的东西
Bar* myBar = getBar();
if(myBar) {
这确实让人感到尴尬。这就是为什么最新的标准允许在条件内定义,以提供一种简短、优雅的方式来做到这一点:
if(Bar* myBar = getBar()) {
这条语句不再有危险,你明确地给变量一个类型,显然希望它被初始化。它还避免了定义变量的额外行,这很好。但最重要的是,编译器现在可以轻松捕获此类错误:
if(Bar* myBar = getBar()) {
...
}
foo(myBar->baz); //compiler error
myBar->foo(); //compiler error
如果语句中没有变量定义if
,就无法检测到这种情况。
简而言之:您问题中的语法是旧 C 的简单性和强大功能的产物,但它是邪恶的,因此编译器可以警告它。由于它也是表达常见问题的一种非常有用的方式,因此现在有一种非常简洁、健壮的方式来实现相同的行为。它有很多好的、可能的用途。
赋值运算符返回赋值的值。所以,我可能会在这样的情况下使用它:
if (x = getMyNumber())
我指定x
为返回的值,getMyNumber
并检查它是否不为零。
避免这样做,我给你一个例子只是为了帮助你理解这一点。
编辑: 添加只是一个建议。
为避免此类错误,直到某些扩展,应将 if 条件写为if(NULL == ptr)
而不是if (ptr == NULL)
因为当您将相等检查运算符拼写==
为 operator=
时,编译将抛出一个左值错误if (NULL = ptr)
,但if (res = NULL)
由编译器传递(这不是您的意思)并保留运行时代码中的错误。
人们还应该阅读有关这种代码的批评。
为什么编译器不抛出警告
一些编译器会为条件表达式中的可疑赋值生成警告,尽管您通常必须显式启用警告。
例如,在 Visual C++ 中,您必须启用C4706(或一般的 4 级警告)。我通常会尽可能多地打开警告并使代码更加明确以避免误报。例如,如果我真的想这样做:
if (x = Foo()) { ... }
然后我把它写成:
if ((x = Foo()) != 0) { ... }
编译器看到显式测试并假定分配是有意的,因此您不会在此处收到误报警告。
这种方法的唯一缺点是在条件中声明变量时不能使用它。也就是说,您不能重写:
if (int x = Foo()) { ... }
作为
if ((int x = Foo()) != 0) { ... }
从语法上讲,这是行不通的。因此,您要么必须禁用警告,要么就范围的紧密程度做出妥协x
。
更新: C++17 增加了在 if 语句 ( p0305r1 ) 的条件中使用 init 语句的能力,这很好地解决了这个问题(为了比较,不仅仅是!= 0
)。
if (x = Foo(); x != 0) { ... }
此外,如果您愿意,可以将范围限制为x
if 语句:
if (int x = Foo(); x != 0) { /* x in scope */ ... }
// x out of scope
这取决于您是否要编写干净的代码。最初开发 C 时,没有完全认识到干净代码的重要性,并且编译器非常简单:使用这样的嵌套赋值通常可以导致更快的代码。今天,我想不出一个好的程序员会做的任何情况。它只是使代码的可读性降低并且更难以维护。
我最近遇到了一个有用的案例,所以我想我会发布它。
假设您想在一个 if 中检查多个条件,并且如果其中任何一个条件为真,您想生成一条错误消息。如果您想在错误消息中包含导致错误的特定条件,您可以执行以下操作:
std::string e;
if( myMap[e = "ab"].isNotValid() ||
myMap[e = "cd"].isNotValid() ||
myMap[e = "ef"].isNotValid() )
{
// here, e has the key for which the validation failed
}
因此,如果第二个条件为真,则 e 将等于“cd”。这是由于标准规定的短路行为||
(除非过载)。有关短路的更多详细信息,请参阅此答案。
在 c++17 中可以使用:
if (<initialize> ; <conditional_expression>) { <body> }
类似于 for 循环迭代器初始化程序。
这是一个例子:
if (Employee employee = GetEmployee(); employee.salary > 100) { ... }
在 an 中做作业if
是一件相当普遍的事情,尽管人们偶然做这件事也很常见。
通常的模式是:
if (int x = expensive_function_call())
{
// ...do things with x
}
反模式是您错误地分配给事物的地方:
if (x = 1)
{
// Always true
}
else
{
// Never happens
}
您可以通过将常量或const
值放在首位来在一定程度上避免这种情况,因此您的编译器会抛出错误:
if (1 = x)
{
// Compiler error, can't assign to 1
}
=
vs.==
是你需要注意的事情。我通常在运算符周围放置空格,以便更明显地执行正在执行的操作,longname=longername
看起来很像longname==longername
一目了然,但=
它们==
本身显然不同。