为单行if或循环使用大括号(即{})的目的是什么?
我正在阅读我的C ++讲师的讲稿,他写了以下内容:
- 使用缩进//确定
- 永远不要依赖运算符优先级 – 总是使用括号// OK
- 总是使用{}块 – 即使是单行// 不行 ,为什么?
- 比较左侧的Const对象// OK
- 使用无符号的variables是> = 0 //好的技巧
- 删除后将指针设置为NULL – 双重删除保护//不错
第三种技术对我来说是不清楚的:通过在{ ... }
放置一行可以获得什么?
例如,拿这个奇怪的代码:
int j = 0; for (int i = 0 ; i < 100 ; ++i) { if (i % 2 == 0) { j++; } }
并将其replace为:
int j = 0; for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;
使用第一个版本有什么好处?
让我们试图在增加j
时修改i
:
int j = 0; for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++; i++;
不好了! 来自Python,这看起来不错,但实际上不是,因为它相当于:
int j = 0; for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++; i++;
当然,这是一个愚蠢的错误,但即使是一个有经验的程序员也可以这样做。
ta.speot.is的答案指出了另一个很好的理由 。
我能想到的第三个是嵌套if
是:
if (cond1) if (cond2) doSomething();
现在,假设你现在想cond1
没有满足(新function)时doSomethingElse()
)。 所以:
if (cond1) if (cond2) doSomething(); else doSomethingElse();
这显然是错误的,因为else
与内在联系在一起。
编辑:由于这是一些关注,我会澄清我的看法。 我回答的问题是:
使用第一个版本有什么好处?
我所描述的。 有一些好处。 但是,国际海事组织“总是”规则并不总是适用。 所以我不完全支持
总是使用{}块 – 即使是单行//不行,为什么?
我并不是说总是使用{}
块。 如果这是一个足够简单的条件和行为,不要。 如果您怀疑某人稍后可能会进来并更改您的代码以添加function,请执行。
如果您不使用{
和}
,意外地更改控制stream和注释非常容易。 例如:
if (condition) do_something(); else do_something_else(); must_always_do_this();
如果你用单行注释注释掉do_something_else()
,你将会得到这个结果:
if (condition) do_something(); else //do_something_else(); must_always_do_this();
它编译,但must_always_do_this()
并不总是调用。
我们在我们的代码库中遇到了这个问题,在发布之前有人曾经非常迅速地closures了一些function。 幸运的是我们在代码审查中发现了它。
我对于讲师的能力有些怀疑。 考虑到他的观点:
- 好
-
(b*b) - ((4*a)*c)
? 有些优先级是明显的(或者应该是),而额外的括号只是增加了混淆。 (另一方面,即使你知道他们不需要,你也应该在不太明显的情况下使用括号。) - 有点。 格式化条件和循环有两个广泛的约定:
如果(cond){ 码; }
和:
如果(cond) { 码; }
首先,我同意他的观点。 开幕式是不可见的,所以最好假设它总是在那里。 但是,在第二种情况下,我(和大多数我一起工作的人)在省略单个语句的括号方面没有任何问题。 (当然,假设缩进是系统化的,并且一致地使用这种风格(而且很多非常好的程序员,编写非常易读的代码,甚至在格式化第一种方法时也省略了大括号)。
- NO 。 事情就像
if ( NULL == ptr )
是丑陋的妨碍可读性。 直观地写出比较结果。 (在很多情况下,结果是不变的)。他的4是不好的build议; 任何使代码不自然的东西都会使其不易读。 - NO 。 除
int
任何内容都保留给特殊情况。 要经验丰富的C和C ++程序员,使用unsigned
信号位操作符。 C ++没有真正的主要types(或任何其他有效的子范围types); 由于促销规则,unsigned
数字值不起作用。 没有算术运算有意义的数值,如序列号,可能是unsigned
。 然而,我反驳它,因为它发送了错误的信息:按位操作也没有意义。 基本规则是积分types是int
,_unless_有一个使用另一个types的重要原因。 - NO 。 系统地做这件事是误导性的,实际上并没有防止任何事情发生。 在严格的OO代码中,
delete this;
通常是最常见的情况(你不能将this
设置为NULL
),否则,大多数delete
都在析构函数中,因此无论如何你都不能访问指针。 并将其设置为NULL
不会做任何其他指针浮动。 系统地将指针设置为NULL
会带来虚假的安全感,并不会真正为您购买任何东西。
查看任何典型引用中的代码。 例如,Stroustrup违反了所有的规则,除了第一条规则。
我build议你找另外一个讲师。 一个谁真正知道他在说什么。
所有其他的答案捍卫你的讲师的规则3。
让我说,我同意你的观点: 这个规则是多余的 ,我不会build议。 诚然,如果你总是添加大括号, 理论上可以防止错误。 另一方面, 我从来没有在现实生活中遇到过这个问题 :与其他答案所暗示的相反,我没有忘记在需要的时候添加大括号。 如果使用正确的缩进,立即显而易见,您需要添加花括号一次多于一个语句缩进。
“组件10”的答案实际上突出了唯一可能的情况,这可能导致错误。 但另一方面,通过正则expression式来replace代码总是需要非常小心。
现在让我们看看奖牌的另一面:总是使用大括号是否有缺点 ? 其他答案完全忽略了这一点。 但有一个缺点:它占用了大量的垂直屏幕空间,而这反过来又会使你的代码变得不可读,因为这意味着你不得不滚动更多的代码。
在开始时考虑一个有很多guard子句的函数(是的,以下是不好的C ++代码,但是在其他语言中这将是相当常见的情况):
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); }
这是可怕的代码,我坚决认为下面的代码更加可读:
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); }
同样,短嵌套循环也可以省略花括号:
matrix operator +(matrix const& a, matrix const& b) { matrix c(aw(), ah()); for (auto i = 0; i < aw(); ++i) for (auto j = 0; j < ah(); ++j) c(i, j) = a(i, j) + b(i, j); return c; }
与之比较:
matrix operator +(matrix const& a, matrix const& b) { matrix c(aw(), ah()); for (auto i = 0; i < aw(); ++i) { for (auto j = 0; j < ah(); ++j) { c(i, j) = a(i, j) + b(i, j); } } return c; }
第一个代码是简洁的; 第二个代码是臃肿的。
是的,这可以通过在前一行放置左大括号来减轻。 但是,这样的代码不会比没有大括号的代码更具可读性。
总之:不要写不必要的代码占用屏幕空间。
我正在编写的代码库中散布着一些病态的厌恶的代码,对于后来出现的人来说,它确实可以对可维护性做出改变。
我遇到的最常见的问题是:
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语句时,如果我不小心,我可以很容易地结束这个:
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秒钟的时间来添加花括号,并且可以在最less的几分钟debugging时间内为您节省大量的时间,为什么不去select减less歧义的选项呢? 对我来说似乎是虚假的经济。
我的2c:
使用缩进
明显
永远不要依赖运算符优先级 – 总是使用括号
我不会用“永远”和“永远”这个词,但总的来说,我认为这个规则是有用的,在某些语言(Lisp,Smalltalk)中,这是一个没有问题的问题。
始终使用{}块 – 即使是单行
我从来没有这样做,从来没有一个单一的问题,但我可以看到它是如何对学生好,尤其是, 如果他们之前学过Python。
比较左侧的Const对象
尤达条件? 不谢谢。 它伤害可读性。 编译代码时只需使用最大的警告级别。
对于> = 0的variables使用无符号的
好。 有趣的是,我听说Stroustrup不同意。
删除后将指针设置为NULL – 双删除保护
不好的build议! 永远不要有指向已删除或不存在的对象的指针。
它更直观,易于理解。 它意图清楚。
而且它可以确保当新用户在添加新的代码语句的同时不知不觉地错过{
, }
时,代码不会中断。
为了增加上述非常明智的build议,我在重构某些代码时遇到的一个示例如下:我正在修改一个非常大的代码库,以便从一个API切换到另一个。 第一个API有一个呼叫来设置公司ID如下:
setCompIds( const std::string& compId, const std::string& compSubId );
而更换需要两个电话:
setCompId( const std::string& compId ); setCompSubId( const std::string& compSubId );
我开始使用非常成功的正则expression式来改变它。 我们也通过astyle来传递代码,这使得它更具可读性。 然后,在审查过程中,我发现在一些有条件的情况下,它正在改变这一点:
if ( condition ) setCompIds( compId, compSubId );
对此:
if ( condition ) setCompId( compId ); setCompSubId( compSubId );
这显然不是什么要求。 我不得不回到起点,通过在一个块内完全处理replace,然后手动改变任何最终看起来很愚蠢的东西(至less这不会是错误的)。
我注意到astyle现在有了--add-brackets
选项,它允许你在没有的情况下添加括号,如果你发现自己和我在同一个位置,我强烈推荐这个选项。
除了less数情况外,我在其他地方都使用{}
。 单行是其中的一种情况:
if(condition) return; // OK if(condition) // return; // and this is not a one-liner
在返回之前添加某种方法可能会伤害到您。 缩进表示当条件满足时返回正在执行,但总是返回。
在C#中使用statment的其他例子
using (D d = new D()) // OK using (C c = new C(d)) { c.UseLimitedResource(); }
相当于
using (D d = new D()) { using (C c = new C(d)) { c.UseLimitedResource(); } }
我能想到的最相关的例子是:
if(someCondition) if(someOtherCondition) DoSomething(); else DoSomethingElse();
if
else
配对呢? 缩进意味着外部if
得到else
,但实际上编译器不会看到它; 内部 if
会得到else
,而外部if
不。 你必须知道这个(或者在debugging模式中看到它的行为),通过检查为什么这个代码可能不符合你的期望。 如果你知道Python,会变得更加困惑。 在这种情况下,您知道缩进定义了代码块,因此您可以期望它根据缩进进行评估。 然而,C#并没有给出关于空白的飞行翻转。
那就是说,我不是特别同意这个“总是使用括号”的规则。 它使得代码在垂直方向噪声很大,降低了快速读取代码的能力。 如果声明是:
if(someCondition) DoSomething();
那么它应该像这样写。 声明“总是使用括号”听起来像“总是用圆括号围绕math运算”。 这会把非常简单的语句a * b + c / d
转化为((a * b) + (c / d))
,引入错误的可能性(许多编码器的祸害) ? 操作的顺序是众所周知的,并得到很好的执行,所以括号是多余的。 您只能使用圆括号来执行不同于通常应用的操作顺序: a * (b+c) / d
例如。 块大括号是相似的; 用它们来定义你想要做什么,如果它不同于默认,并不是“明显的”(主观的,但通常很常识)。
通过回答来看,没有人明确说明我习惯的做法,讲述你的代码的故事:
int j = 0; for (int i = 0 ; i < 100 ; ++i) { if (i % 2 == 0) { j++; } }
变为:
int j = 0; for (int i = 0 ; i < 100 ; ++i) { if (i % 2 == 0) j++; }
将j++
放在与 if应该向其他人发送信号的同一行上 , “我只希望这个块永远增加j
” 。 如果线条尽可能简单,那么coursethis就是有价值的,因为像这里所说的那样,在这里放置一个断点不是很有用。
事实上,我只是运行了部分Twitter风暴API,这是java中的这种“sorting”代码,下面是该幻灯片的第43页上的执行代码的相关代码片段:
... Integer Count = counts.get(word); if (Count=null) count=0; count++ ...
for循环块有两个东西,所以我不会内联该代码。 即永不 :
int j = 0; for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;
这是可怕的,我甚至不知道它是否有效(如预期); 不要这样做 。 新的线条和大括号有助于区分单独的但相关的代码片段,就像散文中的逗号或分号一样。 上面的块是一个非常长的句子,有几个子句和其他一些从来没有中断或暂停区分单独的部分的陈述。
如果你真的想传达给别人,这是一个单行的工作使用三元操作符或?:
forms:
for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;
但是,这是代码高尔夫,我认为不是很好的做法(我不知道是否应该把j ++放在一边)。 注意我以前没有在C ++中运行三元运算符,我不知道这是否有效, 但它确实存在 。
简而言之:
想象你的读者(即维护代码的人)如何解释你的故事(代码)。 尽可能让他们清楚。 如果你知道新手编码器/学生正在维持这一点,甚至可能尽可能多地离开,这样他们就不会感到困惑。
通过明确定义循环和条件块的范围,它使得代码更易读。 它也可以避免意外错误。
wrt 6:更安全是因为删除空指针是一个空操作。 所以如果你碰巧意外地经过了两次这样的path,你将不会导致内存损坏,释放内存,这个内存是免费的或者已经被分配给别的东西。
This is most of an issue with static file scope objects and singletons that have not very clear lifetimes and have been known to get recreated after they've been destroyed.
In most cases, you can avoid the need for this by using auto_ptrs
I like Luchian's accepted answer, in fact I learned the hard way that he is right, so I do always use braces, even for single-line blocks. However, personally I make an exception when writing a filter, as you are in your example. 这个:
int j = 0; for (int i = 0 ; i < 100 ; ++i) { if (i % 2 == 0) { j++; } }
looks cluttered to me. It separates the for loop and the if statement into separate actions, when really your intent is a single action: to count all of the integers divisible by 2. In a more expressive language, this could be written something like:
j = [1..100].filter(_%2 == 0).Count
In languages which lack closures, the filter cannot be expressed in a single statement, but must be a for loop followed by an if statement. However, it is still one action in the mind of the programmer, and I believe that should be reflected in the code, like so:
int j = 0; for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) { j++; }
One option for helping to prevent the errors that have been described above is to inline what you want to happen when you don't use braces. It makes it much harder to not notice the errors when you try to modify the code.
if (condition) doSomething(); else doSomethingElse(); if (condition) doSomething(); doSomething2(); // Looks pretty obviously wrong else // doSomethingElse(); also looks pretty obviously wrong
Because when you have two statements without {}
, it's easy to miss an issue. Let's assume that the code looks like this.
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;
它看起来很好。 The issue with it is really easy to miss, especially when the function containing the code is way larger. The issue is that goto fail
is ran unconditionally. You can easily imagine how frustrating this is (making you ask why last hash_update
always fails, after all everything looks fine in hash_update
function).
However, that doesn't mean I'm for adding {}
everywhere (in my opinion, seeing {}
everywhere is annoying). While it can cause issues, it never did for my own projects, as my personal coding style forbids conditionals without {}
when they aren't on the same line (yes, I agree that my coding style is unconventional, but I like it, and I use project's code style when contributing to other projects). This makes the following code fine.
if (something) goto fail;
But not the following one.
if (something) goto fail;
If you are a compiler, it doesn't make any difference. 两者都是一样的。
But for programmers, the first one is more clear, easy to read and less error-prone.
Another example of adding curly braces. Once I was searching for a bug and found such code:
void SomeSimpleEventHandler() { SomeStatementAtTheBeginningNumber1; if (conditionX) SomeRegularStatement; SomeStatementAtTheBeginningNumber2; SomeStatementAtTheBeginningNumber3; if (!SomeConditionIsMet()) return; OtherwiseSomeAdditionalStatement1; OtherwiseSomeAdditionalStatement2; OtherwiseSomeAdditionalStatement3; }
If you read the method line-by-line you will notice that there is a condition in the method that returns if it's not true. But actually it looks like 100 other simple event handlers that set some variables based on some conditions. And one day the Fast Coder comes in and adds additional variable setting statement at the end of the method:
{ ... OtherwiseSomeAdditionalStatement3; SetAnotherVariableUnconditionnaly; }
As a result the SetAnotherVariableUnconditionnaly is executed when the SomeConditionIsMet(), but the fast guy didn't notice it because all lines are almost similar in size and even when the return condition is vertically indented it is not-so noticeable.
If the conditional return is formatted like this:
if (!SomeConditionIsMet()) { return; }
it is much noticeable and the Fast Coder will find it at a glance.
I consider the first one to be clear then second. It gives the feeling of closing instructions, with little code is fine when code gets complex {...}
helps a lot even if it is endif
or begin...end
//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++;
It is best to set the pointer to NULL when you have finished with it.
Here is an example why:
Class A does the following:
- Allocates a block of memory
- Then some time later, it delete this block of memory but does not set the pointer to NULL
Class B does the following
- Allocates memory (and in this instance it happens to be given the same memory block that was deleted by class A.)
At this point both Class A and Class B have pointers pointing to the same memory block, as far as Class A is concerned this block of memory does not exists because it is finished with it.
Consider the following problem:
What if there was a logic error in Class A which resulted in it writing to memory that now belongs to Class B?
In this particular instance, you will not get an bad access exception error because the memory address is legal, all the while class A is now effectively corrupting class B data.
Class B may eventually crash if it encounters unexpected values and when it does crash, chances are, you will spend quite a long time hunting this bug in class B when the problem is in class A.
If you had set the deleted memory pointer to NULL, you would have gotten an exception error as soon as any logic errors in Class A tried to write to NULL pointer.
If you are worried about the logic error with double delete when pointers are NULL for the second time, then add assert for this.
Also: If you are going to down vote, please explain.
Always having curly braces is very simple and robust rule. However the code may look inelegant when there are lot of braces. If the rules allow to omit curly braces then there should be more detailed style rules and more sophisticated tools. Otherwise it may easily result with chaotic and confusing (not elegant) code. Therefore looking single style rule separate from rest of style guides and tools used is likely fruitless. I will just bring some important details about that rule #3 that haven't even been mentioned in other answers.
First interesting detail is that most proponents of that rule agree to violate it on case of else
. In other words they do not demand in review such code:
// pedantic rule #3 if ( command == Eat ) { eat(); } else { if ( command == Sleep ) { sleep(); } else { if ( command == Drink ) { drink(); } else { complain_about_unknown_command(); } } }
Instead, if they see it they may even suggest to write it like that:
// 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(); }
That is technically violation of that rule since there are no curly brackets between else
and if
. Such duality of the rule surfaces when to try to apply it to code base automatically with a mindless tool. Indeed, why to argue, just let a tool to apply style automatically.
Second detail (that is also often forgotten by proponents of that rule) is that the errors that may happen are never only because of violations of that rule #3. Actually those almost always involve violations of rule #1 too (that no one argues with). Again from viewpoint of automatic tools, it is not hard to make a tool that immediately complains when rule #1 is violated and so most of the errors can be caught timely.
Third detail (that is often forgotten by opponents of that rule) is the confusing nature of empty statement that is represented by single semicolon. Most developers with some experience became confused sooner or later by sole misplaced semicolon or by empty statement that is written using sole semicolon. Two curly braces instead of single semicolon are visually way easier to spot.
I have to admit that not always use {}
for single line, but it's a good practise.
-
Lets say you write a code without brackets that looks like this:
for (int i = 0; i < 100; ++i) for (int j = 0; j < 100; ++j) DoSingleStuff();
And after some time you want to add in j
loop some other stuff and you just do that by alignment and forget to add brackets.
-
Memory dealocation is faster. Lets say you have big scope and create big arrays inside (without
new
so they are in stack). Those arrays are removing from memory just after you leave scope. But it is possible that you use that array in one place and it will be in stack for a while and be some kind of rubbish. As a stack have limited and quite small size it is possible to exceed stack size. So in some cases it is better to write{}
to prevent from that. NOTE this is not for single line but for such a situations:if (…) { //SomeStuff… {//we have no if, while, etc. //SomeOtherStuff } //SomeMoreStuff }
-
Third way to use it is similar with second. It just not to make stack cleaner but to open some functions. If you use
mutex
in long functions usually it is better to lock and unlock just before accessing data and just after finishing reading/writing that. NOTE this way is using if you have some your ownclass
orstruct
withconstructor
anddestructor
to lock memory. -
What is more:
if (…) if (…) SomeStuff(); else SomeOtherStuff(); //goes to the second if, but alligment shows it is on first…
All In All, I cannot say, what is the best way to always use {}
for a single line but it is nothing bad to do that.
IMPORTANT EDIT If you write compiling code brackets for a single line does nothing, but if your code will be interpretated it slowes code for very very slightly. Very slightly.
There are a number of possible ways of writing control statements; certain combinations of them may co-exist without impairing legibility, but other combinations will cause trouble. 样式
if (condition) statement;
will co-exist comfortably with some of the other ways of writing control statements, but not so well with others. If multi-line controlled statements are written as:
if (condition) { statement; statement; }
then it will be visually obvious which if
statements control a single line and which ones control multiple lines. If, however, multi-line if
statements are written as:
if (condition) { statement; statement; }
then the likelihood of someone trying to extend a single-statement if
constructs without adding the necessary braces may be much higher.
The single-statement-on-next line if
statement may also be problematic if the codebase makes significant use of the form
if (condition) statement;
My own preference is that having the statement on its own line generally enhances legibility except in cases where there are many if
statements with similar control blocks, eg
if (x1 > xmax) x1 = xmax; if (x1 < xmin) x1 = xmin; if (x2 > xmax) x2 = xmax; if (x2 < xmin) x2 = xmin; etc.
in which case I will generally precede and follow such groups of if
statements with a blank line to visually separate them from other code. Having a range of statements that all start with if
at the same indentation will then provide a clear visual indication that there's something unusual.