【问题标题】:In which case if(a=b) is a good idea? [duplicate]在哪种情况下 if(a=b) 是个好主意? [复制]
【发布时间】:2021-05-21 22:57:01
【问题描述】:

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

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

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

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

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

但是是否存在应该忽略警告的情况,因为它是使用此“功能”的好方法? 我看不出任何代码清晰的原因,所以有没有它有用的情况?

【问题讨论】:

  • 如果赋值用双括号括起来,一些编译器不会发出警告,例如: if (( a = b ))
  • 请注意,此问题与以下问题重复:stackoverflow.com/questions/399792/…
  • if( a = b ){.....} 和 a = b; if(a) {....} 相同。所以这不是错误

标签: c++ c


【解决方案1】:

两个可能的原因:

  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 的类时,这也可以工作可以将其强制(隐式转换)为布尔值。换句话说,(a=b) 将评估为 truefalse 如果 a 是可以强制为布尔值的类型。

    所以下面是和上面类似的情况,除了=的左边参数是一个对象而不是一个布尔值:

    #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 
    

    注意: 在撰写本文时,上面的代码格式存在错误。我的代码(检查源代码)实际上具有正确的缩进、移位运算符和行距。 &amp;lt;'s 应该是

  2. 覆盖= 运算符

    由于 C++ 允许覆盖运算符,有时 = 会被覆盖以执行与原始类型不同的操作。在这些情况下,对对象执行 = 操作可能会返回一个布尔值(如果这就是该对象类型覆盖 = 操作符的方式)。

    因此,以下代码将对a 执行= 操作,并将b 作为参数。然后它会根据该操作的返回值有条件地执行一些代码:

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

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

【讨论】:

  • 作为对“Sign and Check”的修正,请记住该类型可以具有operator bool() 覆盖。
  • @Billy ONeal:如果可以的话,我想将其添加到我的答案中 - 这是一个很好的观点。但是,也许它应该是列表中自己的项目,而不是添加到 1 或 2?
  • 我认为第二种形式(覆盖 = 返回一个布尔值)是可怕的、邪恶的、丑陋的,会让你成为一个坏人。
  • @Steven Schlansker:为什么? “失败时返回 0,成功时返回 1”似乎是一种合理(尽管简单)的重载 = 的方法。不想反对,只是好奇你为什么这么认为。
  • 因为每个人都希望 a=b 返回值本身。想象一下尝试写 a=b=c=d;当您想将一堆变量相互分配时……然后在十小时后发现 b=c 返回 1 而不是 c!它违反了 = 运算符的一般约定。
【解决方案2】:
while ( (line = readNextLine()) != EOF) {
    processLine();
}

【讨论】:

  • 不过,情况并非如此,因为您没有直接将作业用作测试。
  • 不,确实是一样的情况:赋值结果作为if()语句的求值表达式。
  • 不是,它是一个赋值结果,用作比较表达式的左侧,用作if语句的评估表达式。客观上这不是一回事,因为编译器永远不会警告这种形式。
  • 在我的辩护中,这个问题根本不是具体的,他不想要简单的作业以外的形式。
  • 这不是同样的情况。实际的 if 条件使用 != 比较进行评估。
【解决方案3】:

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

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

假设 some_function 在错误的情况下返回错误代码,否则返回零

【讨论】:

  • 我看不出你不能把赋值放在条件句之前的原因。
  • 你可以把作业放在前面,但这不是问题。
  • @Ed:是的,这就是问题所在。这是关于将它放在那里是否是个好主意。你没有理由提出一个好主意,但你只是展示了一个语法上有效的案例。如果你有一个体面的编译器,它可以很容易地以不同的方式完成,完全没有开销。
【解决方案4】:

这是C语言基本特性的结果:

赋值操作的值就是赋值本身

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

顺便说一句,这与允许这种疯狂简洁的技巧相同:

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

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

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

【讨论】:

  • 您可能希望考虑将“C 语言的基本特性”更改为“C++ 语言的基本特性”,因为这似乎是问题的主要焦点(尽管它也被标记为 @ 987654325@,这取决于你)。
  • 它是 C 语言的一个特性,由 C++ 继承。在我看来,问题出在问题上,因为它似乎表明这是一个仅限 C++ 的功能。因此,至少应该将问题编辑为“C/C++”。
  • 根据这个推理,您可以在讨论特定语言的任何共同概念时说“B 的基本特征”或任何石器时代语言,并询问提问者有关更改所有关于 C 的语言问题或 C++ 到 B。我认为这不符合条件。 C++ 和 C,就像 C 和 B 是不同的语言。
  • @Johannes:弱论点,因为 B 是一种死语言,而 C 仍然是最常用的语言之一。然后,当某些东西同时适用于 C 和 C++ 时,通常会说它是 C/C++ 主题。只有当一个主题 ONLY 应用于 C++ 时,你才称它为 C++ 主题。
【解决方案5】:

虽然该构造是完全合法的语法,但您的意图可能确实如下所示:

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

不要遗漏“!= 0”部分。

从现在起 6 个月、1 年、5 年后查看代码的人,乍一看,只会相信代码包含一个 jr 编写的“经典错误”。程序员并将尝试“修复”它。上面的构造清楚地表明了您的意图,并将由编译器进行优化。如果你是那个人,这将特别尴尬。

