【问题标题】:How to rewrite complicated lines of C++ code (nested ternary operator)如何重写复杂的 C++ 代码行(嵌套三元运算符)
【发布时间】:2013-08-16 17:48:31
【问题描述】:

出于调试目的,我一直在查看其他人的代码并发现:

!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);  

这是什么意思?是否有一种自动化工具可以将其呈现为更易于理解的 if/else 语句?处理这种复杂的控制结构有什么技巧吗?

编辑说明:我将标题中的“不必要的复杂”改为“复杂”,因为这是一个见仁见智的问题。感谢您迄今为止的所有回答。

【问题讨论】:

  • 不需要重写。它非常清晰和简单。您需要了解的是一般的三元运算符和运算符优先级。
  • 我会说即使是中文对于说中文的人来说也是非常清晰和简单的:-)
  • 那个sn-p的代码是一团糟。其他人的建议并不令人惊讶。请参阅我的答案以获得更友好的重构。链式三元运算符通常难以阅读。
  • @TimothyShields - 链式三元运算符实际上更容易阅读,如果格式正确。但前提是你的语言很流利。在链式 if-else 构造(例如您的答案)中,读者必须不断解析重复的赋值构造。在链式三元形式中,它们解析一次赋值,然后遍历可能赋值的可能值,这样更简单。
  • @phonetagger 从根本上说,我同意你的推理。整个构造的重点是分配给good,所以应该有“一个等号”。尽管如此,我认为if-else 块更普遍平易近人。最终这归结为偏好,这可能就是为什么有四票接近的原因。 ;)

标签: c# c++ c


【解决方案1】:

如果改写如下,可以改进所写的语句......

good = m_seedsfilter==0 ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);

...但总的来说,您应该熟悉三元语句。最初发布的代码或 xanatos 的版本或我的代码都没有本质上的邪恶。三元语句并不邪恶,它们是语言的基本特征,一旦你熟悉了它们,你会注意到这样的代码(正如我发布的,而不是你原来的帖子中写的)实际上更容易比一连串的 if-else 语句更容易阅读。例如,在这段代码中,您可以简单地阅读以下语句:“变量good等于...如果m_seedsfilter==0,则true,否则,如果m_seedsfilter==1,则newClusters(Sp),否则,@ 987654328@。”

请注意,我上面的版本避免对变量good 进行三个单独的赋值,并清楚地表明该语句的目标是为good 赋值。另外,这样写,它清楚地表明这是一个“switch-case”结构,默认情况是newSeed(Sp)

应该注意的是,只要operator!()m_seedsfilter 类型没有被覆盖,我上面的重写是好的。如果是,那么您必须使用它来保留原始版本的行为...

good = !m_seedsfilter   ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);

...正如 xanatos 下面的评论所证明的那样,如果您的 newClusters()newSeed() 方法返回的类型彼此不同,并且如果这些类型是用精心设计的无意义转换运算符编写的,那么您将拥有恢复到原始代码本身(尽管希望格式更好,如 xanatos 自己的帖子),以便忠实地复制与原始帖子完全相同的行为。但在现实世界中,没有人会这样做,所以我上面的第一个版本应该没问题。


更新,在原始帖子/答案两年半之后: 有趣的是,@TimothyShields 和我不时地对此表示赞同,而 Tim 的回答似乎始终保持在这个答案的 50% 左右,或多或少(截至本次更新为 43 对 22)。

