web-dev-qa-db-ja.com

「do ... while」と「while」

可能性のある重複:
While vs. Do While
whileループの代わりにdo-whileを使用する必要があるのはいつですか?

私は今しばらくプログラミングを行っており(2年の仕事+ 4.5年の学位+ 1年の大学入学前)、プログラミング入門コースで強制されることのないdo-whileループを使用したことはありません。こんなに根本的なことに出会わなかったら、プログラミングを間違っていると感じるようになりました。

正しい状況に陥っていないということでしょうか?

しばらくの代わりにdo-whileを使用する必要があるいくつかの例は何ですか?

(私の学校教育はほぼすべてC/C++で、私の仕事はC#であるため、do-whileの動作が異なるため絶対に意味のある別の言語がある場合、これらの質問は実際には当てはまりません。)

明確にするために...私はwhiledo-whileの違いを知っています。終了条件を確認してからタスクを実行します。 do-whileはタスクを実行し、終了条件を確認します。

75
mphair

ループを少なくとも1回実行したい場合。それは一般的ではありませんが、私は時々それを使用します。あなたがそれを使用したいかもしれない1つのケースは、再試行を必要とするかもしれないリソースにアクセスしようとしています。

do
{
   try to access resource...
   put up message box with retry option

} while (user says retry);
99
Bob Moore

コンパイラが最適化の能力を持たない場合、do-whileの方が適しています。条件付きジャンプと無条件ジャンプがあるforおよびwhileとは対照的に、do-whileには単一の条件付きジャンプしかありません。パイプライン化され、分岐予測を行わないCPUの場合、これはタイトループのパフォーマンスに大きな違いをもたらす可能性があります。

また、ほとんどのコンパイラーはこの最適化を実行するのに十分スマートなので、逆コンパイルされたコードで見つかったすべてのループは通常do-whileになります(逆コンパイラーが逆方向のローカルgotoからループを再構築することさえ気にする場合)。

60
Ben Voigt

Do whileは、何かを少なくとも1回実行する場合に役立ちます。 do while対whileを使用する良い例として、次のことをしたいとしましょう:電卓。

ループを使用して、各計算の後にプログラムを終了するかどうかを確認することで、これにアプローチできます。おそらく、プログラムが開かれたら、その人は少なくとも一度はこれをやりたいと思うので、次のことができると思います。

do
{
    //do calculator logic here
    //Prompt user for continue here
} while(cont==true);//cont is short for continue
11
thyrgle

TryDeleteDirectory関数でこれを使用しました。こんな感じでした

do
{
    try
    {
        DisableReadOnly(directory);
        directory.Delete(true);
    }
    catch (Exception)
    {
        retryDeleteDirectoryCount++;
    }
} while (Directory.Exists(fullPath) && retryDeleteDirectoryCount < 4);
11
Chandam

これは一種の間接的な答えですが、この質問により、その背後にある論理について考えるようになり、共有する価値があると思いました。

誰もが言っているように、少なくとも一度は本体を実行したいときは_do ... while_ループを使用します。しかし、どのような状況でそれを行いたいですか?

まあ、私が考えることができる最も明白な状況のクラスは、チェック条件の初期(「プライミングされていない」)値が終了するときと同じときです。つまり、ループ本体を一度実行して、条件を非存在値にプライミングし、その条件に基づいて実際の繰り返しを実行する必要があります。プログラマーがとても怠けているので、誰かがこれを制御構造にまとめることにしました。

そのため、たとえば、タイムアウトを使用してシリアルポートから文字を読み取るには、次の形式を使用できます(Pythonの場合)。

_response_buffer = []
char_read = port.read(1)

while char_read:
    response_buffer.append(char_read)
    char_read = port.read(1)

# When there's nothing to read after 1s, there is no more data

response = ''.join(response_buffer)
_

コードの重複に注意してください:char_read = port.read(1)。 Pythonに_do ... while_ループがあった場合、私は使用したかもしれません:

_do:
    char_read = port.read(1)
    response_buffer.append(char_read)
while char_read
_

ループの新しいスコープを作成する言語に追加される利点:_char_read_は、関数の名前空間を汚染しません。しかし、これを行うためのより良い方法があることにも注意してください。それはPythonのNone値を使用することです:

_response_buffer = []
char_read = None

while char_read != '':
    char_read = port.read(1)
    response_buffer.append(char_read)

response = ''.join(response_buffer)
_

だからここに私のポイントの核心があります:null許容型の言語では、_initial_value == exit_value_の状況ははるかに少ない頻度で発生し、 that があなたがそれに出会わない理由かもしれません。関数が有効な状態を示すためにNoneを返す場合がまだあるので、決して起こらないとは言っていません。しかし、私が急いで簡単に考えた意見では、これは、使用する言語が意味する値を許可していなかった場合にさらに起こります。この変数はまだ初期化されていません。

これは完全な推論ではありません。実際には、ヌル値が一般的になった今、変数が取り得る有効な値のセットのもう1つの要素を形成するだけです。しかし実際には、プログラマーは、ループ終了状態を含む可能性のある賢明な状態にある変数と、初期化されていない状態にある変数を区別する方法を持っています。

10
detly

私は学校にいたときにかなり使いましたが、それ以降はあまり使いませんでした。

理論的には、終了条件チェックの前にループ本体を1回実行する場合に便利です。問題は、最初にチェックを行いたくない少数のインスタンスについては、通常、最後ではなくループ本体の終了チェックが必要ですです。その場合、よく知られているfor (;;)を本文のどこかにif (condition) exit;とともに使用することを好みます。

実際、ループの終了条件が少し不安定な場合、必要に応じてexitステートメントを使用してfor (;;) {}としてループを書き始めると便利な場合があります。初期化、終了条件、および/または増分コードをforの括弧内に移動することで「クリーンアップ」できるかどうかを確認できます。

7
T.E.D.

do whileは、コードブロックを少なくとも1回実行する場合です。一方、whileは、指定された基準に応じて常に実行されるとは限りません。

6
spinon

コードの一部を常に1回実行する必要があり、その結果に応じて、おそらくそれ以上の回数実行する必要がある状況。同じことは、通常のwhileループでも生成できます。

rc = get_something();
while (rc == wrong_stuff)
{
    rc = get_something();
}

do
{
    rc = get_something();
}
while (rc == wrong_stuff);
6
Edward Leno

それはそれと同じくらい簡単です:

事前条件と事後条件

  • while(cond){...}-前提条件。チェック後にのみコードを実行します。
  • {...} while(cond)-事後条件、コードは少なくとも1回実行されます。

秘密を知ったので..それらを賢く使用してください:)