您的另一个选择是使用 cmets 大量使用它。不过上面是自文档化的代码,比较好。

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

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

这是关于代码可以得到的清楚的。如果性能受到影响,则几乎为零。

【讨论】:

    【解决方案6】:
    void some( int b ) {
        int a = 0;
        if(  a = b ) {
           // or do something with a
           // knowing that is not 0
        } 
        // b remains the same 
     }
    

    【讨论】:

    • 如果 b 在这里为零怎么办?我们还知道a里面不是零吗?
    • 如果 b 为零,a 保持为 0 并且不进入 if 代码。
    • 哦,当然 :P 整数 0 被视为假总是让我和 c 混淆。
    • 只需将函数的主体替换为int a = b; if(b) {} 显然,我不明白您要表达的意思。
    【解决方案7】:

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

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

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

    就我的两分钱...

    【讨论】:

    • +1:人们普遍认为,在条件中使用任何形式的“副作用”都是一种糟糕的编码风格,因为事实证明它会增加错误率(使代码更复杂的每一件小事或可读性较差,即使只有最轻微的量,也会以这种方式做出贡献)。这就是为什么您会收到此类代码的编译器警告。
    【解决方案8】:

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

    赋值返回赋值

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

    数字和指针都有真值

    C 最初直到 1999 年标准才具有 bool 类型,因此它使用 int 来表示布尔值。向后兼容性要求 C 和 C++ 允许在 ifwhilefor 中使用非 bool 表达式。

    所以,如果a = b 有一个值并且if 对它接受的值很宽容,那么if (a = b) 可以工作。但我建议使用if ((a = b) != 0) 来阻止任何人“修复”它。

    【讨论】:

      【解决方案9】:

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

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

      【讨论】:

        【解决方案10】:

        您应该以更好的编码方式显式编写检查语句,避免 assign & check 方法。示例:

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

        【讨论】:

          【解决方案11】:
          while( (l = getline()) != EOF){
                  printf("%s\n", l);
          }
          

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

          【讨论】:

          • 永远不要使用小写 L 作为变量名。
          • 这是我一开始的想法,但我认为这不适用于这个问题。我认为在这种情况下,编译器不会显示警告(不过我可能错了)。
          • 在 C99 中,使用 for 循环可以更好地完成这样的赋值,在该循环中为循环不变量声明一个局部变量。但是,仅当您稍后不需要代码中的最后一行时才有效。
          【解决方案12】:

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

          可以通过在分配周围放置一个额外的括号来抑制警告。那种澄清了程序员的意图。我见过的与 (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。

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

          【讨论】:

          • +1 我总是自动添加额外的括号。但是如果我忘记了,鉴于我总是将警告级别设置得很高,哪些编译器会不会警告我?
          • 如果我的投票算数(在我的梦中),我会投票要求它是一个构建错误,没有括号或条件表达式中的一些其他显式语法(while/for/if/ternary操作员?:)。那么我们就不必关心警告级别了。
          【解决方案13】:

          序言

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

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

          Inadvertent use of = instead of ==

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

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

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

          对于那些对= 错字感到不安的人,有一些解决方案(请参阅下面的替代方法...)。

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

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

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

          例如,您可以:

          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++ 变量注入。在 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's)。

          关于您的错字问题

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

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

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

          这样,人们可以保护自己免受拼写错误的影响,而不需要语言限制(这会削弱语言),因为这是一个很少发生的错误(即几乎从不,据我在我的代码中记得它)

          【讨论】:

          • 我喜欢你在那个 foo() 示例中混入的方式,这是对 LSP 的公然违反。
          • +1,很好的答案。指出我喜欢的部分 - 注意 lhs 是 == 的常数是特别好的建议。
          • 仅作记录,第一部分给出的原因仅适用于 C++,不适用于 C。C99 仅允许在 for 语句中声明变量。为了避免这种常见错误的技巧:我发现左侧的常量不容易阅读。对我来说,这有点像你在宣传 10[a] 而不是 a[10] ;-) 坦率地说,我认为编译器警告已经足够好了。
          • @Noah Roberts:我同意。这就是我写“RTTI 不应该被滥用”的原因。有时,您只是没有正确的工具(访问者、访问对象)来使用更正确的 OO 方式。
          • @Jens Gustedt:当我开始这个答案时(并且浪费了很多时间试图跟踪 Dobbs 博士关于变量注入的正确文章),标签仍然是 C++,这说明我什至没有这里考虑C。还是……
          【解决方案14】:

          绝不!

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

          【讨论】:

            【解决方案15】:

            这里有一个方面没有被提及:C 不会阻止你做任何它不需要做的事情。它不会阻止你这样做,因为 C 的工作是给你足够的绳子来吊死你自己。不要以为它比你聪明。而且很擅长。

            【讨论】:

            • 它的 if(a==b) 不是你说的
            【解决方案16】:

            我最喜欢的是:

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

            【讨论】:

              【解决方案17】:

              正则表达式示例

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

              赋值测试

              诠释 i = 0; 如果((我 = 1)== 1){ // 1 等于分配给 int 值 1 的 i } 别的 { // ? }

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-05-24
                • 2012-01-08
                • 2012-07-17
                • 1970-01-01
                • 2010-12-27
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多