" C ++ Primer" (第5版)建议在第149页上,简短的代码比较长的替代方案更不容易出错。它将以下代码行作为示例:
cout << *iter++ << endl;
它声称上述行比其他行更不容易出错:
cout << *iter << endl;
++iter;
对我来说,即使*iter++
的含义很明确,这个表达式仍然需要非零精神解析,因此第二个版本更具可读性。所以,我想理解:第二个版本更容易出错?
答案 0 :(得分:21)
我也不同意作者。
通常每个代码段都会在将来发生变化。
例如这句话
cout << *iter++ << endl;
可以通过以下方式更改
cout << *iter++ << ... << SomeFunction( *iter ) << endl;
在这种情况下,代码将具有未定义的行为,因为未指定函数参数的评估顺序。
所以在我看来这段代码
cout << *iter << endl;
++iter;
不易出错。:)
在代码段中引入副作用并不会减少错误。
此外,在代码片段中,您显示迭代器的增量与输出其值之间没有逻辑关系。
cout << *iter++ << endl;
因此不清楚为什么迭代器在此语句中递增。
将此语句分成两个语句会使代码段更加清晰,因为增量似乎与代码的其他部分有关。
简洁使代码更具表现力。但这并不意味着它也必然会使代码更容易出错。:)
答案 1 :(得分:5)
问题是,当你的函数在代码中包含几行时,它们中的每一行都被视为一个&#34;步骤&#34;实现一些目标。当你阅读这样一个函数时,你会阅读每一行并思考它的作用和原因。在您的示例中,
cout << *iter << endl;
++iter;
逻辑上可以是一步:&#34;当迭代容器时,将每个元素写入cout
&#34;。这是因为当你忘记这些行中的任何一行时,整个步骤都是不正确的。当然,这个例子并不是特别好,因为编写代码并不难,其中两行是两个不同的逻辑步骤。但是,我认为作者的意思是,通过写作
cout << *iter++ << endl;
保护自己不要忘记逻辑步骤的一部分,并为读者发出信号,这是一个逻辑步骤。
答案 2 :(得分:4)
我可以考虑由于某种原因重新排序的行(也许它们被其他代码分开并且稍后被修改),或者添加了if / for / while,无论是没有大括号还是错误地放置它们: / p>
++iter;
cout << *iter << endl;
if (some_condition)
cout << *iter << endl;
++iter;
while (something_happens)
{
cout << *iter << endl;
}
++iter;
在这个小例子中,错误是非常明显的,但是如果你有更多的行,情况可能并非如此。
(是的,我知道第二和第三个例子中的缩进应该得到纠正,但遗憾的是我已经看过很多像这样的例子。)
答案 3 :(得分:3)
我不能更多地反对作者。
表达式*iter++
对iter
做了两件不同的事情 - 它取消引用它并递增它。是的,定义了顺序,但是理解单个表达式中单个变量的两个动作的结果需要比仅对单个变量只有一个效果的东西更多的智力。
增加开发人员错误的最可靠方法之一就是不必要地增加理解他们的代码实际执行操作所需的智力。
将效果分解为两个不同的陈述的优点是它更容易理解。
另一个现象是,被教导在单个语句中对单个变量包含两个效果的程序员更有可能创建对某些差变量具有更多影响的表达式。除了使代码更难以理解之外,它还增加了未定义行为的可能性(因为在单个表达式中多次修改任何变量的任何表达式都有未定义的行为)。
答案 4 :(得分:3)
可能值得与其他语言的类似结构进行比较。
在python中,为了迭代序列,我们使用了一个生成器。使用生成器基本上只能执行一个操作:调用next
来获取元素并使生成器前进。迭代,当手动完成时,通过重复调用next
来获取序列的条款,直到它引发StopIteration
以指示序列结束。
在java中,为了迭代序列,我们使用Iterator
。迭代器基本上只能执行一个操作:call next
,它获取一个元素并推进迭代器。迭代,当手动完成时,通过重复调用next
来获取序列的条款,直到hasNext
返回false
来表示序列的结束。
在C / C ++中......我们经常想要获得一个元素并在序列中前进。它已经很久了(C ++之前的AFAIK甚至存在),这个操作是*p++
。
我们甚至考虑将此分为两个步骤的唯一原因 - 一步是获取当前元素而另一步是推进到下一个术语 - 是实现的工件细节
如果一个人真正认为这两个步骤是独立的,独立的事情,那么最好将它们作为单独的表达式保留。
但是相对确定的是,不人们在想什么 - 人们正在考虑&#34;获得元素并推进迭代器&#34;。手动完成迭代是通过重复调用*iter++
来获取序列的条件来完成的,直到iter == end_iter
返回true表示序列结束。
当这样思考时,将一个概念性步骤拆分为两个语法上独立的元素(*iter
和++iter
)比将其保持为一个步骤更容易出错。
答案 5 :(得分:2)
不是一个很好的例子,因为你不应该在没有大括号的情况下使用它,但它可能会导致这样的错误:在这种情况下,添加while时会有一个无限循环。
while (*iter)
cout << *iter << endl;
++iter;
我认为这是一个糟糕的例子!更好的是一个例子,你有多个依赖步骤。然后有人将一些步骤复制到另一个函数,但是错过了一些行,这些行看起来不是语义上需要的。