我想我会添加另一个例子来说明三元语句在明智地使用时可以增加的清晰度。下面的示例是我为调用堆栈使用分析器(一种分析编译的 C 代码的工具,但该工具本身是用 C# 编写的)编写的代码中的简短 sn-ps。所有三种变体都实现了完全相同的目标,至少就外部可见效果而言。

1.没有三元运算符:

Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
if (fcnInfo.callDepth == 0)
{
   Console.Write(" (leaf function");
}
else if (fcnInfo.callDepth == 1)
{
   Console.Write(" (calls 1 level deeper");
}
else
{
   Console.Write(" (calls " + fcnInfo.callDepth + " levels deeper");
}
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

2。使用三元运算符,分别调用 Console.Write():

Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
Console.Write((fcnInfo.callDepth == 0) ? (" (leaf function") :
              (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                                         (" (calls " + fcnInfo.callDepth + " levels deeper"));
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

3.使用三元运算符,折叠为对 Console.Write() 的一次调用:

Console.WriteLine(
   new string(' ', backtraceIndentLevel) + fcnName +
   ((fcnInfo.callDepth == 0) ? (" (leaf function") :
    (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                               (" (calls " + fcnInfo.callDepth + " levels deeper")) +
   ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

有人可能会争辩说,上面三个示例之间的区别微不足道,既然微不足道,为什么不更喜欢更简单的(第一个)变体呢?一切都是为了简洁;用“尽可能少的词”表达一个想法,以便听众/读者在我到达这个想法的结尾时仍然可以记住这个想法的开始。当我和小孩说话时,我会使用简单、简短的句子,因此需要更多的句子来表达一个想法。当我与能说流利我的语言的成年人交谈时,我会使用更长、更复杂的句子来更简洁地表达想法。

这些示例将单行文本打印到标准输出。虽然它们执行的操作很简单,但应该很容易将它们想象为更大序列的子集。我越能清楚地表达该序列的子集,该序列的更多内容就可以在我的编辑器屏幕上显示。当然,我很容易把这种努力做得太过分,使它更难理解;目标是在通俗易懂和简洁之间找到“sweet spot”。我认为,一旦程序员熟悉了三元语句,理解使用它们的代码就会比理解不使用它们的代码更容易(例如上面的 23,vs. 1 以上)。

有经验的程序员应该对使用三元语句感到舒服的最后一个原因是避免在进行方法调用时创建不必要的临时变量。作为一个例子,我提出了上述例子的第四个变体,其逻辑被浓缩为对Console.WriteLine()的一次调用;结果是 less 易于理解 less 简洁:

4.没有三元运算符,折叠为对 Console.Write() 的单个调用:

string tempStr;
if (fcnInfo.callDepth == 0)
{
   tempStr = " (leaf function";
}
else if (fcnInfo.callDepth == 1)
{
   tempStr = " (calls 1 level deeper";
}
else
{
   tempStr = " (calls " + fcnInfo.callDepth + " levels deeper";
}
Console.WriteLine(new string(' ', backtraceIndentLevel) + fcnName + tempStr +
                  ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

在争论“没有必要将逻辑压缩为对Console.WriteLine() 的单个调用”之前,请考虑这只是一个示例:想象一下调用其他一些方法,该方法需要多个参数,所有这些都需要基于其他变量的状态。您可以创建自己的临时对象并使用这些临时对象进行方法调用,或者您可以使用三元运算符并让编译器创建自己的(未命名的)临时对象。我再次认为三元运算符比没有三元运算符可以实现更简洁的和可理解的代码。但要使其易于理解,您必须放弃任何先入为主的观念,即三元运算符是邪恶的。

【讨论】:

  • 现在重要的问题是......它是否可以通过非常邪恶的 C++ 运算符重载来破解?一个标准的人是否可以证明它不是?还是像哥德巴赫猜想?
  • @xanatos - 注意。并进行了相应的编辑。实际上,您可能会注意到,我在您发表评论的同时发布了我的编辑!
  • @xanatos - 很好的例子。当然,您必须非常努力地获得这种行为,使用一对精心设计的类型和无意义的转换运算符。谁愿意盲目地将任何 MyClass2 对象转换为“假”MyClass1 对象而不考虑 MyClass2 对象的实际状态?无论如何,你已经证明自己(技术上)是正确的。
【解决方案2】:

等效的非邪恶代码是这样的:

if (m_seedsfilter == 0)
{
    good = true;
}
else if (m_seedsfilter == 1)
{
    good = newClusters(Sp);
}
else
{
    good = newSeed(Sp);
}

链式三元运算符——也就是下面的

condition1 ? A : condition2 ? B : condition3 ? C : D

- 是让您的代码不可读的好方法。

我会支持@phonetagger 的建议,即您应该熟悉三元运算符 - 这样您就可以在遇到嵌套运算符时消除它们。

【讨论】:

  • 你是说三元运算符是邪恶的吗?
  • 但是如果constexpr呢? :-P
  • @DaanTimmer 不。我是说链式三元运算符是邪恶的。 :)
  • @TimothyShields 三元链式运算符只有在格式不正确的情况下才是邪恶的。
  • 恕我直言,写if/else if/else链而不将语句放入适当的范围块{}
【解决方案3】:

这样更好吗?

!m_seedsfilter ? good=true 
               : m_seedsfilter==1 ? good=newClusters(Sp) 
                                  : good=newSeed(Sp);  

我要补充一点,虽然理论上可以简化这个表达式(为什么?这太清楚了!),但在所有可能的情况下,生成的表达式可能不会 100% 等效......并显示如果两个表达式在 C++ 中真的等价是一个非常非常非常非常复杂的问题...

我设计的退化示例(http://ideone.com/uLpe0L)(注意它不是很退化......它只是基于一个小的编程错误)是基于考虑good a bool,创建两个类UnixDateTimeSmallUnixDateTimenewClusters() 返回 SmallUnixDateTimenewSeed() 返回 UnixDateTime。它们都应该用于包含从 1970-01-01 午夜开始的秒数格式的 Unix 日期时间。 SmallUnixDateTime 使用 int,而 UnixDateTime 使用 long long。两者都可以隐式转换为bool(如果它们的内部值是!= 0,它们会返回,“经典”),但UnixDateTime 甚至可以隐式转换为SmallUnixDateTime(这是错误的,因为可能会丢失精度......这是小的编程错误)。转换失败时,将返回设置为 0SmallUnixDateTime。在这个例子的代码中,总会有一个单一的转换:在SmallUnixDateTimebool 之间或UnixDateTimebool 之间...

在这个相似但不同的例子中:

good = !m_seedsfilter ? true 
                      : m_seedsfilter==1 ? newClusters(Sp) 
                                         : newSeed(Sp);

有两种可能的路径:SmallUnixDateTime (newClusters(Sp)) 转换为 boolUnixDateTime (newSeed(Sp)) 先转换为 SmallUnixDateTime,然后再转换为 bool。显然这两个表达式不等价。

为了使其工作(或“不工作”),newSeed(Sp) 返回一个不能包含在 SmallUnixTime (std::numeric_limits<int>::max() + 1LL) 中的值。

【讨论】:

  • 查看三元运算符的文档可能会更容易cplusplus.com/articles/1AUq5Di1
  • @yankee2905 你是这里的精英。您不能指望 OP 阅读文档!没门! </sarcasm>
  • 在他的辩护中,如果你不知道“三元运算符”这个词,那么在谷歌上搜索“这些该死的东西是什么?和:在我的代码中??”并不容易
  • @yankee2905 我一直认为 StackOverflow 应该有一些搜索替换机制,能够在搜索中搜索非字母字符并将它们转换为标签......就像你搜索 ?它提出 [operator?]... 然后我们可以用 [operatos?] 标记这些问题
【解决方案4】:

为了回答您的主要问题,这是一个条件表达式的示例

条件表达式逻辑或表达式 逻辑或表达式 ? 表达式 条件表达式

如果逻辑或表达式的计算结果为true,则表达式的结果为? 后面的表达式,否则为: 后面的表达式。例如,

x = y > 0 ? 1 : 0;

如果y 大于0,将分配1 给x,否则分配'0'。

你对这个例子感到不安是对的,因为它写得不好。作者试图使用?: 运算符作为控制结构,但它并不适用。

更好的写法是


    good = !m_seedsfilter ? true : 
                            ( m_seedsfilter == 1 ? newClusters(SP) : 
                                                   newSeed(SP) );

如果m_seedsfilter 等于0,则good 将设置为true。如果m_seedsfilter 等于1,则good 将设置为newClusters(SP) 的结果。否则,good 将设置为newSeed(SP) 的结果。

【讨论】:

    【解决方案5】:

    嵌套三元语句使代码的可读性降低恕我直言。只有当它们显着简化其余代码时,我才会使用它们。引用的代码可以这样重写:

    good = !m_seedsfilter ? true : m_seedsfilter==1 ? newClusters(Sp) : newSeed(Sp); 
    

    或者像这样:

    if(!m_seedsfilter)
        good = true;
    else if(m_seedsfilter==1)
        good = newClusters(Sp);
    else
        good = newSeed(Sp);
    

    第一种选择更简洁,但对于新手来说可读性较差且可调试性较差。

    【讨论】:

    • '好 = !m_seedsfilter ?真:
      m_seedsfilter==1? newClusters(Sp) : newSeed(Sp)
    【解决方案6】:
    if ( !m_seedsfilter )
      good = true;
    else if ( m_seedsfilter == 1 )
      good = newClusters(Sp);
    else
      good = newSeed(Sp);
    

    后跟? 的表达式大致对应于if ( expression ),而: 引入了类似于else 子句的内容。请注意,这是一个表达式而不是一个语句,即

    <condition> ? <expression-1> : <expression-2>
    

    是一个表达式,如果condition为真,则其值为expression-1,否则为expression-2

    【讨论】:

    • 这个答案也没有错...可读性好(作为一般做法,我可能会使用“不必要的”大括号)
    【解决方案7】:
    !m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);
    

    会翻译成

    if (!m_seedsfilter)
    {
         good = true;
    }
    else
    {
         if (m_seedsfilter == 1)
         {
              good = newClusters(Sp);
         }
         else
         {
              good = new Seed(Sp);
         }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-07-02
      • 2012-02-02
      • 2020-10-06
      • 2021-11-16
      • 2011-09-07
      • 2011-10-29
      相关资源
      最近更新 更多