无意中使用=而不是==

看起来

if (x=y) { .... } 

代替

 if (x==y) { ... } 

是许多邪恶的根源。

为什么并非所有编译器都将其标记为错误而不是可配置警告?

我有兴趣找出构造if (x=y)有用的情况。

大多数情况下,编译器非常努力地保持向后兼容。

改变他们在这件事上的行为以抛出错误将破坏现有的合法代码,甚至开始抛出警告将导致自动系统出现问题,通过自动编译并检查错误和警告来跟踪代码。

这是一个邪恶,我们几乎坚持atm,但有办法规避和减少它的危险。

例:

  void *ptr = calloc(1, sizeof(array)); if (NULL = ptr) { // some error } 

这会导致编译错误。

一个有用的结构是例如:

 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 ,以便退出循环。

简单回答:赋值操作(例如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!");

这也是一样的,但更简洁。

因为它不是非法的(无论如何在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 } 

如果你没有在括号内放置括号,大多数编译器都会产生真正的臭味,我喜欢。

关于if(i = 0)的有效用法

问题是你正在把问题颠倒过来。 “if”表示法不是像在某些其他语言中那样比较两个值。

C / C ++“if”指令等待任何将计算为布尔值或null /非null值的表达式。 该表达式可以包括两个值比较,和/或可以更复杂。

例如,您可以:

 if(i >> 3) { std::cout << "i is less than 8" << std::endl } 

这certificate,在C / C ++中,if表达式不限于==和=。 任何事都可以,只要它可以被评估为真或假(C ++),或零非零(C / C ++)。

另一个C ++有效用途:

 if(MyObject * pObject = dynamic_cast(pInterface)) { pObject->doSomething() ; } 

这些是if表达式的简单用法(请注意,这也可以在for循环声明行中使用)。 确实存在更复杂的用途。

关于C ++中if(i = 0)的高级用法[引自我自己]

在发现这个问题的副本后, 在哪种情况下(a = b)是个好主意? ,我决定用一个额外的奖励来完成这个答案,也就是说,可变注入一个范围,这在C ++中是可能的,因为if它将评估它的表达式,包括一个变量声明,而不是限制自己来比较两个操作数就像它完成用其他语言:

所以, 引用自己 :

另一种用途是使用所谓的C ++ Variable Injection。 在Java中,有这个很酷的关键字:

 synchronized(p) { // Now, the Java code is synchronized using p as a mutex } 

在C ++中,您也可以这样做。 我没有确切的代码(也没有我发现它的确切的DDJ文章),但这个简单的定义应该足以用于演示目的:

 #define synchronized(lock) \ if (auto_lock lock_##__LINE__(lock)) synchronized(p) { // Now, the C++ code is synchronized using p as a mutex } 

这是相同的方式,混合注入if和for声明,你可以声明一个原始的foreach宏(如果你想要一个工业级的foreach,使用boost)。

请参阅以下文章,了解不那么天真,更完整和更强大的实现:

这种错误真的发生了多少?

很少。 事实上,我还没有记住一个,我8年来一直很专业。 我猜它发生了,但是,在8年后,我确实产生了大量的bug。 只是这种错误并没有发生,让我在挫折中记住它们。

在C中,由于缓冲区溢出会导致更多错误,例如:

 void doSomething(char * p) { strcpy(p, "Hello World, how are you \?\n") ; } void doSomethingElse() { char buffer[16] ; doSomething(buffer) ; } 

事实上,微软被烧得太厉害了,因为他们在Visual C ++ 2008中添加了一个警告,不赞成使用strcpy!

你怎么能避免大多数错误?

对此错误的第一个“保护”是“转向”表达式:由于无法为常量赋值,因此:

 if(0 = p) // ERROR : It should have been if(0 == p). WON'T COMPILE ! 

不会编译。

但我觉得这个解决方案很糟糕,因为它试图隐藏在风格背后应该是一般的编程实践,即:任何不应该改变的变量应该是不变的。

例如,而不是:

 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") ; } 

尝试“const”尽可能多的变量将使您避免大多数拼写错误,包括那些不在“if”表达式内的错误:

 void doSomething(const char * const p) // CONST ADDED HERE { if(p == NULL) // NO TYPO POSSIBLE return ; const size_t length = strlen(p) ; // CONST ADDED HERE if(length == 0) // NO TYPO POSSIBLE printf("\"%s\" length is %i\n", p, length) ; else printf("the string is empty\n") ; } 

当然,它并不总是可能的(因为一些变量确实需要改变),但我发现我使用的大多数变量都是常量(我一直初始化它们,然后只读它们)。

结论

通常,我看到代码使用if(0 == p)表示法,但没有const-notation。

对我来说,这就像有一个垃圾桶用于可回收物品,另一个用于不可回收的,然后在最后,将它们放在同一个容器中。

所以,不要嘲笑一种简单的习惯习惯,希望它能让你的代码变得更好。 它不会。 尽可能使用语言结构,这意味着,在这种情况下,在可用时使用if(0 == p)表示法,并尽可能使用const关键字。

‘if(0 = x)’成语旁边是无用的,因为当双方都是变量(’if(x = y)’)和大多数(全部?)的时候你应该使用常量变量时​​它没有用而不是魔术数字。

另外两个原因我从不使用这个成语,恕我直言,它使代码不易读取,说实话,我发现单个’=’是非常小的邪恶的根源。 如果你彻底测试你的代码(显然我们都这样做),这种语法错误很快就会出现。

许多编译器会检测到这一点并发出警告,但前提是警告级别设置得足够高。

例如:

  〜> gcc -c -Wall foo.c
 foo.c:在函数'foo'中:
 foo.c:5:警告:建议用作真值的赋值括号

用于迭代的标准C语言:

 list_elem* curr; while ( (curr = next_item(list)) != null ) { /* ... */ } 

它取决于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 

无论如何,您应该尝试使用尽可能高的警告设置进行编译。

这真的是一个常见错误吗? 当我自己学习C时,我了解了它,作为一名教师,我偶尔会警告我的学生并告诉他们这是一个常见错误,但我很少在实际代码中看到它,即使是初学者。 当然不会比其他操作员错误更频繁,例如写“&&”而不是“||”。

因此编译器不将其标记为错误(除非它是完全有效的代码)的原因可能是它不是很多邪恶的根源。

我认为C和C ++语言设计者注意到它没有真正用于禁止它,因为

  • 无论如何,编译器可以发出警告
  • 禁止它会在语言中添加特殊情况,并删除可能的function。

允许它没有复杂性。 C ++只是说需要隐式转换为bool的表达式。 在C中,有其他答案详述的有用案例。 在C ++中,他们更进一步,并允许这一个:

 if(type * t = get_pointer()) { // .... } 

这实际上将t的范围仅限于if和它的主体。

尝试观看

 if( life_is_good() ) enjoy_yourself(); 

 if( tmp = life_is_good() ) enjoy_yourself(); 

部分原因与个人风格和习惯有关。 如果(kConst == x)或if(x == kConst),我无法读取。 我不使用左边的常量,因为从历史上看,我没有犯这个错误,而且我按照我的说法编写代码或者想要阅读它。 我将此视为个人决定,作为个人责任的一部分,成为一名自我意识,改进工程师。 例如,我开始分析我正在创建的错误的类型,并开始重新设计我的习惯,以便不制作它们 – 类似于左边的常量,只是与其他东西。

也就是说,历史上编译器警告非常糟糕,尽管多年来这个问题已经众所周知,但直到80年代后期才在生产编译器中看到它。 我还发现,处理可移植的项目有助于清理我的C,因为不同的编译器和不同的口味(即警告)和不同的细微语义差异。

在一个条件语句中,赋值运算符有很多很好的用途,并且总是看到每个人都有关于每个警告的警告。 什么是好的将是你的IDE中的一个function,让你突出显示所有使用赋值而不是相等检查的地方 – 或 – 在你写这样的东西后:

 if (x = y) { 

然后那条线闪烁了几次。 足以让你知道你做了一些不完全标准的东西,但不是那么烦人的。

我个人认为这是最有用的例子。

假设你有一个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); } 

第一个构造感觉很尴尬,第二个构造以令人不愉快的方式重复编码。

当然,除非我为此错过了一些精彩的成语……

作为条件的赋值是合法的C和C ++,并且任何不允许它的编译器都不是真正的C或C ++编译器。 我希望任何不能与C语言明确兼容的现代语言(如C ++那样)都会认为它是一个错误。

在某些情况下,这允许简洁的表达式,例如惯用的while (*dest++ = *src++); 在C中复制一个字符串,但总的来说它不是很有用,我认为它在语言设计中是一个错误。 根据我的经验,这很容易犯这个错误,并且在编译器没有发出警告时很难发现。

  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年的编程中,我没有看到使用这种结构的正当理由,但如果存在,它可能与故意混淆代码的需要有关。

当我们的一个新人遇到问题时,这是我要寻找的事情之一,同时不会在字符串上粘贴终止符,并将调试语句从一个地方复制到另一个地方而不是将’%i更改为’ %s’匹配他们正在转储的新字段。

这在我们的商店中很常见,因为我们经常在C和Oracle PL / SQL之间切换; if(k = 1)是PL / SQL中的正确语法。

你问为什么它有用,但不断质疑人们提供的例子。 它很有用,因为它简洁。

是的,所有使用它的例子都可以重写 – 作为更长的代码片段。

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样式检查,但这当然不是必需的。 有些浏览器不会validationHTML和Javascript的每个字节都会被抛出,那么为什么编译器呢?

有几种策略可以帮助发现这一点……一个是丑陋的,另一个通常是一个宏。 这取决于你如何阅读你的口语(从左到右,从右到左)。

例如:

 if ((fp = fopen("foo.txt", "r") == NULL)) 

VS:

 if (NULL == (fp = fopen(...))) 

有时可以更容易地读/写(首先)您的测试,这使得更容易发现任务与测试。 然后引入大多数讨厌这种风格的comp.lang.c人。

所以,我们引入assert ():

 #include  ... fp = fopen("foo.txt", "r"); assert(fp != NULL); ... 

当你在中间或者一组错综复杂的条件结束时,断言()是你的朋友。 在这种情况下,如果FP == NULL,则引发abort()并传送违规代码的行/文件。

所以,如果你oops:

 if (i = foo) 

绝对的

 if (i == foo) 

其次是

 assert (i > foo + 1) 

……你很快就会发现这样的错误。

希望这可以帮助 :)

简而言之,在调试时,反转参数有时会有所帮助.assert()是你的长寿朋友,可以在生产版本的编译器标志中关闭。

在我15年的发展过程中,我只有这个错字。 我不会说它是我要注意的事项列表中的首要问题。 无论如何我也避免那种结构。

另请注意,某些编译器(我使用的编译器)会对该代码发出警告。 警告可以被视为任何有价值的编译器的错误。 它们也可以被忽略。

将常数放在比较的左侧是防御性编程。 当然,你永远不会忘记那些额外的’=’,但谁知道其他人。

D编程语言将此标记为错误。 为了避免以后想要使用该值的问题,它允许声明类似于C ++允许使用for循环。

 if(int i = some_fn()) { another_fn(i); } 

编译器不会将其标记为错误,因为它是有效的C / C ++。 但你可以做的(至少使用Visual C ++)是打开警告级别,以便将其标记为警告,然后告诉编译器将警告视为错误。 无论如何,这是一个很好的做法,因此开发人员不会忽略警告。

如果你实际上意味着=而不是==那么你需要更加明确它。 例如

 if ((x = y) != 0) 

从理论上讲,你应该能够做到这一点:

 if ((x = y)) 

覆盖警告,但似乎并不总是有效。

这就是为什么写作更好的原因:

0 == CurrentItem

代替:

CurrentItem == 0

因此,如果键入=而不是==,编译器会发出警告。

正如在其他答案中所指出的,有些情况下,在条件中使用赋值提供了一个简短但可读的代码片段,可以满足您的需求。 此外,许多最新的编译器会警告您,如果他们看到他们期望条件的任务。 (如果你是开发的零警告方法的粉丝,你会看到这些。)

我开发的一种习惯让我不被这种(至少在C-ish语言中)所困扰,如果我正在比较的两个值之一是一个常数(或者不是合法的左值),我把它放了在比较器的左侧: if (5 == x) { whatever(); } 然后,如果我不小心输入if (5 = x) ,代码将无法编译。

RegEx样本

 RegEx r;

 if(((r = new RegEx(“\ w *))。IsMatch()){
    // ...在这里做点什么
 }
 else if((r = new RegEx(“\ d *”))。IsMatch()){
    // ...在这里做点什么
 }

作为价值测试

 int i = 0;
 if((i = 1)== 1){
    // 1等于分配给int值1的i
 }
其他{
    //?
 }

在实践中我不这样做,但一个好的建议是:

 if ( true == $x ) 

在省略等于的情况下,将$x赋值$x true显然会返回错误。