如何重写复杂的C++代码行(嵌套三元运算符)

inv*_*ino 24 c c# c++

我一直在查看其他人的代码以进行调试,并发现:

!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);  
Run Code Online (Sandbox Code Playgroud)

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

编辑注释:我将其从"不必要的复杂"更改为标题中的"复杂",因为这是一个意见问题.到目前为止,感谢您的所有答案.

pho*_*ger 58

如果改写如下,可以改进书面陈述......

good = m_seedsfilter==0 ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);
Run Code Online (Sandbox Code Playgroud)

......但总的来说,你应该熟悉三元声明.关于最初发布的代码或xanatos版本或我的代码,没有任何内在的恶意.三元语句不是邪恶的,它们是语言的基本特征,一旦你熟悉它们,你就会注意到这样的代码(就像我发布的那样,而不是你原来的帖子中写的)实际上更容易阅读而不是一串if-else语句.例如,在此代码中,您可以简单地按如下方式阅读此语句:"变量good等于... if m_seedsfilter==0,然后true,否则,if m_seedsfilter==1,then newClusters(Sp),否则," newSeed(Sp).

请注意,我上面的版本避免了对变量的三个单独赋值good,并清楚地表明该语句的目标是为其赋值good.此外,以这种方式编写,它清楚地表明,基本上这是一个"switch-case"结构,默认情况是newSeed(Sp).

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

good = !m_seedsfilter   ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);
Run Code Online (Sandbox Code Playgroud)

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


更新,原始帖子/答案后两年半:有趣的是@TimothyShields和我不时得到这个回报,Tim的回答似乎始终跟踪这个答案的大约50%的赞成,或多或少(截至本次更新时为43 vs 22).

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

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)");
Run Code Online (Sandbox Code Playgroud)

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)");
Run Code Online (Sandbox Code Playgroud)

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)");
Run Code Online (Sandbox Code Playgroud)

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

这些示例将单行文本打印到标准输出.虽然它们执行的操作很简单,但应该很容易将它们想象为更大序列的子集.我可以更简洁地清楚地表达该序列的子集,该序列可以在我的编辑器屏幕上显示得越多.当然,我可以轻易地将这种努力付诸实践,使其更难以理解; 目标是找到易于理解和简洁之间的" 甜蜜点 ".我认为,一旦程序员熟悉三元语句,理解使用它们的代码变得比理解不能理解的代码更容易(例如上面的23,上面的1).

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

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)");
Run Code Online (Sandbox Code Playgroud)

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


Tim*_*lds 27

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

if (m_seedsfilter == 0)
{
    good = true;
}
else if (m_seedsfilter == 1)
{
    good = newClusters(Sp);
}
else
{
    good = newSeed(Sp);
}
Run Code Online (Sandbox Code Playgroud)

链式三元运算符 - 即以下

condition1 ? A : condition2 ? B : condition3 ? C : D
Run Code Online (Sandbox Code Playgroud)

- 是让您的代码无法读取的好方法.

我将第二个@ phonetagger建议您熟悉三元运算符 - 这样您就可以在遇到嵌套运算符时消除嵌套运算符.

  • @TimothyShields三元链式运营商只有在没有正确格式化的情况下才是邪恶的. (10认同)
  • 但是如果`constexpr`怎么办?`:-P` (7认同)
  • 你是说三元运算符是邪恶的吗? (3认同)
  • @DaanTimmer没有.我说链式三元运算符是邪恶的.:) (2认同)
  • 编写`if/else if/else`链而不将语句放入适当的范围块`{}`是更邪恶的恕我直言! (2认同)
  • 如果`operator int()`总是返回`0`而``operator bool()`总是返回`true`?:-) :-) :-) (2认同)

xan*_*tos 8

这个更好?

!m_seedsfilter ? good=true 
               : m_seedsfilter==1 ? good=newClusters(Sp) 
                                  : good=newSeed(Sp);  
Run Code Online (Sandbox Code Playgroud)

我会补充一点,虽然理论上可以简化这个表达式(为什么?它非常清楚!),结果表达式在所有可能的情况下都不可能100%相等...并显示两个表达式是否相同在C++中真的相当于一个非常非常非常复杂的问题......

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

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

good = !m_seedsfilter ? true 
                      : m_seedsfilter==1 ? newClusters(Sp) 
                                         : newSeed(Sp);
Run Code Online (Sandbox Code Playgroud)

有两个可能的路径:SmallUnixDateTime(newClusters(Sp))转化为boolUnixDateTime(newSeed(Sp))首先被转换为SmallUnixDateTime,然后到bool.显然,这两个表达方式并不相同.

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

  • 在他的防守,如果你不知道的术语"三元opreator"它不是很容易的谷歌"这些是什么该死的和:在我的代码?" (2认同)
  • @ yankee2905 [但这些符号有英语名称...](https://www.google.com/search?q=question+mark+colon+c) (2认同)