我正在读我的C++讲师的一些讲稿,他写了如下内容:
Use Indentation // OK
Never rely on operator precedence - Always use parentheses // OK
Always use a { } block - even for a single line // not OK, why ???
Const object on left side of comparison // OK
Use unsigned for variables that are >= 0 // nice trick
Set Pointer to NULL after deletion - Double delete protection // not bad
第三种方法对我来说并不清楚:我放一行进去会得到什么一个{ ... }?
例如,以这个奇怪的代码为例:
1 2 3 4 5 6 7 8
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0)
{
j++;
}
} |
替换为:
1 2 3 4
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++; |
使用第1版有什么好处?
- 可读性和可维护性。现在还不清楚语句块"j++"属于什么,在它之后添加代码也不会与if语句关联。
- 我总是被告知要用花括号做这些线有几个原因。它使代码更清晰易读。另外,六个月后,其他人可能需要编辑您的代码,因此清晰性很重要,使用大括号时,发生错误的可能性较小。在技术上没有什么比这更正确的了,这只是一个良好的实践问题。请记住,一个项目可能有成千上万行的代码,可以让一个新的家伙通过!
- 我这样做是因为代码发生了变化,当为了添加一行代码而不得不添加大括号时,我会很生气。"不过,"永远"是一个很强的词。
- 我不认为5. Use unsigned for variables that are >= 0是一个很好的技巧。如果你减一个无符号int==0,你会得到一个下溢。这很容易发生。
- 我不同意6的说法,因为它会隐藏一个双重删除并可能隐藏逻辑错误。
- 我不会为4而烦恼。读它生成的代码很奇怪,它只捕获一小类错误,任何编译器都应该对此发出警告。
- #5可能很棘手-考虑这个循环:for (unsigned i = 100; i >= 0; --i)。
- 顺便说一句,(i % 2 == 0)与(2)相矛盾。您依赖于运算符优先级,其含义当然是((i % 2) == 0),而不是(i % (2 == 0))。我将规则2归类为"一种有效的情感,但‘总是’是错误的"。
- @阿奇:在这个循环中,i不是"一个大于等于0"的变量。它大于等于0,除非期望为负以终止循环。所以它不应该是无符号的,规则5也不说它应该是无符号的:—)
- @Federico基本上是为了避免双重删除。这种方法有其优点和缺点。这篇文章应该给你更多的细节:删除指针后,将其空值是一个好的实践吗?
- @森林标准树:"现在还不清楚‘J++’这个语句块属于什么……"请告诉Guido van Rossum。
- "从不依赖运算符优先级"意味着,至少表达式i % 2 == 0需要括号:(i % 2) == 0。当然,如果你做类似于i = 2 * j + 1;的事情,你需要多个括号:i = ((2 * j) + 1);。你的代码最终会看起来像lisp(很多愚蠢的无间道括号)。不是你问的那样,而是愚蠢的规则导致了愚蠢的代码。其中一些准则是针对初学者错误的编码解决方案。大多数程序员不会让初学者待太久,遵循初学者的规则不是一个好主意。学会写正确的代码。
- 正如你还没有说过的,你必须假设我们正在谈论C++的当前版本,因此,你不应该用很好的理由使用原始指针,如果是,当设置为"非有效状态"时,将其设置为EDCOX1〔10〕。
- 在现代C++中,你不应该使用EDCOX1 11。始终使用智能指针。
- @罗恩:埃多克斯1〔12〕。
- 最流行的原因似乎是"当您稍后决定向循环体添加更多语句时,可以忘记添加大括号"。不过,我认为这是一种夸大。实际上,我使用的每一个IDE都会自动更正缩进,这样的东西会像拇指痛一样突出。
- 我发现,对于一个有如此多答案和评论(包括答案上的答案和评论)的问题来说,这是非常值得注意的:代码的主要受众是其他程序员和您自己。在未来的许多年里,你可能对这个系统和你正在应用的假设知之甚少。总是假设有人在阅读你的代码时违反了主要的最后期限(现在生产量下降,每小时花费数百万美元),他们是精神病患者,知道你住在哪里。
- @理查德:不幸的是,在这种情况下,你不能赢。当在两个规则之间做出选择时,有一个假设性精神病患者不能阅读一个规则,另一个假设性精神病患者不能阅读另一个规则。所以不管你编码的是哪一个,都会有人发现你的代码不可读。我的意思是,"发现不可读"是指"愿意在理想样式的上下文中反对可读性",不一定他们实际上不能阅读它。
- 因此,您的代码也可以在Perl中不做任何更改地工作。
- 你说这是C++?对象退出其作用域时自动调用析构函数…因此,将本地VAR放在它们的块中,在它们的块结束时释放它们,这在C++中是一件很好的事情。
- 我跟不上6号。我有一个bug,我空白了,忘记了我删除的指针是一个共享资源。如果删除后将其设置为空,我的程序将继续运行,并发出错误的答案。因为我没有将其设置为空,所以下次运行它时它会出错,让我找到实际的问题。永远不要让电脑把你的逻辑错误扫到地毯下而忘记它们。
- @史蒂文杰索普更多的是一个假设性的精神病患者,由于声明不在一个街区,并在测试中被错过,因为边缘案件没有考虑。我被这个烫伤了(但不严重)。(然而,如果精神病患者不能阅读格式一致的代码,无论哪种常见的样式,那么根本问题是他们缺乏能力)。TL/DR:不是关于支架的放置,而是它们的存在,我同意规则3,对于那些例外的情况,它可以节省我的屁股。
- @理查德:够公平的,如果他们在修改我的代码时出错,他们会用斧子来抓我,但是如果他们认为我的大括号妨碍了他们对我代码的看法,他们就不会用斧子来抓我;-)事实上,我确实是这样做的,除非我在一行上写if (condition) statement;,这是罕见的。所以我应该对你这种精神病患者基本上是安全的,但不是完全安全。不幸的是,我把开口支架放在同一条线上,所以rms无论如何都会找到我。
- 我想我只同意那张单子上的第一项。你应该对你的代码的正确性充满信心,因为你测试了它,而不是因为你遵循了一些规则来避免错别字。
- @卢西亚·格里戈尔,我非常同意你的观点。双重删除通常是一个逻辑错误。除非按设计的指针可以为空,否则不应将其设置为空。
- #1是这里唯一没有问题的指导方针
- 在您的示例中,您的循环应该是for (int i = 0 ; 100 > i ; ++i)(根据规则4)。
- @汤坦纳的问题"征求辩论,争论[…]或延长讨论"。下面的答案不仅仅是证明。但是已经有4个重新开放的投票,所以请放心,辩论、辩论和延长的讨论很快就会恢复生机。
- 只有OTB。
- 带括号的一般规则是,如果我必须问自己表达式的运算符优先级是什么,请使用parens使其显式。特别是,我更喜欢使用parens对使用多个布尔运算符的表达式进行分组。我认为任何人都不必阅读操作优先文档来阅读我的代码。但是如果你不知道x + 5 * y == z组是如何组成的,也许你应该在做任何编程之前先上代数课程。
- @当然,这取决于语言。我知道至少有一个不具有运算符优先级,因此您的示例确实会被解析为((x + 5) * y) == z。
- "比较左侧的常量对象//确定"。你的意思是做一个这样的比较:if (5 == myVar)。我喜欢尤达,但没那么喜欢。
- @伊兹卡塔:如果你用流行性腮腺炎或其他类似的语言编程,显然规则是不同的。但是,使用帕伦斯就成了一个必须考虑的问题,而不是风格,因此它与这段对话并不真正相关。
- 如果你的单线是一条macro…哼!
- @emadpres:如果不起作用,您应该更正宏(宏在语法上应该是表达式或单个语句),而不是在代码上添加大括号。
- "有什么好处?"因为否则你会让自己面临这样致命的白痴风险:dwheeler.com/散文/apple-goto-fail.html…或者你对导致不幸的粗心丑陋风格的嗜好-然后是毫无意义的不一致性-就像这样:lkml.org/lkml/2015/12/14/461同样在emadpres,大括号可以在单行上下文中使用,另外你可以人为地使用line-b仍然使用``重新访问宏
- "对比左侧的常量对象//好的"-我以前这么认为。我想,这很聪明!我觉得我很聪明!我想,我是在避免意外的任务!是的-避免意外的分配-在颠倒需要的比较的时候,不总是在匆忙中得到正确的结果,所以有时会颠倒结果和响应。它实际上并不聪明,不值得冒险,并且让大多数人困惑——通常包括你自己——可能是因为它与直觉和数学课相矛盾。C/++对=和==做出了一个可疑的决定,但还不足以证明这一点。
在增加EDOCX1[1]时,我们也尝试修改EDOCX1[0]。
1 2 3 4 5
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++; |
哦不!从python来看,这看起来不错,但实际上不是,因为它相当于:
1 2 3 4 5
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++; |
当然,这是一个愚蠢的错误,但即使是一个有经验的程序员也能犯。
另一个很好的理由是在Ta.speot.is的答案中指出的。
我能想到的第三个是嵌套的if:
1 2 3
| if (cond1)
if (cond2)
doSomething(); |
现在,假设您现在想在不满足cond1的情况下使用doSomethingElse()(新功能)。所以:
1 2 3 4 5
| if (cond1)
if (cond2)
doSomething();
else
doSomethingElse(); |
这显然是错误的,因为else与内部if有关。
编辑:由于这引起了一些注意,我会澄清我的观点。我要回答的问题是:
What's the benefit of using the 1st version?
我已经描述过了。有一些好处。但是,在我看来,"总是"的规则并不总是适用。所以我并不完全支持
Always use a { } block - even for a single line // not OK, why ???
我不是说总是用一个{}块。如果这是一个足够简单的条件和行为,请不要这样做。如果您怀疑有人稍后可能进来,请更改代码以添加功能。
- 很好,但只有两点。1)更糟糕的是,在同一条线上有J+;I+;认为没问题,但事实并非如此。2)一个好的IDE应该告诉你我身份不明,因为它超出了范围。
- @科幻小说:是的,但是如果你在j++之前加上i++,那么两个变量在使用时仍然在范围内。
- 这听起来很合理,但忽略了这样一个事实,即编辑器执行缩进,而不是您,并且它将以一种立即显示它不是循环的一部分的方式缩进i++;。(在过去,这可能是一个合理的论点,我也看到过这样的问题。大约20年前。从那时起)
- @詹姆斯:这不是"事实",而是你的工作流程。很多人的工作流程,但不是每个人的。我不认为将C++源视为纯文本文件,而不是强制执行格式化规则的WYSIWYG编辑器(VI/Emacs/VisualStudio)的输出是错误的。所以这个规则是编辑不可知的,超出了你所需要的,但并不是人们实际用来编辑C++的。因此"防御"。
- @Jameskanze你真的依赖于这样一个假设:每个人都在强大的IDE中工作?我写的最后一个C是纳米的。即便如此,在一个IDE中我首先要关闭的是自动缩进——因为这个IDE会妨碍我的非线性工作流程,试图基于不完整的信息纠正我的"错误"。IDE并不擅长自动缩进每个程序员的自然流。那些使用这些特性的程序员倾向于将他们的风格合并到他们的IDE中,如果您只使用一个IDE,这是很好的,但是如果您跨多个IDE工作的话,就不需要这样了。
- @Jameskanze:我敢打赌,很多程序员会把这两条指令写在一行中,因为它们太短了。然后是卡邦。
- 显示正在执行的操作的实际情况的示例应该使用大括号,这样编译器将100%清楚地处理这些简短示例。
- "这是一个愚蠢的错误,但即使是一个有经验的程序员也能犯下。"——正如我在回答中所说,我不相信。我认为这是一个完全人为设计的案例,在现实中并不构成问题。
- @Konradrudolph你已经够幸运了,因为这个你不必去修理虫子。我有。在某些情况下,这一点都不明显。
- 一个C++源是一个"纯文本",我经常这样对待它,例如使用EDCOX1 3或EDCX1 4。但是开发高质量的软件是困难的,如果不使用我可用的所有工具,那将是愚蠢的。源代码编辑器比比皆是;当合适的工具随时可用时,为什么要使用不合适的工具?(我见过的编辑——你提到的三个——不执行格式化规则。您总是可以覆盖它们。但是,当隐式格式不符合您的期望时,通常是您犯了错误的迹象。)
- @Gorpik我从来没有见过允许程序员将两个语句放在一行中的编码准则。
- @詹姆斯:好吧,"强制"可能是个错误的词,当然,一个合适的IDE对源代码所做的一切都是可选的和可配置的。下流的行为有时也有自己的想法。但是,如果他们不强制执行,那么说缩进是由编辑器而不是程序员完成是不正确的。我的编辑器首选的"自动缩进"程度是当我点击返回键时保持当前的缩进级别,并且只有当我希望编辑器生成一个不是我自己的特定样式时(即,当编码到严格的标准时),我才会从这个级别更改。
- 接下来是"来自python"…您还需要一个规则来在每个(非复合)语句后放置分号。我发现我忘记这些比忘记不必要的牙套更频繁。
- "stevejessop有趣。在这里,我找到两个编辑器将缩进的说我的满意度比IP,所以"校正"的编辑的压痕从小时两个小时。但在《编辑做第一自动压痕,取决于parentheses牙套,等打开IP,有这么多的错误,我在我的typlng不会梦想的工作,没有它。酸性,编译器将complain以后,但在之前找到的(误差,是更好的。
- 当我们使用数据驱动的实时代码缩进的回答,这是java。这是一个在前,但我不记得它让错误,我做了它,因为太阳的压痕的规则是(或至少是Oracle,也许他们愚蠢的婊子了),我有麻烦的表壳,手动上弦跟随他们。在一定没有感觉的缺乏它,当我不是用它。你在做一件事情,但不管怎样,这是我的错误catches工具,让他们更多的错误往往比它不。
- 怎么非C语言吗?Delphi的范围内的变量有(A程序,没有进一步的定域。其中《阶+ +(或公司)或J +冰+不叫他们的循环。
- 如何回答这两顶mostly preposterous offerings与他们有过一个这样的赞美的城市的投票(鸭怎么变得如此流行的问题超出了我的冰淇淋)说,只有两个# assume',C和Java的人c++overran这个简单的问题。
- 第三,合理的对嵌套IFS出现很多错误,当固定在传统的C / C + +代码(认为K &;R型"天之前的鲁棒的主意)。想了两个图如果原编码器的代码意味着两个executed为书面,或只是messed IP冰problematic编码器是当他们长retired(或死亡)。在这些案例在语言能力有两个demonstrated引起混淆,让你明确的意图。未来的一代会感谢你的。
- 在VC的方式很多,真的愚蠢的错误,但他们已经没有引起西安市unbracketed,如果一行。这是一个"最佳"的几年实践,我不agree与。(这个评论会咬我明天的工作,稍等)
- "你是chadschouggins酮上市的幸运。在使用{}只读的时候会发生什么的预测是错误的(因此这项防御)。它不我任何两个成本与{}环绕的声明,但它可以保存你的未来的问题,为什么不呢?
- 好的,很好用,如果其他解释的实例。谢谢luchian GRIGORE这是真的很酷。
- "如果你怀疑有人会进来以后&;改变你的代码添加两个功能性,做的。"我认为这是唯一的一个全我的代码是主体两人(包括我自己)在未来6个月的道路和功能性变化?"底线是,它使《娱乐maintainable和读码,这应该是一个目标的所有代码。
- 永远使用{},甚至在平凡的情况下,当冰所需添加另一个表的两个相同的块,这可能是不小心被忘却的零售。相信我,我已经看到太多的说这两个小时。
- 当我第一次读《实例,在思想i++会化为泡影。for环(incremented鸭会在每一个迭代)。在看《等价的代码,在realised在没看到,一些牙套是缺失的。所以,当然我想安治疗方案可以让相同的错误。我的个人原则是永远不会有一个内联内联块内的块,但一般避免内联块共readability,除非他们的改进。
如果不使用{和},很容易意外地用注释更改控制流。例如:
1 2 3 4 5 6
| if (condition)
do_something();
else
do_something_else();
must_always_do_this(); |
如果您用一行注释来注释do_something_else(),您将得到以下结果:
1 2 3 4 5 6
| if (condition)
do_something();
else
//do_something_else();
must_always_do_this(); |
它编译,但并不总是调用must_always_do_this()。
我们的代码库中存在这个问题,在发布之前,有人很快就进入其中禁用了一些功能。幸运的是,我们在代码审查中发现了它。
- 哦,男孩!!它的定义的行为,如果你must_always_do_this();will execute评论/做什么其他的_ _();
- 好的答案,但似乎像我第一句话得到了它的错误。所以在固定它。在案例中只是说我错了,因为它的奇怪的是没有一个或noticed和改变它。
- "超",这是第一次写的,他说的……很难打破的,如果你使用正确的流程,然后给出了牙套,易安院如何以它不具有两个破发bracketed恰当的代码
- "seancheshire啊,现在看到的是什么?意味着。在interpreted"变更为"AA"disabling按用途的声明"。我的祈祷。好的,blueraja。
- 型前几天我就遇到了这个。if(debug)
//print(info);。基本上拿出了整个图书馆。
- 型Fortunately we caught it in code review.哎呀!听起来很不对劲。Fortunately we caught it in unit tests.会更好!
- 型@B?你说我?但是如果代码在单元测试中呢?这让人难以置信。(开玩笑吧,这是一个传统的应用程序。没有单元测试。)
我对讲师的能力有疑问。考虑到他的要点:
好啊
有人真的会写(或想读)(b*b) - ((4*a)*c)吗?一些先例是显而易见的(或者应该是),还有一些附加的括号只是增加了混乱。(另一方面,你应该使用在不太明显的情况下插入括号,即使你知道它们不是需要。)
某种程度上。格式化有两种广泛分布的约定条件和循环:
还有:
1 2 3 4
| if ( cond )
{
code;
} |
首先,我同意他的观点。开口EDOCX1[1]不可见,所以最好假设它一直在那里。然而,在第二个阶段,我(和我共事过的大多数人)不介意忽略单个语句的大括号。(当然,前提是缩进是系统化的,并且您始终使用这种样式。(还有很多非常好的程序员,写了非常可读的代码,省略了即使格式化第一种方式,大括号也是如此。)
不,像if ( NULL == ptr )这样的东西丑陋得足以阻碍可读性。直观地写下比较。(在很多情况下结果是右边的常量。)他的4是坏建议;任何东西这使得代码变得不自然,使其可读性变差。
不,除了int以外的任何东西都是为特殊情况保留的。到有经验的C和C++程序员,使用EDCOX1,4位信号位运算符。C++没有真正的基数类型(或其他任何类型)。有效子范围类型);unsigned不适用于数值,因为晋升规则。数值,不算术运算是有意义的,就像序列号一样,可以大概是unsigned。但是,我反对它,因为它发送错误的消息:按位操作也没有意义。基本规则是积分类型为int,除非使用其他类型的重要原因。
不,系统地这样做是误导,实际上防患于未然。在严格的OO代码中,delete this;通常是最常见的情况(不能将this设置为NULL),以及否则,大多数delete都在析构函数中,因此您无法访问无论如何,稍后指针。把它设置到EDOCX1[10]没有任何作用。任何其他的指针。设置指针系统地对NULL给出了错误的安全感,而不是真的给你买任何东西。
查看任何典型引用中的代码。stroustrup违反例如,除了第一条规则之外,你给出的每一条规则。
我建议你再找一个讲师。真正知道什么的人他在说。
- 型数字4可能很难看,但它有一个目的。它试图防止if(ptr=null)。我想我从来没有用过delete this,它比我见过的更常见吗?我不认为在使用后将指针设置为空是一件糟糕的事情,但YMMV除外。也许只是我一个人,但他的大部分指导方针似乎并不那么糟糕。
- 型@Firedragon:大多数编译器都会警告if (ptr = NULL),除非你把它写成if ((ptr = NULL))。我不得不同意詹姆斯坎兹的观点,他认为拥有埃多克斯1〔4〕的丑陋首先使我明确地拒绝了。
- 型@詹姆斯坎兹:我不得不说,我不同意你在这里所说的大部分内容——尽管我欣赏并尊重你提出的论点。对有经验的C和C++程序员,使用无符号信号位运算符。-我一点也不同意:使用位运算符表示使用位运算符。对我来说,使用unsigned表示程序员希望变量只代表正数。与有符号的数字混合通常会导致编译器警告,这可能是讲师的意图。
- 型我不同意你的许多观点(尤其是3&5),但无论如何+1,因为你表明讲师的教条主义列表是一个错误,无论什么。这种规则,特别是在没有正当理由的情况下,是很糟糕的。
- 型对有经验的C和C++程序员来说,使用无符号信号位运算符或不用。有人吗?
- 型@Ta.speot.is:size_t当然是无符号类型,但它不一定与unsigned相同,使用它不使用关键字unsigned。在我看来,你可以用size_t来表示尺寸,当然不能用unsigned。但我不确定讲师说的"使用未签名"是指"使用unsigned"(这是詹姆斯的回应),还是"使用未签名类型"(其中size_t是其中之一)。那么第二阶段的论点是,你应该用size_t来表示尺寸吗?我认为这很好,但有些人(包括stroustrup iirc)并不总是麻烦,其他人则积极避免。
- 型@利奥:我不太喜欢在我的程序中使用这种风格,但我只是指出了一个使用它的原因:大多数编译器都会发出警告,但有些人会忽略警告,如果它能帮助缺乏经验的开发人员立即发现错误,那就不是什么坏事了。我认为康拉德所说的规则的原因清单应该由讲师提供。
- 我真的不知道你所说的参考,但是strustroup发明并维护了语言,我想可能不维护有数百万行代码的系统。对于初学者来说,关于语言或特性的例子不一定涉及最佳实践,因为它们可能隐藏了示例的要点。
- @詹姆斯·坎兹,考虑一下目的。您正在将经验丰富的程序员生成的代码与教学示例进行比较。这些规则是由讲师提供的,因为他们是他看到学生犯的错误。有了经验,学生可以放松或无视这些绝对。
- @最后,你必须找到一个共识。问题是,即使在实际值不能为负值的情况下,C++中的EDOCX1 0的语义也不适用于数值。例如,像abs( a - b )这样的表达式给出了错误的结果。
- @是的。比实际情况更糟的是,没有给出任何理由。所有的规则都应该有一个理由。
- @Ta.speot.是size_t必须是无符号整型的事实,这是该语言的历史缺陷。事实上,要求签名的部分动机与未签名的问题有关。如果要使签名依赖于实现,则情况会更糟。(在16位机器上,您不希望要求它超过16位,即使大小大于32K的对象可以也确实存在。)
- @TwisterrobStroustrup发明了这种语言,因为他必须构建和维护大型程序。(至少部分如此。)
- @其中一些(比如建议使用unsigned)是不好的规则,特别是对于初学者。我很清楚积分推广的规则,我通常可以避免unsigned的陷阱,但初学者应该只看到两种数字类型:double和int。(形式上,bool和char也是数字类型,但初学者不应将其视为数字类型:bool只应采用true和false的值,char应始终包含字符。)
- @stevejessop规则是积极避免无符号整数类型。事实上,我比大多数专家更不严格;在有限的情况下,我主要是比较std::vector<>::size(),例如,我将使用size_t。但一般规则是,如果比较两个值之间的差异大小是有意义的,那么必须避免所有无符号整数类型。abs( a - b )不会给你预期的结果。
- 我明白这一点,但每当有人争论时,我都想知道,定义distance函数终究会不会是一场天启般的灾难。我肯定会支持这样一种方案,即无符号类型上的abs会给出错误或至少是警告。但是,船已经启动了:C有太多的隐式转换,C++引入了EDOCX1×17的重载,所以你不能阻止人们编写EDCOX1×19。除了哈希之外,模2 ^ n算术不是你想要的,而且溢出的未定义行为永远不是你想要的,所以C++的整数类型都从根本上被打破了。
- 我希望看到这个答案被删除,因为大多数答案和随后的讨论与原始问题无关。这是噪音。
- @SteveJessop这个问题可能比其他任何问题都更像是一个隐式转换。但是,无符号类型的减法返回无符号类型也是一个问题。使用无符号的参数实际上很大程度上是具有子范围类型的参数。这可能不是一个坏主意,但它不是在C++(也不,据我所知,它已经被提出)。
- @克里斯托弗不,这不是噪音,它显示了一个非常重要的方面:即,不管我们如何看待单子上的个别点,编制这样一个单子,如教条是一致认为是坏的。可以说,这就是这个问题的答案。
- 我同意除To experienced C and C++ programmers, the use of unsigned signals bit operators以外的一切。
- 我只想说:wtf删除这个????永远不要这样做。尝试不使用原始指针,独特的指针更好。如果执行原始指针,则删除并分配给nullptr有助于查找错误,因为这些指针上的操作会为您提供正确的segfaults,而不是静默地假装工作。
- @阿恩,不清楚你的意思是什么。编写良好的应用程序中的大多数指针都是原始指针,因为大多数指针都是用于导航的(而std::unique_ptr不能用于导航)。将nullptr分配给刚删除的内容通常是浪费时间;通常,delete将处于一个上下文(例如析构函数)中,指针无论如何都将在该上下文中停止存在,并且设置一个指向nullptr的指针对对象的其他指针没有任何作用。
- @对于delete this来说,它是否合适取决于应用程序。(例如,在事务完整性很强的情况下,delete可能必须等到事务结束。)不过,正常的OO理念是对象本身具有智能,并管理自己的生命周期,在响应某些外部事件时删除自己。
- 把它扔出去,有一种使用较少但仍然标准的类型,叫做ssize_t,它的签名是size_t。
所有其他答案都支持你的讲师规则3。
让我说我同意你的观点:这条规则是多余的,我不建议这么做。如果你总是加上花括号,理论上它可以防止错误。另一方面,我在现实生活中从未遇到过这样的问题:与其他答案所暗示的相反,我从来没有忘记在必要时添加花括号。如果使用适当的缩进,那么很明显需要在多个语句缩进一次后添加大括号。
"组件10"的答案实际上突出了唯一可能导致错误的情况。但是另一方面,通过正则表达式替换代码无论如何都需要非常小心。
现在让我们看看奖牌的另一面:总是使用花括号有什么缺点吗?其他答案只是忽略了这一点。但是有一个缺点:它占用了很多垂直的屏幕空间,这反过来会使代码不可读,因为这意味着你必须滚动更多的内容。
在开始时考虑一个带有很多保护子句的函数(是的,下面是坏的C++代码,但在其他语言中,这将是一个非常常见的情况):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void some_method(obj* a, obj* b)
{
if (a == nullptr)
{
throw null_ptr_error("a");
}
if (b == nullptr)
{
throw null_ptr_error("b");
}
if (a == b)
{
throw logic_error("Cannot do method on identical objects");
}
if (not a->precondition_met())
{
throw logic_error("Precondition for a not met");
}
a->do_something_with(b);
} |
这是一个可怕的代码,我强烈认为下面的代码可读性要高得多:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void some_method(obj* a, obj* b)
{
if (a == nullptr)
throw null_ptr_error("a");
if (b == nullptr)
throw null_ptr_error("b");
if (a == b)
throw logic_error("Cannot do method on identical objects");
if (not a->precondition_met())
throw logic_error("Precondition for a not met");
a->do_something_with(b);
} |
同样,短嵌套循环也得益于省略大括号:
1 2 3 4 5 6 7 8 9
| matrix operator +(matrix const& a, matrix const& b) {
matrix c(a.w(), a.h());
for (auto i = 0; i < a.w(); ++i)
for (auto j = 0; j < a.h(); ++j)
c(i, j) = a(i, j) + b(i, j);
return c;
} |
比较:
1 2 3 4 5 6 7 8 9 10 11 12 13
| matrix operator +(matrix const& a, matrix const& b) {
matrix c(a.w(), a.h());
for (auto i = 0; i < a.w(); ++i)
{
for (auto j = 0; j < a.h(); ++j)
{
c(i, j) = a(i, j) + b(i, j);
}
}
return c;
} |
第一个代码简洁;第二个代码臃肿。
是的,这在某种程度上可以通过在前一行放置开口支撑来减轻。但这仍然比没有任何花括号的代码可读性差。
简而言之:不要编写占用屏幕空间的不必要代码。
- +(一)有合理的竞合理由。但是我倾向于不同意。也许很奇怪,但是我发现如果没有额外的括号,你的矩阵示例就很难阅读了。我可以看到第一个看起来更好,因为抛出会将您踢出函数,但对于大多数示例,我需要括号。我还看到了函数的第一行是有条件的,后面跟着一个"返回",这也没那么糟糕。但它的使用与可能出现的错误相比,我认为太棒了。
- 如果您不相信编写不必要地占用屏幕空间的代码,那么您就没有必要将左大括号放在自己的行上。我现在可能不得不避开GNU的神圣报复,但认真地说——要么你想让你的代码垂直紧凑,要么你不想这样做。如果你这样做了,不要做那些仅仅是为了减少代码的垂直紧凑而设计的事情。但是正如您所说,修复了这个问题之后,您仍然需要删除多余的大括号。或者把if (a == nullptr) { throw null_ptr_error("a"); }写成一行。
- fwiw,在矩阵示例中,我个人会将左括号留在括号中,左括号与for在同一行,右括号各在自己的行上。但在循环之后我不会有空行,在循环之前我也可能没有空行,所以我最多使用一行,而不是您喜欢的样式。
- @史蒂夫,事实上,我确实把开口撑放在前一行,因为你所说的原因。我在这里使用了另一种风格,以使它更明显的区别是多么的极端。
- +1我完全同意,没有大括号,您的第一个示例更容易阅读。在第二个示例中,我的个人编码风格是在循环的外部使用大括号,而不是在内部使用大括号。我不同意@stevenjessop关于必须是一个极端或另一个极端的关于垂直压缩代码的观点。为了减少垂直空间,我省略了带有一个衬板的额外大括号,但我确实将开放大括号放在了一条新行上,因为我发现当大括号排列时,更容易看到范围。目标是可读性,有时这意味着使用更多的垂直空间,有时这意味着使用更少。
- 我不会说您的第一个示例"可怕",但我确实发现它比没有大括号的可读性稍差。当有嵌套时,使用大括号可能更容易阅读,但是,不管有没有大括号都不是很严重的问题,只要您是一致的。事实上,重要的一点是,这条规则在孤立的情况下可能没有意义。有几种方法编写可读的C++代码,基于缩进、括号和一些其他的东西。你决定一个,坚持下去。但考虑的是整体风格,而不是任何孤立的元素。
- "我在现实生活中从未遇到过这个问题":你真幸运。像这样的事情不仅会烧坏你,还会给你90%的三度烧伤(这只是一些管理层要求在晚上晚些时候修复)。
- @理查德,我就是不买那个。正如我在聊天中所解释的那样,即使这个错误应该发生(我认为不太可能发生),只要查看堆栈跟踪就可以解决这个问题,因为很明显,只要查看代码,错误就在哪里。你夸大的说法完全没有根据。
- "我在现实生活中从未遇到过这样的问题":@richard是对的——这是那种在最沉闷的时刻往往会出现的事情。
- 如果您编写的代码省略了大括号,并且代码是由其他人修改的,并且其中一些程序员没有您那么细心,我保证在某些时候会出错。也许你不必去处理它,因为你从不犯错误,但是如果你能通过输入两个额外的字符来帮助防止将来的错误,为什么不呢?
- @布赖恩,关键不是我没有犯这个错误。关键是我不相信任何有经验的程序员会犯这个错误,或者,如果他们犯了这个错误,他们会立即抓住它,并且不会造成任何伤害。上面,理查德甚至声称这会导致很多艰苦的工作。我觉得这句话太不可思议了。
- 出于上述原因,我并不总是使用大括号。答案是在问题的背景下给出的:"这条规则获得了什么"。在我的答案中引用的"现实世界"的例子不是我写的代码,而是一个同事的代码。如果你正确地使用意图,很难让你自己进入一个你在不知情的情况下改变无支撑的流控制声明的位置。
- @斯佩特很公平。卢西亚在聊天中也告诉过我很多相同的事情,所以我认为从我的答案中删除你的名字是公平的。毕竟,这不是个人的指控。干杯。
- 我在你的回答中对我的名字没有任何问题,特别是作为我的回答的参考。无论如何,谢谢你的考虑。
- goto fail
- @xfix是的,goto fail是与这个答案完全相反的例子。但是,我必须同意康拉德·鲁道夫的观点,即完全支持的代码非常丑陋。就我个人而言,我不采用任何风格,而是在同一行的if()或for()后面直接放置一个声明体。这可以防止像goto fail这样的错误,并且是编写此类代码的最简洁的方式。
- @实际上,使用大括号根本无法防止"goto fail"。诚然,一开始我也这么认为,但如果你稍微考虑一下——不,goto fail;行可能是由版本控制合并或类似的过程添加的——即使是在有支撑的代码中,它也可能在括号之后,导致完全相同的问题。这里真正的教训是:使用适当的警告级别;所有现代编译器都会警告此代码。这不应该通过审查。
- @stevejessop:如果每个if、while等后面都跟着一个缩进的单语句或一个{,它的缩进与控制语句和匹配的}匹配,那么单语句控制块就很容易识别出来了。将{移到前一行,很难直观地确认代码的结构是否符合预期。我假设,将每个{放在与它的}相同的行或列中,可以为复杂的控制结构添加一行,但允许从简单的控制结构中删除一行。对于具有更简单控制结构的代码,这是一个胜利。
我正在研究的代码库中散布着一些对大括号有着病态厌恶的人编写的代码,对于后来出现的人来说,它确实可以对可维护性产生影响。
我遇到的最常见的问题例子是:
1 2
| if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
this_looks_like_a_then-statement_but_isn't; |
因此,当我提出并希望添加一个then语句时,如果我不小心的话,我可以很容易地得到这个结果:
1 2 3 4 5
| if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
this_looks_like_a_then-statement_but_isn't;
i_want_this_to_be_a_then-statement_but_it's_not;
} |
考虑到添加大括号需要大约1秒钟的时间,并且至少可以节省几分钟的调试时间,那么为什么不使用减少歧义选项呢?在我看来,这似乎是一种错误的经济。
- 这个例子中的问题不是在于不正确的缩进和太长的线条,而不是大括号吗?
- 是的,但是遵循设计/编码准则,这只是"安全的",假设人们也遵循其他准则(例如没有太长的行),似乎是在自找麻烦。如果从一开始就有支撑,那么在这种情况下,不可能以一个错误的if块结束。
- 如何添加支架(使其成为if(really long...editor){ do_foo;}帮助您避免这种情况?看来问题还是一样的。就我个人而言,我更喜欢在不需要大括号的时候避免使用大括号,但是这与编写大括号所需的时间无关,而是由于代码中的两行额外增加,导致可读性降低。
- 好的一点-我假设强制使用大括号也会导致它们被放在一个合理的地方,但当然,有人决心让事情变得困难,可以像在您的例子中一样将它们放在一条直线上。不过,我想大多数人不会。
- 当我触摸文件时,第一件也是最后一件事就是点击自动格式化按钮。它消除了大部分这些问题。
- 如果制作大括号需要一整秒钟,您应该去打字课。s
我的2C:
Use Indentation
明显地
Never rely on operator precedence - Always use parentheses
我不会用"从不"和"总是"这两个词,但总的来说,我认为这条规则是有用的。在某些语言(lisp、smalltalk)中,这不是问题。
Always use a { } block - even for a single line
我从来没有这样做过,也从来没有遇到过一个问题,但我知道这对学生有什么好处,尤其是如果他们以前学过Python。
Const object on left side of comparison
尤达情况?不,请。它损害可读性。编译代码时只需使用最大警告级别。
Use unsigned for variables that are >= 0
好啊。有趣的是,我听说斯特劳斯特鲁普不同意。
Set Pointer to NULL after deletion - Double delete protection
糟糕的建议!永远不要有指向已删除或不存在对象的指针。
- +仅就最后一点而言。原始指针无论如何都没有拥有内存的业务。
- 关于使用unsigned:不仅是stroustrup,还有k&r(在c中)、herb sutter和(我认为)scott meyers。事实上,我从未听说过有人真正理解C++使用无符号的规则。
- @事实上,就在我听到斯特劳斯鲁普的意见(2008年在波士顿召开的一次会议)的同一时刻,赫伯·萨特也在场,并在现场与比雅恩意见不同。
- @内曼扎特弗诺维奇,然后他改变了他的看法(或者我把他和别人搞混了)。我不能从这里访问谷歌集团,我也不确定要使用什么搜索标准,但我知道在过去的一段时间里,comp.lang.c++.moderated中有一个关于它的大讨论,最后,大家一致反对使用unsigned。包括最初为之辩护的人。(共识的一部分是你避免它,因为在C++中定义的EDCOX1×9)被破坏了。我不会那么做,但它的语义确实限制了它的可用性。)
- 只是为了完成"EDCOX1 9"的破坏,其中一个问题是,当C++比较类似大小的符号和无符号类型时,在进行比较之前,它转换为未签名的类型。从而导致价值的变化。转换为带符号的并不一定更好;比较应该"像"两个值都转换为一个更大的类型,它可以表示两种类型中的所有值。
- @我刚在网上搜索过。我想的可能是斯科特·梅耶斯,而不是赫伯·萨特。参见ARISTIA.COM/Copy/C++ReaveStudio/SEP95.PDF
- @詹姆斯:这当然证明了迈耶斯反对使用无符号类型,但我发现他最后的结论很特别,即任何函数都不应返回大于INT_MAX的值,因为(他说)当int不是一个足够大的结果类型时,必须满足将其分配给int的客户。因此,我相信他的立场是"不同意",但我想知道这篇论文是否真的是他所认为的关于这个问题的最后论据。一个客户机对一个如此愚蠢的数字有什么影响吗?作为接口的设计者,这不是我的错吗?
- 公平地说,这篇论文写于1995年,当时16位int在我们后面,64位long在我们前面。我们是"使用适当电脑的人"。因此,也许所有提到的int在现代代码中都应该被视为intmax_t,那么结论是,您不应该仅仅为了扩展程序处理的范围而使用INTMAX_MAX之外的值。
- @SteveJessop我认为您必须在返回unsigned的函数的上下文中使用它。我相信他对exp(double)返回的值大于MAX_INT没有问题。但是,真正的问题还是隐式转换。EDOCX1 3是完全有效的C++。stroustrup实际上曾一度提出了去预测有损隐式转换,但委员会对此并不感兴趣。(一个有趣的问题:unsigned->int是否被认为是有损的?我认为unsigned->int和int->unsigned都有损耗。这对制作unsigned有很大帮助。
- Never have a pointer which points to a deleted or non-existing object.如何在删除对象后不更改指针的情况下执行此操作?
它更直观,更容易理解。它清楚地表明了意图。
它确保了在添加新的代码语句时,当新用户可能无意中错过{、}时,代码不会中断。
- Makes the intent clear+ 1,这是我们最简明和准确的原因。
为了增加上面非常明智的建议,我在重构一些关键代码时遇到的一个例子是:我修改了一个非常大的代码库,以便从一个API切换到另一个API。第一个API调用如下设置公司ID:
1
| setCompIds( const std::string& compId, const std::string& compSubId ); |
而替换需要两个电话:
1 2
| setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId ); |
我开始使用非常成功的正则表达式来更改它。我们还通过Astyle传递了代码,这使代码更加可读。然后,在审查过程的一部分过程中,我发现在某些有条件的情况下,它正在改变这一点:
1 2
| if ( condition )
setCompIds( compId, compSubId ); |
对此:
1 2 3
| if ( condition )
setCompId( compId );
setCompSubId( compSubId ); |
这显然不是所要求的。我不得不回到开始的时候,再次这样做,将替换部分完全放在一个块内,然后手动修改任何看起来很愚蠢的内容(至少它不会是错误的)。
我注意到Astyle现在有了选项--add-brackets,它允许您在没有括号的地方添加括号,如果您发现自己和我处于相同的位置,我强烈建议您这样做。
- 有一次我看到了一些文档,上面写着"微软疏忽"这句妙语。是的,在全局搜索和替换中可能会犯重大错误。这就意味着全球搜索和替换必须智能使用,而不是微软的疏忽。
- 我知道这不是我死后要做的,但是如果你要在源代码上做文本替换,那么你应该按照同样的规则来做,你将使用在语言中建立良好的文本替换:宏。您不应该编写宏#define FOO() func1(); \ func2(); (在反斜杠后面加一个换行符),搜索和替换也是如此。也就是说,我已经看到"总是使用大括号"作为样式规则,因为它可以避免在do .. while(0)中包装所有多语句宏。但我不同意。
- 顺便说一句,这是"成熟的",在这个意义上,日本野草是成熟的:我不是说我们应该走出我们的方式使用宏和文本替换,但我是说,当我们做这样的事情,我们应该以一种可行的方式,而不是做一些只有当一个特定的风格规则成功地强加给整个代码库:—)
- @史蒂文杰索普:当然,你是对的,而且在使用宏观规则方面也很有意义。也同意尾巴不应该摇狗。EDCOX1×0显然不是在C++代码上执行基于上下文的重构的最佳工具。也就是说,当时没有更多合适的工具可用,如果我诚实的话,我从这些经验中学到了很多东西,比如说,你从你的错误中学到了很多东西。其中一个最大的教训是:尽你所能保护你的代码,从而保护你的声誉,不受未来漫无目的的人(比如我!)
- @史蒂文杰索普也可以为背带和腰带辩护。如果你必须使用这样的宏(我们在C++和EDCOX1前1),那么你应该瞄准它们尽可能多地发挥作用,如果需要的话,使用EDCOX1的2个技巧(还有很多额外的括号)。但这仍然不能阻止你在任何地方使用背带,如果这是房子的风格。(FWIW:我在不同风格的地方工作过,涵盖了这里讨论的所有风格。我从来没有发现任何严重的问题。)
- @詹姆斯:同意,我当然会为宏辩护,即使我也为在代码中到处放置大括号而辩护,这只是我遇到的另一个位置。我认为很多关于不同风格的可读性和错误倾向性的争论都是无意中从人们看到一些令人惊讶的东西并假设它是坏的。如果每个人都知道它是什么,任何一种不完全愚蠢的风格都会坚持下去,因为他们也知道要密切关注它的流行。最大的问题是,当(例如)一个习惯于在任何地方使用大括号的人在没有大括号的情况下误读代码时。
- 我认为,你使用的样式越多,你就越仔细地阅读和编辑代码。所以,即使你有一个最容易阅读的偏好,你仍然可以成功地阅读其他的。我在一家公司工作,在那里不同的团队用不同的"房子风格"写不同的组件,正确的解决方法是在午餐室抱怨没有什么效果,而不是试图创造一种全球风格:—)
- 谢天谢地,我的更改是永久性的,而不是宏驱动的预处理器时间替换。但同样的规则仍然适用。我确实向客户机建议了一个内联包装函数来进行转换,这样做风险更小,实现速度更快,但是这被否决了,因为这样做对未来的维护人员来说是不必要的,他们不需要知道原始的API。不过,我离题了——我使用这个例子主要是为了证明,如果不小心完成的话,一个到多个指令的自动转换会破坏代码。
我到处都在使用{},除了一些明显的例子。单线是其中一种情况:
1 2 3 4
| if(condition) return; // OK
if(condition) //
return; // and this is not a one-liner |
在返回前添加一些方法可能会伤害您。缩进表示满足条件时正在执行返回,但它将始终返回。
C中使用语句的其他示例
1 2 3 4 5
| using (D d = new D()) // OK
using (C c = new C(d))
{
c.UseLimitedResource();
} |
相当于
1 2 3 4 5 6 7
| using (D d = new D())
{
using (C c = new C(d))
{
c.UseLimitedResource();
}
} |
- 只需在using语句中使用逗号,您不必这样做:)
- @Minitech在这里根本不起作用——您只能在类型相等时使用逗号,而不能用于不相等的类型。Lukas这样做的方式是规范的,IDE甚至以不同的格式来格式化(注意第二个using没有自动缩进)。
我能想到的最相关的例子是:
1 2 3 4 5
| if(someCondition)
if(someOtherCondition)
DoSomething();
else
DoSomethingElse(); |
哪个if将与else配对?缩进意味着外部if得到else,但这并不是编译器看到它的方式;内部if得到else,而外部if没有。您必须知道(或在调试模式中看到它的行为方式)才能通过检查找出此代码可能失败的原因。你的期望。如果您了解python,就会更加困惑;在这种情况下,您知道indentation定义了代码块,所以您希望它根据indentation进行计算。然而,C并没有给出一个关于空白的快速翻转。
现在,也就是说,我并不特别同意这个表面上的"总是用括号"规则。它使代码在垂直方向上非常嘈杂,从而降低了快速读取代码的能力。如果声明是:
1 2
| if(someCondition)
DoSomething(); |
…那么应该这样写。语句"总是使用括号"听起来像"总是用括号包围数学运算"。这将把非常简单的声明a * b + c / d转变成((a * b) + (c / d)),引入缺少一个紧密的paren(许多编码者的祸根)的可能性,为什么呢?操作顺序是众所周知的,并且执行得很好,因此括号是多余的。您只需使用括号来执行与通常应用的不同的操作顺序:例如a * (b+c) / d。块大括号是类似的;使用它们来定义在与默认值不同的情况下要做什么,并且不是"明显"(主观的,但通常是相当普通的意义)。
- @Alexbrown…这正是我的观点。OP中所述的规则是"始终使用括号,即使是单行",我不同意这一点,因为我所述的原因。括号对第一个代码示例很有帮助,因为代码的行为方式与缩进方式不同;您必须使用括号将else与第一个if配对,而不是第二个。请取消投票。
仔细看答案,没有人明确说明我习惯的那种做法,讲你的代码的故事:
1 2 3 4 5 6 7 8
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0)
{
j++;
}
} |
变成:
1 2 3 4 5
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0) j++;
} |
把j++放在与if应该向任何其他人发出的信号相同的行上,"我只希望这个块增加j"。当然,这只有在行尽可能简单的情况下才有意义,因为正如Peri提到的,在这里放置一个断点不会非常有用。
事实上,我刚刚浏览了一段Twitter风暴API,它在爪哇有这样的代码,这里是这个幻灯片第43页的执行代码的Relabnter片段。
1 2 3 4 5
| ...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
... |
for循环块中有两件事,所以我不会内联该代码。我从不:
1 2
| int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++; |
这太糟糕了,我甚至不知道它是否有效(按预期);不要这样做。新行和大括号有助于区分单独但相关的代码片段,就像逗号或分号在散文中的作用一样。上面的块是很糟糕的一个很长的句子,有几个从句和一些其他的语句,永远不会中断或暂停来区分不同的部分。
如果你真的想给别人发电报,那是一个只有一行的工作,使用三元运算符或?:形式:
1
| for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++; |
但这几乎是代码高尔夫,我认为这不是一个很好的实践(我不清楚是否应该把J++放在:的一边)。NB我以前没有在C++中运行三元运算符,我不知道它是否有效,但是它确实存在。
简而言之:
想象一下你的读者(即代码维护者)如何解释你的故事(代码)。尽可能清楚地告诉他们。如果你知道初学的编码员/学生在维护这一点,可能会尽可能多地离开{},这样他们就不会感到困惑。
- (1)将语句放在同一行使其可读性变差,而不是变差。特别是简单的思考,就像一个增量,很容易被忽略。一定要把它们换成新品。(2)当然,你可以把你的for循环放在一条线上,为什么这样不行?它的工作原理与您可以省略括号相同,新行在C++中并不重要。(3)您的条件运算符示例,除了可怕之外,是无效的C++。
- 康拉德鲁夫谢谢,我对C++有点生疏。我从来没有说过(1)更易读,但这意味着这段代码应该在线一行。(2)我的评论更多的是,我无法阅读它,也无法知道它是有效的,无论它是有效的还是按预期的;这是一个例子,说明出于这个原因不能做什么。(3)谢谢,我很久没有写C++了。我现在就修。
- 同时,在一行中放入多个以上的表达式会使代码调试更加困难。如何在该行的第二个expresion上放置断点?
因为当您有两个没有{}的语句时,很容易遗漏一个问题。假设代码看起来像这样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();
if ((err = prepare_hash(hash, &hash_result))) != 0)
goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
goto fail;
goto fail;
if ((err = hash_finish(hash)) != 0)
goto fail;
error = do_important_stuff_with(hash);
fail:
hash_free(hash);
return error; |
看起来不错。这个问题很容易忽略,尤其是当包含代码的函数大得多时。问题是,goto fail是无条件运行的。你可以很容易地想象这是多么令人沮丧(让你问为什么最后一个hash_update总是失败,毕竟在hash_update功能中一切看起来都很好)。
但是,这并不意味着我要在所有地方添加{}(在我看来,看到{})是令人讨厌的)。虽然这会引起问题,但我自己的项目从来没有这样做过,因为我的个人编码风格禁止没有{}的条件,当它们不在同一行时(是的,我同意我的编码风格是非常规的,但我喜欢它,并且我在为其他项目贡献时使用项目的代码风格)。这使得下面的代码很好。
1
| if (something) goto fail; |
但不是下面的那个。
1 2
| if (something)
goto fail; |
- 确切地。不要把(完全不必要的)换行+缩进,你完全回避了这个问题,每个人都会很快提出。
有一种方法可以帮助防止上面描述的错误,那就是在不使用大括号的情况下,内联希望发生的事情。当您试图修改代码时,很难不注意到错误。
1 2 3 4 5 6
| if (condition) doSomething();
else doSomethingElse();
if (condition) doSomething();
doSomething2(); // Looks pretty obviously wrong
else // doSomethingElse(); also looks pretty obviously wrong |
- 第二个选项将产生编译错误,因为else与if没有关联。
- 内联的一个不太明显的问题是,大多数IDE在默认情况下使用其自动套用格式实用程序时都将其更改为缩进样式。
- @不过,这是一个高度关注的政治问题。如果我们是在代码基础上合作的,要么我们必须在每一个细节上使用相同的缩进样式,要么我们都同意不要"仅仅因为"自动套用现有代码的格式。如果前者,那么商定的样式仍然可以包括这个,但是您必须配置您的IDE以尊重它,或者不使用自动格式。如果我们永远使用同一个IDE,那么同意通用格式是"无论我的IDE自动套用格式是什么"就非常好了,否则就不太好了。
警告6:删除一个空指针比较安全,因为这是一个禁止操作的操作。因此,如果您意外地通过该路径两次,您不会导致内存损坏,而是释放可用的或已分配给其他对象的内存。
这是静态文件作用域对象和单例对象的主要问题,它们的生命周期不太清楚,并且已知在销毁它们后会重新创建。
在大多数情况下,您可以使用auto-ptrs来避免这种需要。
- 型如果你两次走过那条路,你会发现一个编程错误。将指针设置为空以减少此错误的危害并不能解决基础问题。
- 型同意,但我以前见过这个建议,我相信它在一些专业的编程标准中。我更多地是在评论为什么海报的教授提出了它,而不是什么时候它有什么好处。
- 型按照皮特·贝克尔的说法:这并不能解决根本问题,但可能会掩盖它。(有些情况下,在删除后,您会设置指向NULL的指针。如果NULL是指针在这些情况下要具有的正确值,例如指针指向缓存值,而NULL表示缓存无效。但是当你看到有人将一个指针设置为EDCOX1(4)作为析构函数中的最后一行时,你会想知道他是否知道C++。
通过清晰地定义循环和条件块的范围,它可以使代码更具可读性。它还可以让你避免意外的错误。
我喜欢卢西恩接受的答案,事实上,我学到了很难的方法,他是对的,所以我总是使用大括号,即使是对于单行块。然而,就我个人而言,我在编写过滤器时做了一个例外,正如您在示例中所做的那样。这是:
1 2 3 4 5 6 7 8
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0)
{
j++;
}
} |
在我看来杂乱无章。它将for循环和if语句分隔成单独的操作,当您真正的意图是一个操作时:计算可被2整除的所有整数。在一种更具表达力的语言中,这可以写成如下内容:
1
| j = [1..100].filter(_%2 == 0).Count |
在缺少闭包的语言中,过滤器不能用单个语句来表示,但必须是一个for循环,后跟一个if语句。然而,它仍然是程序员心中的一个动作,我相信这应该反映在代码中,就像这样:
1 2 3 4 5 6
| int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
{
j++;
} |
- 我喜欢每个人如何忽略EDOCX1第12条,为了继续关于压痕的争论;-也许我们可以有一个完全不同的斗殴,如何用"标准"来表示逻辑"为每个i在某个范围内具有某个属性"在C++中没有一个循环,使用一些标准组合的噩梦组合。thms,filter_iterator和/或counting_iterator。
- 另外,如果我们有这样的想法,那么对于如何缩进生成的大量单个语句,我们可能会意见不一致。
- @史蒂夫,这只是一个例子。这种模式有很多合法的用途。很明显,如果你想把1到100之间的数除以2,你所要做的就是100/2。
- 当然,我知道,这就是为什么我抽象为"在一定范围内,每个i都有一定的属性"。正因为如此,通常情况下,人们很快就会忽略实际问题,而倾向于用一种完全不同的方法来处理给出的例子。但是缩进很重要,所以我们不需要;-)
型
另一个添加大括号的示例。有一次我在搜索一个bug并发现了这样的代码:
1 2 3 4 5 6 7 8 9 10 11
| void SomeSimpleEventHandler()
{
SomeStatementAtTheBeginningNumber1;
if (conditionX) SomeRegularStatement;
SomeStatementAtTheBeginningNumber2;
SomeStatementAtTheBeginningNumber3;
if (!SomeConditionIsMet()) return;
OtherwiseSomeAdditionalStatement1;
OtherwiseSomeAdditionalStatement2;
OtherwiseSomeAdditionalStatement3;
} |
如果一行一行地读取方法,您会注意到方法中存在一个条件,如果该条件不为真,则返回该条件。但实际上,它看起来像100个其他简单的事件处理程序,它们根据某些条件设置了一些变量。有一天,快速编码器进来,在方法的末尾添加了额外的变量设置语句:
1 2 3 4 5
| {
...
OtherwiseSomeAdditionalStatement3;
SetAnotherVariableUnconditionnaly;
} |
号
结果,当someConditionIsMet()执行setAnotherVariable无条件分析时,但fast guy没有注意到它,因为所有行的大小几乎都相似,即使返回条件是垂直缩进的,也不那么明显。
如果条件返回的格式如下:
1 2 3 4
| if (!SomeConditionIsMet())
{
return;
} |
它是非常明显的,快速编码器会发现它一目了然。
- 如果您的快速编码器在向函数添加内容之前无法在函数体中发现突出显示的语法return语句,则不应让快速编码器靠近您的代码。你不能阻止这样的人用大括号来控制你的代码。
- @他不再和我们一起工作了。总之,语法突出显示是很好的,但是请记住,有些人看不清楚(去年我甚至看到一个盲人程序员的帖子)。
如果你是一个编译器,它不会有任何区别。两者都是一样的。
但是对于程序员来说,第一个更清晰,更容易阅读,更不容易出错。
型
我认为第一个是清楚的,然后第二个。它给人一种关闭指令的感觉,当代码变得复杂时,即使是endif或begin...end,代码很少也很好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| //first
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
if (i % 2 == 0)
{
j++;
}
}
//second
int j = 0;
for (int i = 0 ; i < 100 ; ++i)
if (i % 2 == 0)
j++;
i++; |
。
有许多可能的方法来编写控制语句;它们的某些组合可能共存,但不会损害易读性,但其他组合会造成麻烦。风格
1 2
| if (condition)
statement; |
将与其他一些编写控制语句的方法舒适地共存,但与其他方法不太好。如果多行控制语句写为:
1 2 3 4 5
| if (condition)
{
statement;
statement;
} |
那么,从视觉上看,哪些if语句控制一条线,哪些语句控制多条线是显而易见的。但是,如果多行if语句写为:
1 2 3 4
| if (condition) {
statement;
statement;
} |
那么,有人试图扩展单个语句if构造而不添加必要的大括号的可能性可能要高得多。
如果代码库大量使用表单,则下一行的单个语句if语句也可能存在问题。
1
| if (condition) statement; |
我自己的偏好是,将语句放在自己的行上通常可以提高易读性,除非有许多具有类似控制块的if语句,例如。
1 2 3 4 5
| if (x1 > xmax) x1 = xmax;
if (x1 < xmin) x1 = xmin;
if (x2 > xmax) x2 = xmax;
if (x2 < xmin) x2 = xmin;
etc. |
在这种情况下,我通常会在这些if语句组前面和后面加一个空行,以便在视觉上将它们与其他代码分开。在同一个缩进中,所有的语句都以if开头,这将提供一个清晰的视觉指示,表明有一些异常。
完成后,最好将指针设置为空。
下面是一个例子,为什么:
A级执行以下操作:
分配内存块
稍后,它会删除这个内存块,但不会将指针设置为空。
B级执行以下操作
分配内存(在本例中,它碰巧被赋予了与被类A删除的内存块相同的内存块。)
此时,类A和类B都有指向同一内存块的指针,就类A而言,该内存块不存在,因为它已经完成了。
考虑以下问题:
如果在类A中有一个逻辑错误导致它写入到现在属于类B的内存中,该怎么办?
在这个特定的例子中,您不会得到一个错误的访问异常错误,因为内存地址是合法的,而所有的情况下,类A现在有效地破坏了类B数据。
如果B类遇到意外的值,它最终可能会崩溃,并且当它崩溃时,很有可能,当问题出现在A类中时,您将花费相当长的时间在B类中寻找这个bug。
如果将已删除的内存指针设置为空,则只要类A中的任何逻辑错误尝试写入空指针,就会出现异常错误。
如果您担心指针第二次为空时出现双删除逻辑错误,请为此添加断言。
另外:如果你要投反对票,请解释一下。
- 如果有逻辑错误,应该修复它,而不是屏蔽它。
- @巴尔玛,OP说…6。删除后将指针设置为空-双重删除保护//不错。有些人回应说没有将它设置为空,我是说为什么应该将它设置为空,这是6的哪一部分。我设置为空的答案不适合6?
- @沙昆,你如何建议首先找到这些逻辑错误?在删除内存后,将指针变量设置为空。任何引用空指针的尝试都将崩溃到您非法尝试的行上的调试器。您可以追溯并查看逻辑错误在哪里,然后修复问题。如果删除内存后未将指针变量设置为空,则由于不知道逻辑错误而非法尝试写入此已删除内存可能会成功,因此不会在该点崩溃。它不能掩盖它。
型
我不得不承认,不一定总是用{}来表示单线,但这是一种很好的做法。
型
过了一段时间,你想要添加j循环一些其他的东西,你只需要通过对齐来完成,然后忘记添加括号。
百万千克1
内存释放更快。假设您有很大的范围并在内部创建大型数组(没有new),所以它们在堆栈中。这些数组将在您离开作用域后从内存中删除。但有可能你在一个地方使用这个数组,它将在堆栈中一段时间,并且是某种垃圾。由于堆栈的大小有限且非常小,因此可能会超过堆栈大小。因此,在某些情况下,最好编写{}来防止这种情况发生。注:这不是针对单线,而是针对这种情况:
如果(…){//有些东西…//我们没有if、while等。//其他东西}//更多的东西}
百万千克1百万千克1
第三种使用方法与第二种类似。它不只是使堆栈更干净,而是打开一些函数。如果在长函数中使用mutex,通常最好在访问数据之前以及完成读/写之后锁定和解锁。注意,如果您有自己的class或struct和constructor和destructor来锁定内存,则使用这种方法。
百万千克1百万千克1
更重要的是:
如果(…)如果(…)somestufacture();其他的someotherstufacture();//转到第二个if,但alligment显示它是在第一个…
百万千克1
型
总的来说,我不能说,什么是最好的方法总是使用{}为一个单一的线路,但这样做并不坏。
重要的是,如果您为一行编写编译代码括号,那么编辑什么都不做,但是如果您的代码被解释得非常慢,那么代码就非常慢。非常轻微。
总是有大括号是非常简单和强大的规则。但是,代码可能看起来当有很多大括号的时候就不好看了。如果规则允许省略大括号,那么应该有更详细的样式规则和更复杂的工具。否则,很容易导致代码混乱和混乱(不优雅)。因此,将单个样式规则与所使用的样式指南和工具的其余部分分开来看可能是徒劳的。我只会介绍一些关于规则3的重要细节,这些细节在其他答案中都没有提到。
第一个有趣的细节是,该规则的大多数支持者都同意在else案件中违反该规则。换言之,他们不要求审查此类代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // pedantic rule #3
if ( command == Eat )
{
eat();
}
else
{
if ( command == Sleep )
{
sleep();
}
else
{
if ( command == Drink )
{
drink();
}
else
{
complain_about_unknown_command();
}
}
} |
相反,如果他们看到了,他们甚至可能建议这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // not fully conforming to rule #3
if ( command == Eat )
{
eat();
}
else if ( command == Sleep )
{
sleep();
}
else if ( command == Drink )
{
drink();
}
else
{
complain_about_unknown_command();
} |
这在技术上违反了这条规则,因为在else和if之间没有花括号。当试图用一个无意识的工具将其自动应用到代码库时,规则的这种二重性就会显现出来。实际上,为什么要争论,只是让一个工具自动应用样式。
第二个细节(该规则的支持者也经常忘记)是,可能发生的错误绝不仅仅是因为违反了该规则。事实上,这些几乎总是涉及违反规则1(没有人争论)。同样,从自动工具的角度来看,当违反规则1时,不难制造出一个立即抱怨的工具,因此大多数错误都可以及时捕获。
第三个细节(经常被该规则的反对者忘记)是由单个分号表示的空语句的混淆性质。大多数有经验的开发人员迟早会被唯一放错位置的分号或使用唯一分号编写的空语句弄糊涂。两个大括号而不是一个分号在视觉上更容易被发现。