5
Ioan Paul Pirau

この質問には適切に回答されているようですが、この非常に具体的なユースケースシナリオを追加したいと思います。 do ... whileをもっと頻繁に使い始めるかもしれません。

do
{
   ...
} while (0)

多くの場合、複数行の#definesに使用されます。例えば:

#define compute_values     \
   area = pi * r * r;      \
   volume = area * h

これは次の場合にうまく機能します。

r = 4;
h = 3;
compute_values;

-しかし-のための落とし穴があります:

if (shape == circle)  compute_values;

これは次のように展開されます。

if (shape == circle) area = pi *r * r;
volume = area * h;

Do ... while(0)ループでラップすると、適切に単一ブロックに展開されます。

if (shape == circle)
  do
  {
    area = pi * r * r;
    volume = area * h;
  } while (0);
4
Brandon Horsley

同じ構造を複数回読み取るリーダーに使用しました。

using(IDataReader reader = connection.ExecuteReader())
{
    do
    {
        while(reader.Read())
        {
            //Read record
        }
    } while(reader.NextResult());
}
2
Gordon Tucker

これまでの回答は、do-whileの一般的な使用法をまとめたものです。しかし、OPは例を求めたので、ここに1つあります:ユーザー入力を取得します。しかし、ユーザーの入力は無効である可能性があります。そのため、入力を要求し、検証し、有効であれば続行し、そうでなければ繰り返します。

Do-whileでは、入力が無効な間に入力を取得します。通常のwhileループでは、入力を1回取得しますが、無効な場合は、有効になるまで何度も入力を取得します。ループの本体がより複雑になった場合、前者の方が短く、エレガントで、保守しやすいことを確認するのは難しくありません。

2
user395760

これは私の個人的な意見ですが、この質問は経験に根ざした答えを求めています:

  • 私は36年間Cでプログラミングしてきましたが、通常のコードでdo/whileループを使用することはありません。

  • このコンストラクトの唯一の説得力のある使用法は、do { multiple statements } while (0)を介して複数のステートメントを単一のステートメントにラップできるマクロでの使用です。

  • 偽のエラー検出または冗長な関数呼び出しを伴うdo/whileループの無数の例を見てきました。

  • この観察に対する私の説明は、プログラマがdo/whileループの観点から考えると問題を誤ってモデル化する傾向があるということです。それらは重要な終了条件を見逃すか、最後まで移動する初期条件の可能性のある失敗を逃します。

