似乎
if (x=y) { .... }
而不是
if (x==y) { ... }
是许多邪恶的根源。
为什么所有编译器都不将其标记为错误而不是可配置警告?
我有兴趣找出构造if (x=y)
有用的案例。
答案 0 :(得分:28)
一个有用的构造例如:
char *pBuffer;
if (pBuffer = malloc(100))
{
//continue to work here
}
编辑:
如前所述,现在已经多次投票,我可能会补充说这不是特别好的风格,但经常看到它说它很有用。我也用new
看过这个,但它让我的胸部更加疼痛。
另一个较少引起争议的例子可能是:
while (pointer = getNextElement(context))
{
//go for it, use the pointer to the new segment of data
}
这意味着当没有下一个元素时,函数getNextElement()
返回NULL
,以便退出循环。
答案 1 :(得分:16)
大多数情况下,编译器会尽力保持向后兼容。
改变他们在这件事上的行为以抛出错误将破坏现有的合法代码,甚至开始抛出警告将导致自动系统出现问题,通过自动编译并检查错误和警告来跟踪代码。
这是一个邪恶,我们几乎坚持使用atm,但有办法规避和减少它的危险。
示例:
void *ptr = calloc(1, sizeof(array));
if (NULL = ptr) {
// some error
}
这会导致编译错误。
答案 2 :(得分:13)
简单答案:赋值操作(例如x = y)具有一个值,该值与x中新分配的值相同。您可以直接在比较中使用它,而不是
x = y; if (x) ...
你可以写
if (x = y) ...
写入(和读取)的代码较少,这有时是一件好事,但现在大多数人都同意应该以其他方式编写以提高可读性。例如:
if ((x = y) != 0) ...
这是一个现实的例子。假设你想用malloc分配一些内存,看它是否有效。它可以像这样逐步编写:
p = malloc(4711); if (p != NULL) printf("Ok!");
与NULL的比较是多余的,因此您可以像这样重写它:
p = malloc(4711); if (p) printf("Ok!");
但是由于赋值操作有一个可以使用的值,你可以将整个赋值放在if条件中:
if (p = malloc(4711)) printf("Ok!");
这也是一样,但更简洁。
答案 3 :(得分:11)
因为它不是非法的(无论如何使用C或C ++)并且有时很有用......
if ( (x = read(blah)) > 0)
{
// now you know how many bits/bytes/whatever were read
// and can use that info. Esp. if you know, say 30 bytes
// are coming but only got 10
}
如果你没有在括号内添加括号,大多数编译器会发出真正的臭味,我喜欢。
答案 4 :(得分:8)
问题在于你正在解决这个问题。 “if”表示法不是与其他语言中的两个值进行比较。
C / C ++“if”指令等待任何将计算为布尔值或null /非null值的表达式。该表达式可以包括两个值比较,和/或可以更复杂。
例如,您可以:
if(i >> 3)
{
std::cout << "i is less than 8" << std::endl
}
这证明,在C / C ++中,if表达式不限于==和=。任何事都可以做,只要它可以被评估为真或假(C ++),或零非零(C / C ++)。
另一个C ++有效用途:
if(MyObject * pObject = dynamic_cast<MyInterface *>(pInterface))
{
pObject->doSomething() ;
}
这些是if表达式的简单用法(请注意,这也可以在for循环声明行中使用)。确实存在更复杂的用途。
在 In which case if(a=b) is a good idea?发现此问题的副本后,我决定用额外的奖励完成此答案,即可变注入范围,这在C ++中是可能的,因为{{ 1}}将评估其表达式,包括变量声明,而不是限制自己比较两个操作数,就像在其他语言中完成一样:
另一种用途是使用所谓的C ++ Variable Injection。在Java中,有一个很酷的关键字:
if
在C ++中,你也可以这样做。我没有确切的代码(也没有我发现它的DDJ的确切文章),但是这个简单的定义应该足以用于演示目的:
synchronized(p)
{
// Now, the Java code is synchronized using p as a mutex
}
这是同样的方法,将注入与if和for声明混合,你可以声明一个原始的foreach宏(如果你想要一个工业强度的foreach,使用boost)。
请参阅以下文章,了解不那么天真,更完整和更强大的实现:
很少。事实上,我还没有记住一个,我8年来一直很专业。 我猜它发生了,但是在8年后,我确实产生了相当数量的错误。只是因为这种错误并没有发生,让我在挫折中记住它们。
在C中,由于缓冲区溢出会导致更多错误,例如:
#define synchronized(lock) \
if (auto_lock lock_##__LINE__(lock))
synchronized(p)
{
// Now, the C++ code is synchronized using p as a mutex
}
事实上,微软被烧得太厉害了,因为他们在Visual C ++ 2008中添加了一个警告,贬低了strcpy !!!
针对此错误的第一个“保护”是“转向”表达式:因为您无法为常量赋值,所以:
void doSomething(char * p)
{
strcpy(p, "Hello World, how are you \?\n") ;
}
void doSomethingElse()
{
char buffer[16] ;
doSomething(buffer) ;
}
无法编译。
但是我发现这个解决方案很糟糕,因为它试图隐藏在风格背后的应该是一般的编程实践,即:任何不应该改变的变量应该是不变的。
例如,而不是:
if(0 = p) // ERROR : It should have been if(0 == p). WON'T COMPILE !
尝试“const”尽可能多的变量将使您避免大多数拼写错误,包括那些不在“if”表达式中的错误:
void doSomething(char * p)
{
if(p == NULL) // POSSIBLE TYPO ERROR
return ;
size_t length = strlen(p) ;
if(length == 0) // POSSIBLE TYPO ERROR
printf("\"%s\" length is %i\n", p, length) ;
else
printf("the string is empty\n") ;
}
当然,它并不总是可能的(因为一些变量确实需要改变),但我发现我使用的大多数变量都是常量(我一直初始化它们,然后只读它们)。
通常,我会看到使用if(0 == p)表示法的代码,但没有使用const-notation。
对我来说,就像有一个垃圾桶用于可回收物品,另一个用于不可回收物品,然后最后将它们放在同一个容器中。
所以,不要嘲笑一种简单的习惯习惯,希望它能让你的代码变得更好。它不会。尽可能使用语言结构,在这种情况下,在可用时使用if(0 == p)表示法,并尽可能使用const关键字。
答案 5 :(得分:7)
'if(0 = x)'成语旁边是无用的,因为当双方都是变量('if(x = y)')和大多数(全部?)的时候你应该是没有帮助使用常量变量而不是幻数。
我从不使用这个成语的其他两个原因,恕我直言,它使代码不易读取,说实话,我发现单个'='是非常小的罪恶的根源。如果你彻底测试你的代码(显然我们都这样做),这种语法错误很快就会出现。
答案 6 :(得分:5)
许多编译器会检测到这一点并发出警告,但前提是警告级别设置得足够高。
例如:
~> gcc -c -Wall foo.c foo.c: In function ‘foo’: foo.c:5: warning: suggest parentheses around assignment used as truth value
答案 7 :(得分:5)
迭代的标准C语言:
list_elem* curr;
while ( (curr = next_item(list)) != null ) {
/* ... */
}
答案 8 :(得分:4)
它取决于language.Java将其标记为错误,因为在if括号内只能使用布尔表达式(除非两个变量都是布尔值,在这种情况下赋值也是布尔值)。
在C语言中,测试malloc返回的指针是一种非常常见的习惯用法,或者在fork之后我们处于父进程或子进程中:
if ( x = (X*) malloc( sizeof(X) ) {
// malloc worked, pointer != 0
if ( pid = fork() ) {
// parent process as pid != 0
如果您要求它,C / C ++编译器会以足够高的警告级别发出警告,但不能将其视为语言允许的错误。除非,然后再次要求编译器将警告视为错误。
每当与常量进行比较时,一些作者建议使用测试常量 == 变量,以便编译器检测用户是否忘记了第二个等号。
if ( 0 == variable ) {
// the compiler will complaint if you mistakenly
// write =, as you cannot assign to a constant
无论如何,您应该尝试使用尽可能高的警告设置进行编译。
答案 9 :(得分:4)
这真的是一个常见错误吗?当我自己学习C时,我了解了这一点,作为一名教师,我偶尔会警告我的学生并告诉他们这是一个常见错误,但我很少在实际代码中看到它,即使是初学者。当然不会比其他操作员错误更频繁,例如写“&amp;&amp;”而不是“||”。
因此,编译器没有将其标记为错误(除非它是完全有效的代码)的原因可能是不是很多邪恶的根源。
答案 10 :(得分:3)
我认为C和C ++语言设计人员注意到禁止它没有真正的用途,因为
允许它没有复杂性。 C ++只是说需要隐式转换为bool
的表达式。在C中,有其他答案详述的有用案例。在C ++中,他们更进一步,并允许这一个:
if(type * t = get_pointer()) {
// ....
}
实际上,t的范围仅限于if及其实体。
答案 11 :(得分:2)
作为条件的赋值是合法的C和C ++,并且任何不允许它的编译器都不是真正的C或C ++编译器。我希望任何不能与C语言明确兼容的现代语言(如C ++那样)都会认为它是错误的。
在某些情况下,这允许简洁的表达式,例如惯用语while (*dest++ = *src++);
来复制C中的字符串,但总体来说它不是很有用,我认为它在语言设计中是一个错误。根据我的经验,这很容易犯这个错误,并且在编译器没有发出警告时很难发现。
答案 12 :(得分:2)
我个人认为这是最有用的例子。
假设您有一个函数read()
,它返回读取的字节数,您需要在循环中使用它。使用
while((count = read(foo)) > 0) {
//Do stuff
}
而不是尝试从循环头中获取分配,这将导致像
这样的事情while(1) {
count = read(foo);
if(!(count > 0))
break;
//...
}
或
count = read(foo);
while(count > 0) {
//...
count = read(foo);
}
第一个构造感觉很尴尬,第二个构造以令人不愉快的方式重复编码。
当然,除非我为此错过了一些精彩的成语......
答案 13 :(得分:2)
在一个条件语句中,赋值运算符有很多很好的用途,并且总是看到每个人都有关于每一个的警告是很痛苦的。什么是好的将是你的IDE中的一个功能,让你突出显示所有使用赋值而不是相等检查的地方 - 或 - 在你写这样的东西之后:
if (x = y) {
然后那条线闪烁了几次。足以告诉你,你已经做了一些不是完全标准的东西,但不是那么烦人。
答案 14 :(得分:2)
部分原因与个人风格和习惯有关。我不可知读取if(kConst == x)或if(x == kConst)。我没有在左边使用常量,因为从历史上看,我没有犯这个错误,而且我按照我的说法编写代码或想要阅读它。我认为这是个人决定,作为个人责任的一部分,成为一名自我意识,改进工程师。例如,我开始分析我正在创建的bug的类型,并开始重新设计我的习惯,以便不制作它们 - 类似于左边的常量,只是与其他东西。
也就是说,历史上编译器警告非常糟糕,尽管多年来这个问题已经众所周知,但直到80年代后期才在生产编译器中看到它。我还发现,处理可移植的项目有助于清理我的C,包括不同的编译器和不同的口味(即警告)和不同的细微语义差异。
答案 15 :(得分:2)
if ((k==1) || (k==2)) is a conditional
if ((k=1) || (k=2) ) is BOTH a conditional AND an assignment statement
与大多数语言一样,C按运算符优先顺序在最内层到最外层工作。
首先,它尝试将k设置为1,然后成功。
Result: k = 1 and Boolean = 'true'
下一步:它将k设置为2,然后成功。
Result: k = 2 and Boolean = 'true'
下一步:它评估(true || true)
Result: k still = 2, and Boolean = true
最后,它解决了条件:If(true)
Result: k = 2 and the program takes the first branch.
在将近30年的编程中,我没有看到使用此构造的正当理由,但如果存在,则可能与故意混淆代码的需要有关。
当我们的一个新人遇到问题时,这是我要寻找的事情之一,同时不会在字符串上粘贴终结符,并将调试语句从一个地方复制到另一个地方而不是更改'%我要'%s'来匹配他们正在倾销的新领域。
这在我们的商店中很常见,因为我们经常在C和Oracle PL / SQL之间切换; if(k = 1)是PL / SQL中的正确语法。
答案 16 :(得分:2)
尝试观看
if( life_is_good() )
enjoy_yourself();
作为
if( tmp = life_is_good() )
enjoy_yourself();
答案 17 :(得分:1)
在我15年的发展过程中,我只有这个错字。我不会说这是我要注意的事项列表中的首要问题。无论如何我也避免使用那个构造。
另请注意,某些编译器(我使用的编译器)会对该代码发出警告。警告可以被视为任何编译器的错误。它们也可以被忽略。
答案 18 :(得分:1)
将常数放在比较的左侧是防御性编程。当然,你永远不会犯这样一个愚蠢的错误,忘记额外的'=',但是谁知道其他人。
答案 19 :(得分:1)
D编程语言将此标记为错误。为了避免以后想要使用该值的问题,它允许类似C ++的声明类似于for
循环。
if(int i = some_fn())
{
another_fn(i);
}
答案 20 :(得分:1)
编译器不会将其标记为错误,因为它是有效的C / C ++。但你可以做的(至少使用Visual C ++)是打开警告级别,以便将其标记为警告,然后告诉编译器将警告视为错误。无论如何,这是一个很好的做法,因此开发人员不会忽略警告。
如果你实际上意味着=而不是==那么你需要更加明确它。 e.g。
if ((x = y) != 0)
理论上,你应该能够做到这一点:
if ((x = y))
覆盖警告,但似乎并不总是有效。
答案 21 :(得分:1)
C / C ++中的“低级”循环结构非常常见,例如副本:
void my_strcpy(char *dst, const char *src)
{
while((*dst++ = *src++) != '\0') { // Note the use of extra parentheses, and the explicit compare.
/* DO NOTHING */
}
}
当然,for循环的赋值很常见:
int i;
for(i = 0; i < 42; ++i) {
printf("%d\n", i);
}
我确信在if
语句的 之外阅读作业更容易:
char *newstring = malloc(strlen(src) * sizeof(char));
if(newstring == NULL) {
fprintf(stderr, "Out of memory, d00d! Bailing!\n");
exit(2);
}
// Versus:
if((newstring = malloc(strlen(src) * sizeof(char))) == NULL) // ew...
确保分配明显,thuogh(与前两个示例一样)。不要隐藏它。
至于意外使用......这种情况不会发生在我身上。一个常见的保护措施是将您的变量(左值)放在比较的右侧,但这对以下内容不起作用:
if(*src == *dst)
因为==
的两个oprands都是左值!
至于编译器......谁能怪他们?编写编译器很困难,你应该为编译器编写完美的程序(还记得GIGO吗?)。有些编译器(肯定是最知名的)提供了内置的lint
样式检查,但这当然不是必需的。有些浏览器不会验证HTML和Javascript的每个字节,所以编译器为什么会这样?
答案 22 :(得分:1)
有几种策略可以帮助发现这一点......一个是丑陋的,另一个通常是一个宏。这实际上取决于你如何阅读你的口语(从左到右,从右到左)。
例如:
if ((fp = fopen("foo.txt", "r") == NULL))
Vs的:
if (NULL == (fp = fopen(...)))
有时可以更容易地读/写(首先)您的测试,这使得更容易发现任务与测试。然后引入大多数讨厌这种风格的comp.lang.c人。
所以,我们引入assert():
#include <assert.h>
...
fp = fopen("foo.txt", "r");
assert(fp != NULL);
...
当你处于一个错综复杂的条件中间或者结束时,断言()是你的朋友。在这种情况下,如果FP == NULL,则引发abort()并传送违规代码的行/文件。
所以如果你oops:
if (i = foo)
insted of
if (i == foo)
接着是
assert (i > foo + 1)
......你很快就会发现这样的错误。
希望这会有所帮助:)
简而言之,在调试时,反转参数有时会有所帮助.assert()是你的长寿朋友,可以在生产版本的编译器标志中关闭。
答案 23 :(得分:1)
你问为什么它有用,但不断质疑人们提供的例子。它很有用,因为它很简洁。
是的,所有使用它的例子都可以重写 - 作为更长的代码片段。
答案 24 :(得分:0)
RegEx样本
RegEx r; if(((r = new RegEx("\w*)).IsMatch()) { // ... do something here } else if((r = new RegEx("\d*")).IsMatch()) { // ... do something here }
指定一个值测试
int i = 0; if((i = 1) == 1) { // 1 is equal to i that was assigned to a int value 1 } else { // ? }
答案 25 :(得分:0)
正如其他答案中所指出的,有些情况下,在条件中使用赋值可以提供简短但可读的代码,可以满足您的需求。此外,许多最新的编译器会警告您,如果他们看到他们期望条件的任务。 (如果你是开发的零警告方法的粉丝,你会看到这些。)
我开发的一种习惯让我不被这种(至少在C-ish语言中)所困扰,如果我正在比较的两个值中的一个是常数(或者不是合法的左值),我把它放在比较器的左侧:if (5 == x) { whatever(); }
然后,如果我不小心输入if (5 = x)
,代码将无法编译。
答案 26 :(得分:0)
在实践中我不这样做,但是要做一个好的建议:
if ( true == $x )
如果您遗漏了等号,将$x
分配给true
显然会返回错误。
答案 27 :(得分:0)
这就是为什么写作更好:
0 == CurrentItem
而不是:
CurrentItem == 0
如果您键入=而不是==。
,编译器会发出警告