如何重写复杂的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的版本,还是我的版本,都没有本质上的邪恶。三元语句并不坏,它们是语言的一个基本特性,一旦你熟悉了它们,你就会注意到像这样的代码(正如我所发布的,而不是你原来的帖子中所写的)实际上比一连串的if-else语句更容易阅读。例如,在这段代码中,您可以简单地阅读如下语句:"Variablegoodequals…如果m_seedsfilter==0,则true,否则,如果m_seedsfilter==1,则newClusters(Sp),否则,newSeed(Sp)."

请注意,我上面的版本避免了对变量good的三次单独赋值,并明确了语句的目标是为good赋值。同样,这样写,它清楚地表明本质上这是一个"切换情况"。构造,默认情况为newSeed(Sp)

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

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

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


更新,原帖子/答案两年半后:有趣的是,@TimothyShields和我时不时地得到点赞,而Tim的答案似乎一直保持在这个答案的50%左右,或多或少(43比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)");

有人可能会说,上面三个例子之间的区别是微不足道的,既然是微不足道的,为什么不选择更简单的(第一个)变体呢?这一切都是关于简洁;用"尽可能少的词"表达一个想法;这样当我讲到结尾的时候,听者/读者还能记住开头的意思。当我和小孩子说话时,我用简单、简短的句子,结果要用更多的句子来表达一个想法。当我用流利的语言与成年人交谈时,我会用更长的、更复杂的句子来更简洁地表达想法。

这些示例将一行文本打印到标准输出。虽然它们执行的操作很简单,但应该很容易将它们想象成一个更大序列的子集。我越能简洁明了地表达该序列的子集,编辑器的屏幕上就能容纳更多的序列。当然,我很容易把这种努力做得太过,使它更难以理解;目标是找到"甜蜜点"。在可理解和简明之间。我认为,一旦程序员熟悉了三元语句,理解使用它们的代码就会比理解不使用它们的代码(例如2)容易得多。和31上图).

有经验的程序员应该放心使用三元语句的最后一个原因是在进行方法调用时避免创建不必要的临时变量。作为一个例子,我给出了上述示例的第四种变体,逻辑浓缩为对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)(注意它不是很退化…它只是基于一个小的编程错误)是基于考虑goodbool,创建两个类UnixDateTimeSmallUnixDateTime,其中newClusters()返回SmallUnixDateTime,newSeed()返回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);

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

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

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

条件表达式:logical-OR-expressionlogical-OR-expression?<表达式/em>:condition -expression

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

x = y > 0 ? 1 : 0;
如果y

大于0,将x赋值为1,否则赋值为'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子句的内容。注意,这是一个表达式而不是一个语句,例如

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

是一个表达式,如果condition为真,则其值为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);
}
}

最新更新