11

看起来

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

代替

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

是万恶之源。

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

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

4

28 回答 28

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

于 2008-12-30T08:45:53.763 回答
15

大多数时候,编译器都非常努力地保持向后兼容。

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

这是一个我们几乎被 atm 困住的邪恶,但有一些方法可以规避和减少它的危险。

例子:

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

这会导致编译错误。

于 2008-12-30T08:57:43.503 回答
12

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

这做同样的事情,但更简洁。

于 2008-12-30T09:16:01.173 回答
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
}

如果您无论如何都不在赋值周围加上括号,大多数编译器都会发出真正的恶臭,我喜欢这一点。

于 2008-12-30T08:46:08.240 回答
9

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

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

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

例如,您可以拥有:

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 循环声明行中使用)。确实存在更复杂的用途。

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

在发现此问题的重复项后在哪种情况下 if(a=b) 是个好主意?,我决定用一个额外的好处来完成这个答案,即变量注入一个范围,这在 C++ 中是可能的,因为if它将评估它的表达式,包括一个变量声明,而不是像在其他语言:

所以,引用我自己的话

另一种用途是使用所谓的 C++ 变量注入。在 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's)。

请参阅以下文章,了解更简单、更完整和更健壮的实现:

真正发生了多少此类错误?

很少。事实上,我还没有记住一个,我是专业的 8 年。我猜它确实发生了,但是在 8 年内,我确实产生了大量的错误。只是这种错误的发生还不足以让我沮丧地记住它们。

在 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 表示法。

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

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

于 2008-12-30T15:58:26.807 回答
6

'if(0 = x)' 习语几乎没用,因为当双方都是变量('if(x = y)')并且大多数(全部?)你应该使用常量变量时​​它没有帮助而不是幻数。

我从不使用这个成语的另外两个原因,恕我直言,它使代码的可读性降低,老实说,我发现单个 '=' 是很少邪恶的根源。如果您彻底测试您的代码(显然我们都这样做),这种语法错误会很快出现。

于 2008-12-30T09:23:54.247 回答
5

用于迭代的标准 C 习惯用法:

list_elem* curr;
while ( (curr = next_item(list)) != null ) {
  /* ... */
}
于 2008-12-30T09:23:33.687 回答
4

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

例如:

~> gcc -c -Wall foo.c
foo.c:在函数“foo”中:
foo.c:5:警告:建议在赋值周围使用括号作为真值
于 2008-12-30T08:48:02.697 回答
4

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

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

于 2008-12-30T09:04:20.950 回答
3

这取决于语言。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

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

于 2008-12-30T08:58:54.640 回答
3

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

  • 编译器可以根据需要发出警告
  • 禁止它会在语言中添加特殊情况,并会删除可能的功能。

允许它不涉及复杂性。C++ 只是说需要一个可隐式转换为的表达式bool。在 C 中,其他答案详细说明了有用的案例。在 C++ 中,他们更进了一步,还允许这样做:

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

这实际上将 t 的范围限制为 if 及其主体。

于 2008-12-30T14:49:52.327 回答
2

尝试查看

if( life_is_good() )
    enjoy_yourself();

作为

if( tmp = life_is_good() )
    enjoy_yourself();
于 2008-12-30T10:44:09.047 回答
2

部分原因与个人风格和习惯有关。我不知道是否阅读 if (kConst == x) 或 if (x == kConst)。我不使用左侧的常量,因为从历史上看,我不会犯那个错误,我会按照我所说的或想阅读的方式编写代码。我认为这是一个个人决定,是作为一个有自我意识、不断改进的工程师的个人责任的一部分。例如,我开始分析我正在创建的错误类型,并开始重新设计我的习惯以避免它们 - 类似于左侧的常量,只是与其他事情。

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

于 2008-12-30T15:31:52.447 回答
2

在条件语句中赋值运算符有很多很好的用途,而且总是看到关于每个运算符的警告会让人头疼。如果你的 IDE 中有一个函数可以让你突出显示所有使用赋值而不是相等性检查的地方 - 或者 - 在你写了这样的东西之后,那就更好了:

if (x = y) {

然后那条线会闪烁​​几次。足以让你知道你做了一些不完全标准的事情,但也没有那么烦人。

于 2008-12-30T15:32:46.453 回答
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);
}