これらの理由から、do/whileループがあるところにバグがあると信じるようになりました、そして私は定期的に初心者プログラマーに、近くのバグを見つけられないdo/whileループを見せるように挑戦しています。

このタイプのループは簡単に回避できます。for (;;) { ... }を使用し、必要に応じて必要な終了テストを追加します。このようなテストが複数必要になることは非常に一般的です。

古典的な例を次に示します。

_/* skip the line */
do {
    c = getc(fp);
} while (c != '\n');
_

ファイルが改行で終わらない場合、これは失敗します。このようなファイルの簡単な例は、空のファイルです。

より良いバージョンはこれです:

_int c;  // another classic bug is to define c as char.
while ((c = getc(fp)) != EOF && c != '\n')
    continue;
_

または、このバージョンはc変数も非表示にします。

_for (;;) {
    int c = getc(fp);
    if (c == EOF || c == '\n')
        break;
}
_

任意の検索エンジンでwhile (c != '\n');を検索してみてください。次のようなバグが見つかります(2017年6月24日取得):

ftp://ftp.dante.de/tex-archive/biblio/tib/src/streams.c では、関数getword(stream,p,ignore)にはdoがあります/ whileおよび少なくとも2つのバグを確認してください:

  • ccharとして定義され、
  • 潜在的な無限ループがありますwhile (c!='\n') c=getc(stream);

結論:do/whileループを避け、バグを見つけたら探してください。

1
chqrlie

私は約12年のプログラミングをしていますが、わずか3か月前に、do-whileを使用するのが本当に便利な状況に遭遇しました。だから、あなたの大きな時間は先だと思います:)。

1
Narek

ここに、私を含むほとんどの人がwhile(){}ループをdo {} while()よりも好む理由を説明します。真実ではない。 whileループはある意味で「より一般的」です。また、プログラマーは、把握しやすいパターンが好きです。 whileループは、開始時にその不変式が何であるかを示し、これは素晴らしいことです。

「より一般的な」ことについて私が意味することは次のとおりです。このdo..whileループを使用します。

do {
 A;
 if (condition) INV=false; 
 B;
} while(INV);

これをwhileループに変換するのは簡単です:

INV=true;
while(INV) {
 A;
 if (condition) INV=false; 
 B;
}

ここで、モデルwhileループを使用します。

while(INV) {
     A;
     if (condition) INV=false; 
     B;
}

そして、これをdo..whileループに変換すると、この怪物が発生します。

if (INV) {
 do
 {
         A;
         if (condition) INV=false; 
         B;

 } while(INV)
}

これで、反対側に2つのチェックがあり、不変式が変更された場合、2箇所で更新する必要があります。ある意味、do..whileは、標準のドライバーが必要なすべてを行うため、決して使用しないツールボックス内の専用ドライバーのようなものです。

1

do...whileループを使用せずにこれほど長く行ったことを想像することはできません。

現在別のモニターに1つあり、そのプログラムには複数のそのようなループがあります。それらはすべて次の形式です。

do
{
    GetProspectiveResult();
}
while (!ProspectIsGood());
1
Loren Pechtel

do/whileループを使用する最も一般的なシナリオは、入力に基づいて実行され、ユーザーが好きな回数だけ繰り返す小さなコンソールプログラムです。明らかに、コンソールプログラムが何回も実行されても意味がありません。しかし、最初はユーザー次第です。したがって、doの代わりにwhile/whileを使用します。

これにより、ユーザーは必要に応じてさまざまな入力を試すことができます。

do
{
   int input = GetInt("Enter any integer");
   // Do something with input.
}
while (GetBool("Go again?"));

私は、ソフトウェア開発者が最近do/whileを使用する機会が少なくなっているのではないかと疑っています。コンソールアプリでは、出力を継続的に更新して指示を提供したり、ユーザーに新しい情報を要求したりする必要があるため、より理にかなっています。対照的に、GUIを使用すると、ユーザーにその情報を提供するテキストはフォーム上に配置でき、プログラムで繰り返す必要はありません。

0
Dan Tao

Utf-8文字列の次の文字位置を返す関数で使用しました。

char *next_utf8_character(const char *txt)
{
    if (!txt || *txt == '\0')
        return txt;

    do {
        txt++;
    } while (((signed char) *txt) < 0 && (((unsigned char) *txt) & 0xc0) == 0xc0)

    return (char *)txt;
}

この関数は心から書かれており、テストされていないことに注意してください。ポイントは、とにかく最初のステップを実行する必要があり、条件を評価する前にそれを実行する必要があるということです。

