我最近失去了一些时间来弄清楚我的代码中的一个错误是由错字造成的:
if(a=b)
而不是:
if(a==b)
我想知道是否有任何特殊情况你想在if
语句中为变量赋值,或者如果没有,为什么编译器不会发出警告或错误?
答案 0 :(得分:37)
if (Derived* derived = dynamic_cast<Derived*>(base)) {
// do stuff with `derived`
}
虽然这被称为反模式(“使用虚拟调度!”),但有时Derived
类型具有Base
根本不具备的功能(因此,不同的功能) ,这是切换语义差异的好方法。
答案 1 :(得分:23)
以下是有关语法的一些历史记录。
在经典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!
然后C99出现了,允许你混合定义和语句,所以很多开发人员会经常编写类似
的东西Bar* myBar = getBar();
if(myBar) {
确实感觉很尴尬。这就是为什么最新标准允许在条件内定义,以提供一种简短,优雅的方式来实现这一目标:
if(Bar* myBar = getBar()) {
此语句中没有任何危险,您明确地给变量一个类型,显然希望它被初始化。它还避免了额外的行来定义变量,这很好。但最重要的是,编译器现在可以轻松捕获这种错误:
if(Bar* myBar = getBar()) {
...
}
foo(myBar->baz); //compiler error
//or, for the C++ enthusiasts:
myBar->foo(); //compiler error
如果没有if
语句中的变量定义,则无法检测到此条件。
简短回答:你问的语法是旧C的简单和强大的产物,但它是邪恶的,所以编译器可以警告它。由于它也是表达常见问题的一种非常有用的方法,因此现在有一种非常简洁,错误的方法来实现相同的行为。它有很多好的,可能的用途。
答案 2 :(得分:11)
赋值运算符返回赋值的值。所以,我可能会在这样的情况下使用它:
if(x = getMyNumber())
然后我将x
指定为getMyNumber
返回的值,并检查它是否为零。
避免这样做,我给你举了一个例子,只是为了帮助你理解这一点。
编辑添加只是一个建议(可能会喜欢)。
要避免这样的错误 - 如果条件为if(NULL == ptr)
而不是if(ptr == NULL)
,应该写一些扩展因为当你拼错等式检查运算符==
时作为运算符=
,编译将抛出一个带有if(NULL = ptr)
的左值错误,但编译器传递if(res = NULL)
(这不是你的意思)并且仍然是运行时代码中的错误。 />
出于同样的原因,我更愿意写if(getMyNumber() ==x)
而不是if(x == getMyNumber())
关于这种代码,还应该阅读:Criticism。
答案 3 :(得分:9)
这取决于您是否要编写干净的代码。什么时候 C首先被开发,清洁代码的重要性 没有完全认可,编译器非常简单: 使用这样的嵌套赋值通常会导致更快 码。今天,我想不出任何一个优秀程序员的情况 会做的。它只会使代码的可读性降低 难以维持。
答案 4 :(得分:5)
我遇到了一个最近才有用的案例,所以我想我会发布它。
假设您要在单个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将等于&#34; cd&#34;。这是由于标准规定的||
的短路行为(除非过载)。有关短路的详细信息,请参阅this答案。
答案 5 :(得分:3)
为什么编译器不会发出警告
某些编译器将为条件表达式中的可疑分配生成警告,但您通常必须明确启用警告。
例如,在Visual C ++中,您必须启用C4706(或一般的4级警告)。我通常尽可能多地打开警告并使代码更明确,以避免误报。例如,如果我真的想这样做:
if (x = Foo()) { ... }
然后我把它写成:
if ((x = Foo()) != 0) { ... }
编译器会看到显式测试,并认为分配是故意的,因此您不会在此处收到误报警告。
这种方法的唯一缺点是在条件中声明变量时不能使用它。也就是说,你不能改写:
if (int x = Foo()) { ... }
作为
if ((int x = Foo()) != 0) { ... }
从语法上讲,这不起作用。因此,您必须禁用警告,或者对x
范围的紧密程度进行评估。
UPDATE: 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
答案 6 :(得分:2)
在 c++17 中可以使用:
if (<initialize> ; <conditional_expression>) { <body> }
类似于 for 循环迭代器初始化器。
这是一个例子:
if (Employee employee = GetEmployee(); employee.salary > 100) { ... }
答案 7 :(得分:1)
在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
,但是=
和==
自己显然是不同的。