第一个结构感觉很尴尬,第二个结构以一种不愉快的方式重复代码。

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

于 2008-12-30T15:45:58.050 回答
2

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

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

于 2008-12-30T16:03:27.903 回答
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

最后,它然后解决条件:如果(真)

  Result: k = 2 and the program takes the first branch.

在近 30 年的编程生涯中,我没有看到使用这种结构的正当理由,尽管如果存在这种结构,它可能与故意混淆代码的需要有关。

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

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

于 2009-07-09T18:44:39.673 回答
1

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

是的,所有使用它的示例都可以重写——作为更长的代码。

于 2008-12-30T09:16:47.850 回答
1

有几种策略可以帮助发现这一点.. 一种很丑陋,另一种通常是宏。这实际上取决于您如何阅读口语(从左到右,从右到左)。

例如:

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

对比:

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

有时,读/写(首先)你的测试内容会更容易,这使得更容易发现作业与测试。然后引入大多数对这种风格充满热情的 comp.lang.c 人。

所以,我们引入assert ():

#include <assert.h>

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

当您处于一组复杂的条件句的中间或结尾时,assert() 是您的朋友。在这种情况下,如果 FP == NULL,则会引发 abort() 并传达违规代码的行/文件。

所以,如果你哎呀:

if (i = foo)

安装的

if (i == foo)

其次是

assert (i > foo + 1)

...您会很快发现此类错误。

希望这可以帮助 :)

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

于 2008-12-30T09:17:34.587 回答
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...

确保分配是显而易见的,虽然(与前两个示例一样)。不要隐藏它。

至于意外使用......这在我身上并没有发生太多。一个常见的保护措施是将变量(左值)放在比较的右侧,但这不适用于以下情况:

if(*src == *dst)

因为两个操作数都是左值==

至于编译器……谁能责怪他们?编写编译器很困难,无论如何你都应该为编译器编写完美的程序(还记得 GIGO 吗?)。一些编译器(肯定是最著名的)提供内置的lint-style 检查,但这当然不是必需的。一些浏览器不会验证它抛出的 HTML 和 Javascript 的每个字节,那么编译器为什么要验证呢?

于 2008-12-30T09:32:49.690 回答
1

在我 15 年的开发中,我只遇到过一次这个错字。我不会说它在我需要注意的事项清单的首位。无论如何,我也避免使用这种结构。

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

于 2008-12-30T15:06:05.493 回答
1

将常数放在比较的左侧是防御性编程。当然,您永远不会犯忘记那个额外的“=”的愚蠢错误,但谁知道另一个人。

于 2008-12-30T15:13:41.483 回答
1

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

if(int i = some_fn())
{
   another_fn(i);
}
于 2008-12-30T15:18:58.603 回答
1

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

如果您实际上是指 = 而不是 == 那么您需要更明确地说明它。例如

if ((x = y) != 0)

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

if ((x = y))

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

于 2008-12-30T16:14:55.290 回答
0

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

if ( true == $x )

如果你遗漏了一个等号,分配$xtrue显然会返回一个错误。

于 2008-12-30T10:55:36.747 回答
0

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

我养成的一个让我不会被这个问题困扰的习惯(至少在 C-ish 语言中)是,如果我要比较的两个值之一是常量(或者不是合法的左值),我把它在比较器的左侧:if (5 == x) { whatever(); } 然后,如果我不小心键入if (5 = x),代码将无法编译。

于 2008-12-30T15:24:32.850 回答
0

正则表达式示例

正则表达式 r;

if(((r = new RegEx("\w*)).IsMatch()) {
   // ... 在这里做点什么
}
否则 if((r = new RegEx("\d*")).IsMatch()) {
   // ... 在这里做点什么
}

分配价值测试

诠释 i = 0;
如果((我 = 1)== 1){
   // 1 等于分配给 int 值 1 的 i
}
别的 {
   // ?
}
于 2010-06-26T10:49:49.177 回答
-1

这就是为什么最好写:

0 == 当前项

代替:

当前项目 == 0

这样编译器会在您键入 = 而不是 == 时发出警告。

于 2008-12-30T09:00:14.437 回答