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

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

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

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

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

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

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

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

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

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

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

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


更新,原始post/答案后两年半:有趣的是@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)"); 

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)"); 

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

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

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

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

 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建议您熟悉三元运算符 – 这样您就可以在遇到嵌套运算符时消除嵌套运算符。

这个更好?

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

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

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

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

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

有两种可能的路径: SmallUnixDateTimenewClusters(Sp) )转换为boolUnixDateTimenewSeed(Sp) )首先转换为SmallUnixDateTime然后转换为bool 。 显然,这两个表达方式并不相同。

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

要回答您的主要问题,这是条件表达式的示例:

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

如果逻辑OR表达式的计算结果为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)的结果。

嵌套的三元语句使代码不太可读恕我直言。 我只会在它们显着简化代码的其余部分时使用它们。 引用的代码可以像这样重写:

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

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

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

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

  ?  :  

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

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