Bjarne Stroustrup(C++作成者)はかつて、「do/while」ループを回避し、代わりに「while」ループの観点からコードを記述することを好むと述べました。 [以下の引用を参照してください。]
これを聞いて以来、私はこれが真実であることがわかりました。あなたの考えは何ですか?代わりに「while」を使用した場合よりも「do/while」がはるかにクリーンで理解しやすい例はありますか?
いくつかの答えに答えて:はい、「do/while」と「while」の技術的な違いを理解しています。これは、ループを含むコードの可読性と構造化に関するより深い質問です。
別の方法を聞かせてください。「do/while」の使用を禁止されていると仮定します。「while」を使用して不潔なコードを書く以外に選択肢がない現実的な例はありますか?
「C++プログラミング言語」、6.3.3から:
私の経験では、DOステートメントはエラーと混乱の原因です。理由は、条件が評価される前に、本体が常に1回実行されるためです。しかし、体が正常に機能するためには、その状態に非常によく似たものが初めてでも保持されなければなりません。私が想像したよりも頻繁に、プログラムが最初に記述されてテストされたとき、またはその前のコードが変更された後に期待どおりに条件が保持されないことがわかりました。 私は「前もってそれを見ることができる」という条件を好む。その結果、私はdo文を避ける傾向がある。 -Bjarne
はい、do whileループをwhileループに書き換えることができることに同意しますが、whileループを常に使用する方がよいことに同意しません。常に少なくとも1回は実行されますが、これは非常に便利なプロパティです(最も一般的な例は(キーボードからの)入力チェックです)
#include <stdio.h>
int main() {
char c;
do {
printf("enter a number");
scanf("%c", &c);
} while (c < '0' || c > '9');
}
もちろん、これはwhileループに書き換えることができますが、これは通常、はるかにエレガントなソリューションと見なされます。
do-whileは、事後条件を持つループです。ループ本体を少なくとも1回実行する場合に必要です。これは、ループ条件を適切に評価する前に何らかのアクションが必要なコードに必要です。 whileループでは、2つのサイトから初期化コードを呼び出す必要があり、do-whileでは、1つのサイトからのみ呼び出すことができます。
別の例は、最初の反復を開始するときに有効なオブジェクトがすでにあるため、最初の反復が開始する前に何も実行したくない(ループ条件評価を含む)場合です。例として、FindFirstFile/FindNextFile Win32関数を使用します。エラーまたは最初のファイルの検索ハンドルを返すFindFirstFileを呼び出し、エラーが返されるまでFindNextFileを呼び出します。
擬似コード:
Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
do {
process( params ); //process found file
} while( ( handle = FindNextFile( params ) ) != Error ) );
}
do { ... } while (0)
は、マクロを適切に動作させるための重要な構成要素です。
実際のコードでは重要ではありませんが(必ずしも同意しません)、プリプロセッサのいくつかの欠陥を修正するために重要です。
編集:私は今日、自分のコードでdo/whileがずっときれいになる状況に遭遇しました。ペアの LL/SC 命令のクロスプラットフォーム抽象化を行っていました。これらは、次のようにループで使用する必要があります。
do
{
oldvalue = LL (address);
newvalue = oldvalue + 1;
} while (!SC (address, newvalue, oldvalue));
(専門家は、SC実装ではoldvalueが使用されていないことに気付くかもしれませんが、この抽象化をCASでエミュレートできるように含まれています。)
LLおよびSCは、do/whileが同等のwhileフォームよりもかなりクリーンな状況の優れた例です。
oldvalue = LL (address);
newvalue = oldvalue + 1;
while (!SC (address, newvalue, oldvalue))
{
oldvalue = LL (address);
newvalue = oldvalue + 1;
}
このため、Google Goには do-while構造を削除することを選択した があるという事実に非常に失望しています。
条件が満たされるまで何かを「実行」したい場合に便利です。
次のようにwhileループでファッジできます:
while(true)
{
// .... code .....
if(condition_satisfied)
break;
}
次の一般的なイディオムは、私にとって非常に簡単です。
do {
preliminary_work();
value = get_value();
} while (not_valid(value));
do
を回避するための書き換えは次のように思われます:
value = make_invalid_value();
while (not_valid(value)) {
preliminary_work();
value = get_value();
}
その最初の行は、テスト常にtrueと評価されるが初めてであることを確認するために使用されます。言い換えれば、テストは常に最初は不要です。この余分なテストがなければ、最初の割り当てを省略することもできます。このコードは、自分自身と戦う印象を与えます。
このような場合、do
コンストラクトは非常に便利なオプションです。
(両方の違いを知っていると仮定)
Do/Whileは、条件がチェックされ、whileループが実行される前にコードをブートストラップ/事前初期化するのに適しています。
コーディング規約で
したがって、do {} while(xx)
はほとんどありません。
int main() {
char c;
do {
printf("enter a number");
scanf("%c", &c);
} while (c < '0' || c > '9');
}
で書き換えられます:
int main() {
char c(0);
while (c < '0' || c > '9'); {
printf("enter a number");
scanf("%c", &c);
}
}
そして
Handle handle;
Params params;
if( ( handle = FindFirstFile( params ) ) != Error ) {
do {
process( params ); //process found file
} while( ( handle = FindNextFile( params ) ) != Error ) );
}
で書き換えられます:
Params params(xxx);
Handle handle = FindFirstFile( params );
while( handle!=Error ) {
process( params ); //process found file
handle = FindNextFile( params );
}
読みやすさがすべてです。
可読性の高いコードは、コード保守の頭痛を減らし、コラボレーションを改善します。
その他の考慮事項(最適化など)は、ほとんどの場合、それほど重要ではありません。
ここでコメントを得たので、詳しく説明します。do { ... } while()
を使用するコードスニペット[〜#〜] a [〜#〜]がある場合、 while() {...}
と同等の読みやすさ[〜#〜] b [〜#〜]、それから私は投票します[〜#〜] a [〜#〜]。希望する場合[〜#〜] b [〜#〜]、ループ状態が「前」にあるので、そしてより読みやすいと思う(したがって、保守可能など)-すぐに使用して、[〜#〜] b [〜#〜]。
私のポイントは、あなたの目(そして同僚)にとって読みやすいコードを使用することです。もちろん、選択は主観的です。
私の意見では個人的な選択にすぎません。
ほとんどの場合、do ... whileループをwhileループに書き換える方法を見つけることができます。しかし、必ずしも常にではありません。また、現在のコンテキストに合わせてdo whileループを作成する方が論理的な意味があります。
上を見ると、TimWからの返信は、それ自体を物語っています。私の意見では、Handleの2つ目は特に面倒です。
これは、私が見たdo-whileの最もクリーンな代替手段です。これは、Pythonにdo-whileループがない場合に推奨されるイディオムです。
1つの注意点は、<setup code>
にcontinue
を含めることはできないことです。ブレーク条件をジャンプしますが、do-whileの利点を示す例は条件の前に続行する必要がないためです。 。
while (true) {
<setup code>
if (!condition) break;
<loop body>
}
ここでは、上記のdo-whileループの最良の例のいくつかに適用されます。
while (true); {
printf("enter a number");
scanf("%c", &c);
if (!(c < '0' || c > '9')) break;
}
この次の例は、//get data
は通常は短いが//process data
部分は長くなる可能性があるため、条件が上部近くに保持されるため、do-whileよりも構造が読みやすい場合です。
while (true); {
// get data
if (data == null) break;
// process data
// process it some more
// have a lot of cases etc.
// wow, we're almost done.
// oops, just one more thing.
}
構造化プログラム定理 を読んでください。 do {} while()はいつでもwhile()do {}に書き換えられます。シーケンス、選択、反復がすべて必要です。
ループ本体に含まれているものは常にルーチンにカプセル化できるため、while()do {}を使用することの汚さは決して悪化する必要はありません。
LoopBody()
while(cond) {
LoopBody()
}
Do-whileループは、常にwhileループとして書き換えることができます。
Whileループのみを使用するか、while、do-while、およびforループ(またはそれらの組み合わせ)を使用するかは、作業するプロジェクトの美学と慣習に対する好みに大きく依存します。
個人的に、私はwhile-loopを好む。それは、ループ不変式についての推論を単純化するからだ。
Do-whileループが必要な状況があるかどうかについて:
do
{
loopBody();
} while (condition());
いつでもできる
loopBody();
while(condition())
{
loopBody();
}
そのため、何らかの理由でできない場合はdo-whileを使用する必要はありません。 (もちろん、この例はDRYに違反していますが、これは概念実証にすぎません。私の経験では、通常、do-whileループをwhileループに変換し、DRY =具体的なユースケースでは。)
「ローマにいるときは、ローマ人のように。」
ところで:あなたが探している引用は多分これです([1]、セクション6.3.3の最後の段落):
私の経験から、doステートメントはエラーと混乱の原因です。その理由は、条件がテストされる前に、本体が常に1回実行されるためです。ただし、身体が正しく機能するためには、最初の実行で最終状態と同様の状態を保持する必要があります。予想よりも頻繁に、これらの条件が正しくないことがわかりました。これは、問題のプログラムをゼロから作成してテストし、コードを変更した後の両方に当てはまりました。さらに、「前もって見ることができる」という条件を好みます。したがって、私はdo文を避ける傾向があります。
(注:これはドイツ語版の翻訳です。英語版をお持ちの場合は、引用を自由に編集して元の文言に一致させてください。残念ながら、Addison-WesleyはGoogleを嫌っています。)
[1] B。Stroustrup: C++プログラミング言語。第3版。アディソン・ウェスリー、レディング、1997年。
まず、do-while
はwhile
よりも読みにくいことに同意します。
しかし、私は非常に多くの答えをした後、言語にdo-while
さえ存在する理由を誰も考えなかったことに驚いています。その理由は効率です。
N
条件チェックを持つdo-while
ループがあり、条件の結果がループ本体に依存しているとしましょう。次に、それをwhile
ループに置き換えると、代わりにN+1
条件チェックが取得されますが、追加のチェックは無意味です。ループ条件に整数値のチェックしか含まれていない場合、それは大したことではありませんが、
something_t* x = NULL;
while( very_slowly_check_if_something_is_done(x) )
{
set_something(x);
}
ループの最初のラップでの関数呼び出しは冗長です。x
がまだ何にも設定されていないことは既にわかっています。では、なぜ無意味なオーバーヘッドコードを実行するのでしょうか?
条件内のコードが比較的遅い(組み込みの遅いハードウェア周辺機器からの応答をチェックする)リアルタイム組み込みシステムをコーディングするとき、私はこのまさに目的のためにdo-whileをしばしば使用します。
次の理由だけでそれらを使用することはほとんどありません。
ループは事後条件をチェックしますが、事後条件を処理しないように、ループ内でこの事後条件をチェックする必要があります。
サンプルの擬似コードを取得します。
do {
// get data
// process data
} while (data != null);
理論的には簡単に聞こえますが、現実の状況ではおそらく次のようになります。
do {
// get data
if (data != null) {
// process data
}
} while (data != null);
余分な「if」チェックは、それだけの価値はありません。 whileループの代わりにdo-whileを実行する方が簡潔な例はほとんどありません。 YMMV。
不明(google)からDan Olsonの回答への質問/コメントへの応答:
「do {...} while(0)は、マクロを適切に動作させるための重要な構成要素です。」
_#define M do { doOneThing(); doAnother(); } while (0)
...
if (query) M;
...
_
do { ... } while(0)
なしで何が起こるかわかりますか?常にdoAnother()を実行します。
do/ whileの私の問題は、厳密にCでの実装にあります。 whileキーワードの再利用により、間違いのように見えるため、人をつまずかせます。
whileがのみに予約されていた場合whileループとdo/ whileはdoに変更されました/ までまたは繰り返し/ ntil、ループ(確かに便利であり、いくつかのループをコーディングする「正しい」方法)がそれほど大きな問題を引き起こすとは思わない。
JavaScriptに関してこれについては以前に暴言しました 、これはCからもこの残念な選択を継承しました。
まあ、これはいくつかのステップに戻りますが、
do
{
output("enter a number");
int x = getInput();
//do stuff with input
}while(x != 0);
使用できるとは限りませんが、可能です
int x;
while(x = getInput())
{
//do stuff with input
}
0以外の数を使用してループを終了する場合
while((x = getInput()) != 4)
{
//do stuff with input
}
しかし、ここでも、読みやすさの低下があります。条件内で代入ステートメントを使用するのは悪い習慣であると考えられていることは言うまでもありません。それが最初の実行であるループに。
次のようなものを検討してください。
int SumOfString(char* s)
{
int res = 0;
do
{
res += *s;
++s;
} while (*s != '\0');
}
たまたま '\ 0'が0になっていますが、ポイントが得られることを願っています。
DavidBožjakの例が好きです。しかし、悪魔の擁護者を演じるために、少なくとも一度実行したいコードをいつでも別の関数に分解して、おそらく最も読みやすいソリューションを達成できると思います。例えば:
int main() {
char c;
do {
printf("enter a number");
scanf("%c", &c);
} while (c < '0' || c > '9');
}
これになる可能性があります:
int main() {
char c = askForCharacter();
while (c < '0' || c > '9') {
c = askForCharacter();
}
}
char askForCharacter() {
char c;
printf("enter a number");
scanf("%c", &c);
return c;
}
(不正な構文はご容赦ください。私はCプログラマーではありません)