"if"条件下的变量赋值

Sma*_*ash 29 c++ if-statement

我最近失去了一些时间来弄清楚我的代码中的错误是由一个错字引起的:

if(a=b)
Run Code Online (Sandbox Code Playgroud)

代替:

if(a==b)
Run Code Online (Sandbox Code Playgroud)

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

Lig*_*ica 39

if (Derived* derived = dynamic_cast<Derived*>(base)) {
   // do stuff with `derived`
}
Run Code Online (Sandbox Code Playgroud)

虽然这被称为反模式("使用虚拟调度!"),但有时Derived类型具有Base根本不具备的功能(并因此具有不同的功能),这是切换语义差异的好方法.

  • @JamesKanze - 其他人发现它增加了可读性和可维护性,部分是通过最小化变量的范围."为了避免意外误用变量,通常最好将变量引入可能的最小范围.特别是,通常最好延迟变量的定义,直到可以给出初始值...这两个原则最优雅的应用之一是在条件中声明一个变量." - Stroustrup,"C++编程语言". (39认同)
  • 将定义置于条件中会缩小范围,以便在if语句之后不会在下一行意外引用`derived`.这与我们使用`for(int i = 0 ...`而不是`int i; for(i = 0; ...`)的原因相同. (38认同)
  • 反模式正在对条件进行定义.对于不可读和不可维护的代码来说,这是一个肯定的秘诀. (9认同)
  • @James:在我们的代码库采用这种方法的十年中,我的团队中没有人在阅读或维护代码时遇到问题. (9认同)
  • 在条件中定义变量时变量范围的有趣之处在于变量也可以在`else`子句中访问.`if(T t = foo()){dosomething(t); } else {dosomethingelse(t); }` (9认同)
  • @AndyThomas 有趣的报价。这是我不同意 Stroustrup 的地方。它_是_优雅的。太优雅了,到了令人困惑的地步。引用中的关键规则是,在可以为其指定初始值之前不要声明变量。关于变量的定义,有两个问题:一是需要隐式类型转换(除非它有类型`bool`)---隐式转换不好;第二个是它导致代码在预期只有一个的上下文中做两件事。 (3认同)
  • "if(我成功获得了这个值){使用这个值}"对我来说似乎很好. (3认同)
  • 在 C++17 中修复了“if (Derived*衍生=动态_cast&lt;Derived*&gt;(base);衍生!= nullptr){” - 现在明确支持在单个步骤中执行赋值和条件。不同之处在于,编译器知道如果您以这种形式完成了它,那么您就明确表示要这样做。因为它无法知道“if (a = b)”是错误还是用户的意思。 (2认同)

cma*_*ter 26

以下是有关语法的一些历史记录.

在经典C中,错误处理经常通过编写如下内容来完成:

int error;
...
if(error = foo()) {
    printf("An error occured: %s\nBailing out.\n", strerror(error));
    abort();
}
Run Code Online (Sandbox Code Playgroud)

或者,每当有一个可能返回空指针的函数调用时,该习惯用法反过来使用:

Bar* myBar;
... //in old C variables had to be declared at the start of the scope
if(myBar = getBar()) {
    //do something with myBar
}
Run Code Online (Sandbox Code Playgroud)

但是,这种语法非常危险

if(myValue == bar()) ...
Run Code Online (Sandbox Code Playgroud)

这就是为什么许多人认为条件不好的情况下的任务,编译器开始警告它(至少有-Wall).但是,可以通过添加一组额外的括号来避免此警告:

if((myBar = getBar())) {  //tells the compiler: Yes, I really want to do that assignment!
Run Code Online (Sandbox Code Playgroud)

然后C99出现了,允许你混合定义和语句,所以许多开发人员会经常写类似的东西

Bar* myBar = getBar();
if(myBar) {
Run Code Online (Sandbox Code Playgroud)

哪个感觉很尴尬.这就是为什么最新标准允许在条件内定义,以提供简短,优雅的方式来执行此操作:

if(Bar* myBar = getBar()) {
Run Code Online (Sandbox Code Playgroud)

这句话中没有任何危险,你明确地给变量一个类型,显然希望它被初始化.它还避免了额外的行来定义变量,这很好.但最重要的是,编译器现在可以轻松捕获这种错误:

if(Bar* myBar = getBar()) {
    ...
}
foo(myBar->baz);  //compiler error
//or, for the C++ enthusiasts:
myBar->foo();     //compiler error
Run Code Online (Sandbox Code Playgroud)

如果没有if语句中的变量定义,则无法检测到此条件.

简而言之:你问的语法是旧C的简单性和强大的产物,但它是邪恶的,所以编译器可以警告它.由于它也是表达常见问题的一种非常有用的方法,因此现在有一种非常简洁,错误的方法来实现相同的行为.它有很多好的,可能的用途.

  • @AndrewHenle 嗯,关于额外括号中的赋值的部分只是为了说明在实际现有代码中多次完成的操作。*当然*,额外的括号只是一个技巧。*当然*,它们看起来很丑。*当然*,这只是在 `if()` 语句中更喜欢 C++ 变量声明而不是 C 赋值的额外原因。你可能会质疑GCC警告行为的敏感性,但这对我的回答完全没有影响。 (2认同)
  • @AndrewHenle 感谢您的解释。这让我想到,指出这是一种黑客行为可能会有所帮助,并且我不鼓励使用它。 (2认同)

Mar*_*oun 13

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

if(x = getMyNumber())

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

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

编辑: 添加只是一个建议(可能会喜欢).

为了避免这样的错误 - 一些扩展,应该写条件if(NULL == ptr)而不是if(ptr == NULL)因为当你将相等检查运算符拼错==为运算符时=,编译将抛出一个左值错误if(NULL = ptr),但是if(res = NULL)由编译器传递(这不是你的意思)和仍然是运行时代码中的错误.
出于同样的原因,我宁愿写if(getMyNumber() ==x)而不是if(x == getMyNumber())

人们还应该阅读:对这种代码的批评.

  • 反转相等比较的操作数,例如写`(NULL == ptr)`而不是`(ptr == NULL)`,是有争议的.就个人而言,我*讨厌它; 我发现它很刺耳,我必须在精神上重新扭转它才能理解它.(其他人显然没有这个问题).这种做法被称为["尤达条件"](http://en.wikipedia.org/wiki/Yoda_Conditions).无论如何,大多数编译器都可以被说服以警告条件中的任务. (13认同)
  • 我想学习/提高自己,请提及你为什么要投票. (10认同)
  • 在您的编辑中提供好的建议+1! (2认同)
  • 我经常使用`if(fp = fopen("file","w")){// file not open} else {// file processing} (2认同)

小智 13

C++17中,可以使用:

if (<initialize> ; <conditional_expression>) { <body> }
Run Code Online (Sandbox Code Playgroud)

类似于for循环迭代器初始值设定项。

这是一个例子:

if (Employee employee = GetEmployee(); employee.salary > 100) { ... }
Run Code Online (Sandbox Code Playgroud)


Adr*_*thy 11

为什么编译器不抛出警告

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

例如,在 Visual C++ 中,您必须启用C4706(或一般的 4 级警告)。我通常会尽可能多地打开警告并使代码更加明确以避免误报。例如,如果我真的想这样做:

if (x = Foo()) { ... }
Run Code Online (Sandbox Code Playgroud)

然后我会写成:

if ((x = Foo()) != 0) { ... }
Run Code Online (Sandbox Code Playgroud)

编译器看到显式测试并假定分配是有意的,因此您不会在此处收到误报警告。

这种方法的唯一缺点是在条件中声明变量时不能使用它。也就是说,你不能重写:

if (int x = Foo()) { ... }
Run Code Online (Sandbox Code Playgroud)

作为

if ((int x = Foo()) != 0) { ... }
Run Code Online (Sandbox Code Playgroud)

从语法上讲,这是行不通的。因此,您要么必须禁用警告,要么对范围的紧密程度做出妥协x

更新: C++17 添加了在 if 语句 ( p0305r1 )的条件中具有 init 语句的能力,这很好地解决了这个问题(用于比较,而不仅仅是!= 0)。

if (x = Foo(); x != 0) { ... }
Run Code Online (Sandbox Code Playgroud)

此外,如果需要,您可以将范围限制x为仅 if 语句:

if (int x = Foo(); x != 0) { /* x in scope */ ... }
// x out of scope
Run Code Online (Sandbox Code Playgroud)

  • 公平地说,如果你愿意用额外的括号包围你的块,你就不必妥协你对 `x` 的范围有多紧。但我打赌这通常不值得。 (2认同)
  • @cmaster:如果您想检查除“!= 0”以外的其他内容,则需要进行显式比较。显式比较也比额外的一组括号更清晰。 (2认同)

Jam*_*nze 9

这取决于您是否要编写干净的代码.当C首次开发时,干净代码的重要性并未得到充分认识,编译器非常简单:使用这样的嵌套赋值通常会导致更快的代码.今天,我想不出一个好的程序员会做的任何情况.它只会使代码的可读性降低,维护起来也更困难.

  • 不幸的是,“干净的代码”是一个非常主观的术语。 (3认同)
  • @user1199931 不是真的。如果您扫描代码以对其有一个大致的了解,您将看到“if”并继续,而忽略了状态发生变化的事实。也有例外(`for`,但关键字本身表明它既是循环又是管理循环状态),但一般来说,一行代码应该做一件事,而且只做一件事。如果它控制流量,则不应修改状态。 (2认同)

ela*_*lhm 5

我遇到了一个案例,该案例最近才有用,所以我认为应该发布它。

假设您要在一个条件中检查多个条件,并且如果其中任何一个条件为真,则您想生成一条错误消息。如果要在错误消息中包括导致错误的特定条件,可以执行以下操作:

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
}
Run Code Online (Sandbox Code Playgroud)

因此,如果第二个条件是评估为true的条件,则e等于“ cd”。这是由于||标准规定了短路行为(除非过载)。有关短路的更多详细信息,请参见此答案。