43

我最近刚刚花了一些时间来找出我的代码中由错字引起的错误:

if (a=b)

代替:

if (a==b)

我想知道是否有任何特殊情况要在if语句中为变量赋值,或者如果没有,为什么编译器不抛出警告或错误?

4

8 回答 8

55
if (Derived* derived = dynamic_cast<Derived*>(base)) {
   // do stuff with `derived`
}

尽管这经常被引用为反模式(“使用虚拟调度!”),但有时该Derived类型具有Base根本不具备的功能(因此,不同的功能),这是打开语义差异的好方法.

于 2013-07-16T16:15:04.587 回答
44

这是有关语法的一些历史记录。

在经典 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 的简单性和强大功能的产物,但它是邪恶的,因此编译器可以警告它。由于它也是表达常见问题的一种非常有用的方式,因此现在有一种非常简洁、健壮的方式来实现相同的行为。它有很多好的、可能的用途。

于 2013-08-26T18:08:24.323 回答
19

赋值运算符返回赋值的值。所以,我可能会在这样的情况下使用它:

if (x = getMyNumber())

我指定x为返回的值,getMyNumber并检查它是否不为零。

避免这样做,我给你一个例子只是为了帮助你理解这一点。

编辑: 添加只是一个建议。

避免此类错误,直到某些扩展,应将 if 条件写为if(NULL == ptr)而不是if (ptr == NULL)因为当您将相等检查运算符拼写==为 operator=时,编译将抛出一个左值错误if (NULL = ptr),但if (res = NULL)由编译器传递(这不是您的意思)并保留运行时代码中的错误。

人们还应该阅读有关这种代码的批评。

于 2013-07-16T16:06:30.670 回答
11

为什么编译器不抛出警告

一些编译器为条件表达式中的可疑赋值生成警告,尽管您通常必须显式启用警告。

例如,在 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) { ... }

此外,如果您愿意,可以将范围限制为xif 语句:

if (int x = Foo(); x != 0) { /* x in scope */ ... }
// x out of scope
于 2013-07-16T16:12:47.437 回答
8

这取决于您是否要编写干净的代码。最初开发 C 时,没有完全认识到干净代码的重要性,并且编译器非常简单:使用这样的嵌套赋值通常可以导致更快的代码。今天,我想不出一个好的程序员会做的任何情况。它只是使代码的可读性降低并且更难以维护。

于 2013-07-16T16:09:08.697 回答
6

我最近遇到了一个有用的案例,所以我想我会发布它。

假设您想在一个 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”。这是由于标准规定的短路行为||(除非过载)。有关短路的更多详细信息,请参阅此答案。

于 2016-06-28T23:15:46.753 回答
5

在 c++17 中可以使用:

if (<initialize> ; <conditional_expression>) { <body> }

类似于 for 循环迭代器初始化程序。

这是一个例子:

if (Employee employee = GetEmployee(); employee.salary > 100) { ... }
于 2021-04-27T10:29:24.853 回答
2

在 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一目了然,但=它们==本身显然不同。

于 2013-07-16T16:06:38.437 回答