在哪种情况下,(a = b)是个好主意?

时间:2010-06-26 00:53:02

标签: c++ c

  

可能重复:
  Inadvertent use of = instead of ==

C ++编译器通过您编写的警告告诉您

if( a = b ) { //...

并且这可能是一个错误,你当然想写

if( a == b ) { //...

但是有一种情况应该忽略警告,因为这是使用这个“功能”的好方法吗? 我没有看到任何代码清晰度可能的原因,那么它是否有用?

17 个答案:

答案 0 :(得分:18)

两个可能的原因:

  1. 分配&检查

    =运算符(未覆盖时)通常会返回它指定的值。这是为了允许a=b=c=3等语句。在您的问题的上下文中,它还允许您执行以下操作:

    bool global;//a global variable
    
    //a function
    int foo(bool x){
    
       //assign the value of x to global
       //if x is equal to true, return 4
       if (global=x)
           return 4;
    
       //otherwise return 3
       return 3;
    }
    

    ......相当于但比......更短:

    bool global;//a global variable
    
    //a function
    int foo(bool x){
    
       //assign the value of x to global
       global=x;
    
       //if x is equal to true, return 4
       if (global==true)
           return 4;
    
       //otherwise return 3
       return 3;
    }
    

    此外,应该注意(正如Billy ONeal在下面的评论中所述),当=运算符的左侧参数实际上是具有conversion operator的类时,这也可以起作用为可以强制(隐式转换)为bool的类型指定。换句话说,如果某个类型可以强制转换为布尔值,则(a=b)将转换为truefalse

    所以以下是与上面类似的情况,除了=的左手参数是一个对象而不是一个bool:

    #include <iostream>
    using namespace std;
    
    class Foo {
    public:
        operator bool (){ return true; }
        Foo(){}
    };
    
    int main(){
        Foo a;
        Foo b;
    
        if (a=b)
            cout<<"true";
        else
            cout<<"false";
    }
    
    //output: true 
    

    注意:在撰写本文时,上面的代码格式会被窃听。我的代码(检查源代码)实际上具有适当的缩进,移位运算符和行间距。 &lt;应该是&lt;'s s,并且每行之间不应该有间隙。

  2. 重写=运算符

    由于C ++允许覆盖运算符,因此有时候=将覆盖除了原始类型之外的其他内容。在这些情况下,对对象执行=操作可能会返回一个布尔值(如果为该对象类型覆盖=运算符的话)。

    因此,以下代码将以=a作为参数执行b操作。然后它会有条件地执行一些代码,具体取决于该操作的返回值:

    if (a=b){
       //execute some code
    }
    

    在这里,a必须是一个对象,b的类型正确,如=运算符覆盖a'对象所定义的那样s型。要了解有关运算符覆盖的更多信息,请参阅此维基百科文章,其中包含C ++示例:Wikipedia article on operator overriding

答案 1 :(得分:12)

while ( (line = readNextLine()) != EOF) {
    processLine();
}

答案 2 :(得分:8)

您可以用来测试函数是否返回任何错误

if (error_no = some_function(...)) {
    //handle error
}

假设some_function在出现错误时返回错误代码,否则返回零

答案 3 :(得分:6)

这是C语言基本功能的结果:

  

赋值操作的值是指定值本身

您可以将“返回值”用作if()语句的条件这一事实是偶然的。

顺便说一下,这就是允许这种疯狂简洁的伎俩:

void strcpy(char *s, char *t)
{
    while( *s++ = *t++ );
}

当然,当达到t中的nullchar时,while会退出,但同时会将其复制到目标s字符串。

这是否是一个好主意,通常不是,因为它会降低代码的可读性并且容易出错。

答案 4 :(得分:3)

虽然构造是完全合法的语法,你的意图可能真的如下所示:

if( (a = b) != 0 ) {
   ...
}

不要将“!= 0”部分留下。

乍一看6个月,1年,5年后看代码的人,只是相信代码中包含一个由jr写的“经典bug”。程序员并将尝试“修复”它。 CLEARLY上面的结构表明了你的意图,并将由编译器进行优化。如果你是那个人,这将特别令人尴尬。

您的另一个选择是重复评论。但以上是自我记录的代码,这是更好的。

最后,我的偏好是这样做:

a = b;
if( a != 0 ) {
   ...
}

这很清楚,因为代码可以获得。如果性能受到影响,则几乎为零。

答案 5 :(得分:2)

void some( int b ) {
    int a = 0;
    if(  a = b ) {
       // or do something with a
       // knowing that is not 0
    } 
    // b remains the same 
 }

答案 6 :(得分:2)

直接回答这个问题,作为个人意见,我真的不认为这是一个好主意。

我的意思是,我知道使用这种语法可以避免在代码中添加额外的行,但我认为它会从代码中消除一些可读性。

这种语法对于像@Steven Schlansker建议的那样非常有用,但直接使用它作为一个条件不是一个好主意。

只是我的两分钱......

答案 7 :(得分:2)

您应该以更好的编码方式明确地编写检查语句,避免 assign&amp;检查方法。例如:

if ((fp = fopen("filename.txt", "wt")) != NULL) {
    // do something with fp
}

答案 8 :(得分:2)

一个有用的常见例子可能是:

do {
 ...
} while (current = current->next);

答案 9 :(得分:2)

这实际上不是C的故意特征,而是其他两个特征的结果:

作业返回指定值

这对于执行a = b = 0等多个分配或while ((n = getchar()) != EOF)等循环非常有用。

数字和指针具有真值

C在1999年标准之前最初没有bool类型,所以它使用int来表示布尔值。向后兼容性要求C和C ++允许boolifwhile中的非for表达式。

因此,如果a = b有值且if对它接受的值是否宽松,则if (a = b)有效。但我建议使用if ((a = b) != 0)来阻止任何人“修理”它。

答案 10 :(得分:1)

while( (l = getline()) != EOF){
        printf("%s\n", l);
}

这当然是最简单的例子,很多时候这很有用。要记住的主要事情是(a = true)返回true,就像(a = false)返回false一样。

答案 11 :(得分:1)

序言

请注意,这个答案是关于C ++的(我在添加标签“C”之前开始写这个答案)。

在阅读Jens Gustedt的评论之后,我意识到这不是我第一次写这样的答案。说实话,这个问题与另一个问题重复,我给出了以下答案:

Inadvertent use of = instead of ==

所以,我会在这里无耻地引用自己来添加重要信息: if不是关于比较。这是关于评估。

这种差异非常重要,因为它意味着任何东西都可以在if的括号内,只要它可以被计算为布尔值。 这是件好事。

现在,通过禁止=限制语言,其中所有其他运算符都被授权,对于该语言来说是一个危险的例外,这个例子的使用远非确定,其缺点确实很多。 / p>

对于那些对=错字不感兴趣的人,有解决方案(参见下面的替代)。

About the valid uses of if(i = 0) [Quoted from myself]

问题在于你正在解决这个问题。 “if”表示法不是与其他语言中的两个值进行比较。

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

例如,您可以:

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

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

关于有效用途

回到未引用的答案。

以下表示法:

if(MyObject * p = findMyObject())
{
   // uses p
}

允许用户声明然后在if中使用p。这是一个语法糖...但有趣的一个。例如,想象一下类似XML DOM的对象的情况,其类型在运行时之前是未知的,并且您需要使用RTTI:

void foo(Node * p_p)
{
    if(BodyNode * p = dynamic_cast<BodyNode *>(p_p))
    {
        // this is a <body> node
    }
    else if(SpanNode * p = dynamic_cast<SpanNode *>(p_p))
    {
        // this is a <span> node
    }
    else if(DivNode * p = dynamic_cast<DivNode *>(p_p))
    {
        // this is a <div> node
    }
    // etc.
}
当然,RTTI不应该被滥用,但这只是这种语法糖的一个例子。

另一种用途是使用所谓的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
}

(请注意,此宏非常原始,不应在生产代码中使用。真正的宏使用iffor。请参阅来源以下是更正确的实施方案)。

这是同样的方式,将注入与iffor声明混合,你可以声明一个原始的foreach宏(如果你想要一个工业强度的foreach,使用boost)。

关于您的拼写错误

您的问题是一个错字,有多种方法可以限制代码中的频率。最重要的是确保左侧操作数是常量。

例如,由于多种原因,此代码无法编译:

if( NULL = b ) // won't compile because it is illegal
               // to assign a value to r-values.

甚至更好:

const T a ;

// etc.

if( a = b ) // Won't compile because it is illegal
            // to modify a constant object

这就是为什么在我的代码中,const是您可以找到的最常用的关键字之一。除非我真的想要修改变量,否则它被声明为const,因此,编译器可以保护我免受大多数错误的影响,包括促使您编写此问题的拼写错误。

但是有一种情况应该忽略警告,因为这是使用这个“功能”的好方法吗?我没有看到任何代码清晰度可能的原因,那么它是否有用?

结论

如上例所示,您在问题中使用的功能有多种有效用途。

由于我使用了此功能启用的代码注入,因此我自己的代码更清晰,更清晰:

void foo()
{
    // some code

    LOCK(mutex)
    {
       // some code protected by a mutex
    }

    FOREACH(char c, MyVectorOfChar)
    {
       // using c
    }
}

...这让我难以接受这个错字的代价是一个可以忽略不计的代价(而且我记不起上次写这种类型而没有被编译器抓住)。

有趣的消息来源

我终于找到了关于变量注射的文章。我们走了!!!

替代

如果有人担心会成为= / ==错字的受害者,那么使用宏可能会有所帮助:

#define EQUALS ==
#define ARE_EQUALS(lhs,rhs) (lhs == rhs)

int main(int argc, char* argv[])
{
   int a = 25 ;
   double b = 25 ;

   if(a EQUALS b)
      std::cout << "equals" << std::endl ;
   else
      std::cout << "NOT equals" << std::endl ;

   if(ARE_EQUALS(a, b))
      std::cout << "equals" << std::endl ;
   else
      std::cout << "NOT equals" << std::endl ;

   return 0 ;
}

通过这种方式,人们可以保护自己免受拼写错误的影响,而不需要语言限制(这会使语言瘫痪),因为很少发生的错误(即几乎从未在我的代码中记住它)

答案 12 :(得分:1)

  

但是有警告的情况   应该被忽略,因为它是好的   如何使用这个“功能”?我没有看到   任何代码清晰度原因都是可能的   有用的情况吗?

可以通过在作业周围添加额外的括号来抑制警告。这样澄清了程序员的意图。我见过的与(a = b)案例直接匹配的常见情况类似于:

if ( (a = expression_with_zero_for_failure) )
{
    // do something with 'a' to avoid having to reevaluate
    // 'expression_with_zero_for_failure' (might be a function call, e.g.)
}
else if ( (a = expression2_with_zero_for_failure) )
{
    // do something with 'a' to avoid having to reevaluate
    // 'expression2_with_zero_for_failure'
}
// etc.

至于编写这种代码是否足够有用,可以证明使用C ++时初学者(有时甚至是最糟糕时刻的专业人士)遇到的常见错误,很难说。这是继承自C和Stroustrup的遗产,而其他有助于设计C ++的遗产可能已经变得完全不同,更安全,因为他们没有尽可能地使C ++向后兼容C语言。

我个人认为这不值得。我在一个团队工作,之前我曾多次遇到过这个bug。我本来会赞成不允许它(至少需要括号或其他一些明确的语法,否则它被认为是构建错误)以换取减轻遇到这些错误的负担。

答案 13 :(得分:0)

<强>从不!

引用的例外情况不会产生编译器警告。在编译器生成警告的情况下,这绝不是一个好主意。

答案 14 :(得分:0)

有一个方面尚未提及:C不会阻止你做任何不必要的事情。它并不妨碍你这样做,因为C的工作就是给你足够的绳索来吊死自己。不要以为它比你聪明。它很擅长。

答案 15 :(得分: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 {
   // ?
}

答案 16 :(得分:0)

我最喜欢的是:

if (CComQIPtr<DerivedClassA> a = BaseClassPtr)
{
...
}
else if (CComQIPtr<DerivedClassB> b = BaseClassPtr)
{
...
}