0
quinmars

whileループは、ループの前に条件をチェックします、do...whileループは、ループ後の状態をチェックします。これは、ループの実行による副作用に基づいて条件を作成したい場合や、ループを少なくとも1回実行したい場合に、他のポスターが言ったように便利です。

私はあなたがどこから来たのか理解していますが、do-whileはほとんど使用されないものであり、私は自分自身を使用したことはありません。あなたは間違っていません。

あなたは間違っていません。これは、byteプリミティブを使用したことがないため、誰かが間違っていると言っているようなものです。それは一般的に使用されているだけではありません。

0
Rafe Kettler

ファイルを読み込むときは、do-whileループを常に使用します。ヘッダーにコメントを含む多くのテキストファイルを使用しています。

# some comments
# some more comments
column1 column2
  1.234   5.678
  9.012   3.456
    ...     ...

do-whileループを使用して「column1 column2」行まで読み取り、目的の列を検索できるようにします。擬似コードは次のとおりです。

do {
    line = read_line();
} while ( line[0] == '#');
/* parse line */

次に、whileループを実行して、ファイルの残りの部分を読み取ります。

0
flies

ファイルの先頭でセンチネル値を読み取るときに_do while_を使用しましたが、それ以外は、この構造があまり一般的に使用されていないことは異常ではないと思います--do-while_ sは本当に状況依存です。

_-- file --
5
Joe
Bob
Jake
Sarah
Sue

-- code --
int MAX;
int count = 0;
do {
MAX = a.readLine();
k[count] = a.readLine();
count++;
} while(count <= MAX)
_
0
danyim

Do-whileでは、あらゆる種類のコンソール入力が適切に機能します。これは、最初にプロンプ​​トを表示し、入力検証が失敗するたびに再プロンプトを表示するためです。

0
Lotus Notes

ギーザープログラマーである私の学校のプログラミングプロジェクトの多くは、テキストメニュー駆動の対話を使用していました。ほぼすべてが、メインプロシージャに次のロジックのようなものを使用しました。

do
    display options
    get choice
    perform action appropriate to choice
while choice is something other than exit

学生時代から、whileループをより頻繁に使用することがわかりました。

0
GreenMatt

私が見たアプリケーションの1つは、結果セットを見るとOracleにあります。

結果セットを取得したら、最初にそれからフェッチし(do)、その時点から..フェッチが要素を返すかどうかを確認します(要素が見つかったとき)。同じことが他の「 「フェッチのような」実装。

0

サーバー/コンシューマーでは非常に一般的な構造です。

_DOWHILE (no shutdown requested)
   determine timeout
   wait for work(timeout)
   IF (there is work)
      REPEAT
          process
      UNTIL(wait for work(0 timeout) indicates no work)
      do what is supposed to be done at end of busy period.
   ENDIF
ENDDO
_

REPEAT UNTIL(cond)do {...} while(!cond)である

場合によっては、work(0)の待機はCPUの方が安くなる場合があります(タイムアウト計算をなくすことで、到着率が非常に高くなる場合があります)。さらに、多くキューイング理論の結果があり、忙しい期間に配信される数を重要な統計値にします。 (たとえば、Kleinrock-Vol 1を参照してください。)

同様に:

_DOWHILE (no shutdown requested)
   determine timeout
   wait for work(timeout)
   IF (there is work)
      set throttle
      REPEAT
          process
      UNTIL(--throttle<0 **OR** wait for work(0 timeout) indicates no work)
   ENDIF
   check for and do other (perhaps polled) work.
ENDDO
_

ここで、_check for and do other work_は、メインループや、効率的なwaitany(waitcontrol*,n)型操作をサポートしないカーネル、または優先順位付けされたキューが他の作業を抑制してスロットルする可能性のあるカーネルに入れるのに莫大な費用がかかる場合があります飢star制御として使用されます。

このタイプのバランス調整は、ハッキングのように思えるかもしれませんが、必要な場合があります。スレッドプールのブラインド使用は、管理者スレッドではなくスレッドプールの使用がスレッドセーフな実装を必要とするため、高更新レートの複雑なデータ構造のためにプライベートキューで管理者スレッドを使用することのパフォーマンス上の利点を完全に無効にします。

擬似コード(たとえば、シャットダウン要求をUNTILでテストする必要があるかどうか)や、管理者スレッド対スレッドプールについて議論したくありません-これは、特定のユースケースのフレーバーを提供するためのものです。制御フロー構造。

0
pgast