こんにちは。田原です。

C++の文法上、文(Statements)として分岐やループ、各種宣言などなどが定義されています。ざっくり分類して7種類あります。C++プログラムを書く時はこれら7種類の文を駆使します。その中でも手続き(アルゴリズム)を記述するのに用いる文が実行文です。一番プログラムらしい部分です。今回は主にこの実行文について解説します。
非実行文である宣言文はC++のクラス定義等を行うものですので、実は仕様がたいへん大きいですから、今回は少しだけ触れて、今後徐々に解説していきます。

 

1.文の分類

文はC++の基本的な構成要素ですので、多数の種類があります。標準規格に沿っていますが、理解を助けるため多少独自に分類してます。
最後の「;で区切る」欄は、文の最後に ;(セミコロン)を書く必要があるかないかを示してます。時々迷うと思いますので参考にされて下さい。

種別 概要 ;で区切る
 実行文 式文 多くは代入ですが、関数呼び出しなどもよく書きます。 YES
ブロック 複数の文を{}で囲んだものです。 NO
選択文 条件分岐を行う文です。if文とswitch文があります。 NO
繰り返し文 繰り返し処理を行う文です。以下の文があります。
while文 NO
do文 YES
for文 NO
ジャンプ文 次の行以外へジャンプする文です。
break文、continue文、return文、goto文があります。
YES
 非実行文  ラベル文 goto文のジャンプ先とswitch文の分岐先を定義します。
後続の文が必要です。
NO
 宣言文  主に関数と変数と型を宣言したり定義したりします。 YES
特殊な実行文 変数を定義して初期化するものです。(初期化付きの宣言) YES

セミコロンのつけ忘れ
私も非常に良くやるのですが、セミコロンを付け忘れることがあります。(私だけ?)この時、付け忘れた行にエラーが出てくれればよいのですか、残念ながら次の(コメントでない)行がエラーになります。(時には「セミコロン無し」ではないエラーになることもあります。)

下記のようなコードを用意してみました。`int x`はセミコロンを付け忘れた文です。

int main()
{
    int x
    // this is a comment.
    x=10;
    return 0;
}

Visual C++でコンパイルすると次のエラーがでます。5行目は`x=10;’です。

semicolon.cpp(5): error C2146: 構文エラー: ';' が、識別子 'x' の前に必要です。

そして、gccでコンパイルすると次のエラーがでました。初期化忘れと言ってます。

semicolon.cpp: In function ‘int main()’:
semicolon.cpp:5:5: error: expected initializer before ‘x’
     x=10;
     ^

ちなみに、Visual C++とgccを比べると経験上gccの方がエラー・メッセージが分かりやすいことが多いです。今回はたまたま珍しいケースにあたったようです。

コンパイル・エラーを潰す時のオススメ
C++は構文が複雑なため、コンパイラから見ると間違った文の正しい姿は1つではなく、複数あることが多いです。その内のどれかが正しい姿だろうと想定してエラー・メッセージが組み立てられます。その想定が外れると、上記のように的確でないエラー・メッセージがでます。

そこで、エラーが出た場合、その行、もしくは、それより前の行にミスがあるので、エラー・メッセージを参考にしつつ、惑わされないように心がけながらエラーを探して下さい。(この辺もC++の難しさの所以と思います。)

また、エラーを潰して行く時は頭から潰すことがお薦めです。前のエラーが原因で他のエラーが出ることは非常に良くあります。ですので、たくさん出ているエラーの最後の方から潰していくとたいへん苦労します。複数出ているエラー・メッセージの最初の方から潰していくことをお勧めします。

 

2.実行文

実行文は関数の中だけに書くことができます。実行文を使って「処理の手順」を記述します。

2-1.式文

前回の2.演算子で説明したような演算子(オペレータ)と被演算子(オペランド)で構成される式を書いて最後に ;(セミコロン)を書いたものです。式を省略して ;だけを書いたものも式文です。これはnull文と呼ばれています。

多くの場合、i = 10;のように代入式ですが、代入式に限りません。foo(10);のように関数呼び出しでもよいし、’++i;’のようなインクリメント演算子とそのオペランドだけの時もあります。あまり使いませんが、複数の代入式を,(カンマ)で区切って1つの式文にすることもできます。例えば、i=10, j=5, x=20;などです。
なお、int i = 10;のようにint型変数iを定義して同時に10で初期化することができますが、これはint型変数iの宣言文です。式文ではありません。上記表の「特殊な実行文」に分類してみました。

2-2.ブロック(複合文)

{と}で囲まれた複数の文です。1つしか文を書けないところに複数の文を書きたい場合に使います。

またこれは、実践C++入門講座5回目 main()関数と基本の型の3.ローカル変数で説明したようにローカル変数の有効範囲も定義します。プログラムを開発する時、変数の有効範囲は必要な範囲だけに限定しておくと、変数がどこで使われているのか調べるのが簡単になりますから、よりメンテナンスしやすくなります。そのために使う場合もあります。

#include <iostream>
int main()
{
    int r;
    std::cout << "radius ? "; std::cin >> r;
    std::cout << "r=" << r << " area of circle=" << r*r*3.14 << "\n";
//     :
    return 0;
}

例えば、上記のようなプログラムの「:」の部分でrを使わない場合、{}ブロックを追加してその中でrを定義することも考えられます。やりすぎるとインデントが深くなって却って読みにくくなり良くないですが、変数の有効範囲を無闇に広くしないよう少し注意を払うと良いですよ。

#include <iostream>
int main()
{
    {
        int r;
        std::cout << "radius ? "; std::cin >> r;
        std::cout << "r=" << r << " area of circle=" << r*r*3.14 << "\n";
    }
//     :
    return 0;
}
2-3.選択文

条件分岐処理を行うのが選択文です。複数の処理から、条件に有ったものを1つ選択する文です。
if文とswitch文があります。

2-3-1.if文

下記の2種類の書き方ができます。

if (条件) 文1
if (条件) 文1 else 文2

「条件」には通常は「式」を書きます。その式は最終的にbool型(trueかfalse)へ変換され、trueなら「文1」、falseなら「文2」が実行されます。falseの時「文2」がなければ何もしません。
「式」を計算した結果が0の時はfalse、それ以外の時はtrueになります。

「条件」には「初期化付きの宣言」を書くこともできます。
例えば、下記です。

if (int ret=foo())
    std::cout << "true : ret=" << ret << "\n";
else
    std::cout << "false : ret=" << ret << "\n";

関数foo()が呼び出されて、その結果がint型変数retへ初期設定されます。
このretの値が0でないならtrueになりますので、「std::coutへの出力文」が実行されます。
この時、「条件」で定義された変数は、if文の終わりまで有効です。

if文を使う時の典型的なバグ
きっと全ての人が最低一回はやらかすと思います。
次のように書いてしまうことです。

if (i=3)
{
    i==3の時の処理;
}

if文の条件に i==3 と書くべきところを i=3 と書いてしまうバグです。
i=3の結果は3ですから、0でないのでtrueと判定されます。つまり、「i==3の時の処理;」を常に実行してしまいます。
最近のコンパイラは警告レベルを上げておけば警告してくれますので、警告に注意を払っていれば直ぐにバグを見つけることができます。ですので、警告レベルをできるだけ上げた上で、警告は全て対処して出ないようにすることをオススメします。

といいつつ、今までのCMakeLists.txtは警告レベルが低いままでした。
警告レベルをできるだけ上げたものは以下の通りです。

CMakeLists.txt : 警告レベルを上げたCMakeLists.txtです。下記のif_test.cpp用です。

project(if_test)

if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /EHsc")
else()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11")
endif()

add_executable(if_test if_test.cpp)

if_test.cpp : CMakeLists.txtテスト用のサンプル・ソースです。

#include <iostream>

int main()
{
    int i=0;
    if (i=3)
    {
        std::cout << "(1)i=" << i << "\n";
    }

    return 0;
}

if文を使う時のオススメ
上記で書いた「文1」、「文2」は常にブロックにしておくことをお勧めします。

・「文1」、「文2」を間違って複数の文にした時、ブロックにしてないとハマり易いです。

if (x != 0)
    std::cout << "x=" << x << "\n";
    x+=100;

もともとx+=100;しかなかったところに、デバッグのためstd::coutの文を追加すると、インデントのせいで一見xが0でない時だけx+=100;が実行されそうに見えますが、ここでは既にif文が終わっているので常に実行されてしまいます。
デバッグしててデバック文を入れたがために、更にバグを作りました。「マヌケ」なバグに見えますが、意外にやらかしてしまいます。そして、気がつかないことがあるので始末に負えないです。

・if文をネストした時もハマり易いです。

if (x != 0)
    if (y == 0)
        y=x;
else
    x=y;

は、インデントのせいでxが0の時xにyが代入されそうに見えますが、コンパイラは次のように処理します。

if (x != 0)
{
    if (y == 0)
        y=x;
    else
        x=y;
}

ですのでxとyが0でない時にxにyが代入されます。
全く動作が異なりますので、頭の痛いバグになります。それをデバッグしようとstd::coutへ出力して先述のバグが発生すると目も当てられません。
なので、少し冗長ですが、初心者の内はできるだけif文の文1、文2は{}で括っておくことをお勧めします。

2-3-2.switch文

if文を特化したものがswitch文です。式の値によって処理を振り分けるものです。

switch(式)
{
case 値1:
    式==値1の時に行う処理。複数の文を書ける。
    break;

case 値2:
    式==値2の時に行う処理。複数の文を書ける。
    break;

      :

default:
    式が全てのcaseの値に一致しなかった時に行う処理。複数の文を書ける。
    break;
}

break;に到達すると継続する行は実行されずにswitch文を抜けます。
caseラベル1つにbreak文1つという制約はありません。
下記のようにif文で条件判断することにより、特定の条件の時、早期にbreak;文を実行することもできます。

switch(式)
{
case 値1:
    if (条件)
    {
break;
    }
    なんらかの処理;
    break;
}

また、break;文を書かないこともできます。良く使われる記述は下記です。
これは便利ですが、break;の書き忘れにはくれぐれも注意が必要です。
caseラベル内に処理を書いた時は、break;を必ず書くくらいの勢いでコードを書いた方が安心です。
break;を書かない方が綺麗に処理を記述出来る時は、コメントでその旨、書くことをお勧めします。
時間が経って忘れてしまった後や他の人がデバッグする時などにbreak;の書き忘れを疑うとその分、無駄な時間を費やすことになりますので。

switch(式)
{
case 値1:
case 値2:
case 値3:
    式が値1, 値2, 値3のどれかの時に行う処理。
    break;

case 値4:
    式が値4の時に行う処理。
//  break; 継続してdefault処理も行いたいので、breakをコメントアウト

default:
    式が全てのcaseの値に一致しなかった時に行う処理。複数の文を書ける。
    break;
}

次に、defaultラベルは省略できます。省略した時、全てのcaseの値に一致しなかった時は、switch文をそのまま抜けます。何もしません。

最後に大事なことがあります。各値1, 値2, 値3, …は、定数式しか書けません。 時々勘違いされるのですが、変数を含む式を書いてもコンパイル・エラーになります。下記のような処理はコンパイル・エラーになります。

int x=10;
switch(y)
{
case x+1:
//  y==(x+1)の時の処理;
    break;

case x+2:
//  y==(x+2)の時の処理;
    break;
}

そのような処理を書きたい場合はif文を使います。

if (y == (x+1))
{
//  y==(x+1)の時の処理;
}
else if (y == (x+2))
{
//  y==(x+2)の時の処理;
}

なお、結果が定数になるなら式を書くこともできます。
「定数」とは、コンパイルする時に値を決定できるものです。なので、その式へ与えるパラメータやオペランドも全て定数であることが必要です。式の途中に変数が1つでも交じるとだめです。

サンプル・プログラム
下記のプログラムを解読して、動作を把握してみて下さい。そして、CMakeLists.txtを作りビルドして実行してみましょう。手順は第2回(Windows向け)第3回(linux向け)で解説していますので、参考にされて下さい。

#include <iostream>

void switch_demo(int value)
{
    switch(value)
    {
    case 0:
        std::cout << "Hello, World!!\n";

    case 1:
        std::cout << "Hello, C++!!\n";
        break;

    case 2:
        std::cout << "What's up ?\n";
        break;

    default:
        std::cout << "Oops! default. iValue=" << value << "\n";
    }
}

int main()
{
    while(true)
    {
        int value;
        std::cout << "value ? "; std::cin >> value;
        if (value < 0)
        {
    break;
        }
        switch_demo(value);
    }
    return 0;
}
2-4.繰り返し文とジャンプ文

繰り返し文は、繰り返し処理(ループ処理)を行う文です。
while文、do文、for文があります。

ジャンプ文は処理の流れを無条件に変更します。繰り返し処理の流れを変えたい時に用います。
break文、continue文、return文、goto文があります。

2-4-1.while文とbeak文、continue文

while文は下記のように書きます。

while(条件) 文

「条件」がtrueの間、「文」を繰り返し実行します。
「条件」にはif文と同じく「式」、もしくは、「初期化付きの宣言」を書くことができます。
「条件」で定義された変数は「文」の実行中有効です。「文」の開始直前に生成されて条件判定に用いられ、trueなら「文」の終了時、falseなら直ぐに破棄されます。

「文」は、{}で囲まれた複合文を書くことが多いです。稀に;だけの空文のこともあります。

break;でループから抜け出します。
continue;は文の残りをスキップします。

サンプル・プログラム
ビルドして実行できますので、できればデバッガでステップ実行して動作を追いかけてみて下さい。
while文やbreak文、continue文の動作が良く分かると思います。

#include <iostream>

int main()
{
    int i=10;

    // iが0でない間繰り返す
    while(i)
    {
        std::cout << "(1)i=" << i << "\n";
        --i;
    }
    std::cout << "(2)i=" << i << "\n";

    // iが10未満の間、繰り返す
    while(++i < 10)
        ;
    std::cout << "(3)i=" << i << "\n";

    // 無限ループだが、途中でbreakする
    while(true)
    {
        std::cout << "(4)i=" << i << "\n";
        if (i < 5)
        {
    break;
        }
        i--;
    }
    std::cout << "(5)i=" << i << "\n";

    // iが10未満の間繰り返す
    while(i < 10)
    {
        i++;

        // iが奇数ならスキップする
        if (i % 2)
        {
    continue;
        }
        std::cout << "(6)i=" << i << "\n";
    }
    std::cout << "(7)i=" << i << "\n";


    return 0;
}
2-4-2.do文

次のように書きます。

do 文 while(式);

まず、「文」を実行し、その後で「式」を計算します。結果がtrueの間繰り返し「文」を実行します。falseになったらdo文を終了します。
break文とcontinue文を書くことができます。これらの働きはwhile文の時と同じです。

do文は少しおもしろい性質を持ってます。
式がfalseの時、1回だけ文を実行します。それを利用して複数の条件を判定して処理をスキップするような構造を記述するのに便利です。

#include <iostream>

int main()
{
    int number;
    std::cout << "number ? ";
    std::cin >> number;

    do
    {
        if ((number % 2) == 0)
    break;

        if ((number % 3) == 0)
    break;

        std::cout << "number is not a multiple of 2 and 3.\n";
    }
    while(false);

    std::cout << "number=" << number << "\n";

    return 0;
}

更に、do文はブロックと違って最後に `;`を書けます。このような特徴を持つ構文は他にありません。この特徴を利用すると便利です。後日、プリプロセッサのマクロを解説する際に説明します。

2-4-3.for文

次のように書きます。

for(初期化; 条件; 式) 文

「初期化」は「式」、または、「初期化付きの宣言」を書けます。
この宣言で定義された変数はfor文の終わりまで有効です。while文の時のように文を実行する度に生成/破棄されることはありません。

「条件」も「初期化」と同様、「式」、または、「初期化付きの宣言」を書けます。
しかし、「初期化付きの宣言」を書くことはまずありません。

典型的な使い方は次の通りです。

#include <iostream>

int main()
{
    for (int i=0; i < 10; ++i)
    {
        std::cout << "i=" << i << "\n";
    }

    return 0;
}

「初期化」で「初期化付きの宣言」により、int型変数iを定義して0で初期化してます。
次に「条件」が評価され、i < 10が成立するので、「文」を実行します。
「文」の実行が終わったら、++iを実行してiを+1します。
ここまでが1回目の処理です。

続けて、「条件」が評価され、trueなら「文」を実行し、falseならfor文を終了します。

また、break文とcontinue文を書くことができます。機能はwhile文の時と同じです。
coninueは「文」の残りをスキップするので、その後で「式」を計算し、次に「条件」を評価してループの継続を判定します。つまり、coninueでスキップしても有り難いことに++iは実行されます。

初期化、条件、式を全て省略することができます。

for ( ; ; )
{
}

これは、次と全く同じで単なる無限ループになります。

while(true)
{
}
2-4-4.範囲ベースfor文

C++11にて、上記のfor文が拡張され、範囲ベースforと言う文が追加されました。。
配列や動的配列、線形リスト等の要素を枚挙する時に便利なfor文です。
動的配列や線形リストは標準コンテナ(STL)と呼ばれるC++の標準ライブラリにて実装されていますし、ご自身で定義することもできます。これらの解説時に範囲ベースforについても説明します。

2-5.ジャンプ文

実行を中断して、指定の位置から実行を再開する文です。
break文、continue文、return文、goto文があります。

break文とcontinue文は既に説明した通りです。
break文は、switch文、while文、do文、for文の中で使え、それらの処理を中断します。
continue文は、while文、do文、for文の中で使え、実行中の文の残りの処理をスキップします。

return文は関数からの戻りです。関数の解説時に説明します。

2-5-1.goto文

goto文は次節で説明するラベル文へジャンプします。いくつかコンパイル・エラーになるような「飛び方」があります。しかし、それ以前にgoto文を「自由」に使っては行けませんので、細かい使い方の説明はパスします。興味のある方は、ここに詳しい解説があります。

goto文は何かと嫌われることが多いです。安易に使うと本当にメンテナンスし辛いプログラムがさくっと出来てしまいますからです。そして、構造化プログラミングの手法により、ほとんど出番はなくなりました。しかし、まだ1点だけ出番が残っています。for文などのループが多重になっている時、内側のループから複数のループを抜け出したい時だけはgoto文を使うと便利な時があります。

2-5-1.goto文のサンプル・プログラムではLoopExitが「識別子」に当たります。
このサンプル・ソースでは、文はnull文を書いています。

switch文の飛び先は次のように書きます。機能は2-3-2.switch文で解説した通りです。
switch文の中だけに書くことができます。

識別子: 文
case 定数:文
default: 文

サンプル・プログラム

#include <iostream>

int main()
{
    for (int i=0; i < 10; ++i)
    {
        std::cout << "[" << i << "] ";
        for (int j=0; j < 20; ++j)
        {
            std::cout << j << " ";
            if ((i == 5) && (j == 12))
    goto LoopExit;
        }
        std::cout << "\n";
    }
LoopExit:;

    std::cout << "<exit by goto>\n";

    return 0;
}

 

3.非実行文と特殊な実行文

非実行文と特殊な実行文は素の実行文と違って様々な場所に記述することができます。その宣言文の種別により書ける場所も様々です。

3-1.ラベル文

ラベル文はgoto文の飛び先を定義するものと、switch文の分岐先を定義するものです。

goto文の飛び先は次のように書きます。
ラベル文は関数の中だけに書くことができます。同じ関数内のラベル文へのみgotoできます。

識別子: 文
3-2.宣言文

いきなりですが宣言文は膨大です。C++言語仕様の大半を占めるのは宣言文と言ってもよいほどです。ですので、ここではほんの触りだけ説明します。

宣言文の大半は下記のC++の3大構成要素を宣言したり定義したりします。

  • 関数
    関数はざっくりグローバル関数とメンバ関数の2種類に分けることができます。
    前者はクラスや構造体に所属しないもので、後者はクラスや構造体に所属するものです。
    両方とも関数テンプレートと言う雛形を定義しておいて空欄を埋めて具体的な関数を作る仕組みがあります。
    また、メンバ関数はstaticな関数と非staticな関数があります。

  • 変数
    変数はざっくりグローバル変数とローカル変数とメンバ変数の3種類に分けることができます。
    ローカル変数は関数内部で定義された関数ローカルな変数です。
    メンバ変数はクラスや構造体に所属する変数です。
    グローバル変数は関数にもクラスにも構造体にも所属しない変数です。
    ローカル変数とメンバ変数にはstatic変数と非static変数があります。
    C++14で変数テンプレートが導入されています。重要な機能ではないので当講座では解説しません。


  • 実践C++入門講座5回目 main()関数と基本の型の4.基本型についてで説明した基本型は処理系で事前に定義された型です。
    他にenum型、クラス、構造体、共用体、配列、ポインタ、参照があります。
    クラスと構造体は事実上同じものです。そして、これらにはクラス・テンプレートがあります。

その他の構成要素として以下があります。

  • 例外
    頻繁に発生するわけではない、「例外」的な事象が発生した祭に、処理を中断し「例外」的な事象が発生したことを呼び出し元へショートカットで通知する仕組みです。その際に中断された処理で解放されるはずだったメモリやファイル・ハンドル等のリソースを解放する様々な仕組みがあります。

  • その他
    名前空間、リンケージ指定、アトリビュートがあります。

そして、宣言文はこれらの全構成要素について必要に応じて次の2種類の記述を行う文です。

  • 構成要素を定義する。
    対象の構成要素を厳密に定義する記述です。

  • 構成要素を使うために必要な情報を宣言する。
    対象の構成要素を使う時、詳細な定義情報までは不要な場合があります。そのために必要な最小限の情報を記述する記述です。

特殊な実行文=初期化付きの宣言
「構成要素を定義する」の中に「変数の定義」があります。これには定義した変数を初期化する機能があります。
この初期化処理は実行文でもあるため、この宣言文だけは特殊な実行文として分類しました。
そして、これはif文、while文、for文で説明した「初期化付きの宣言」のことでもあります。

例えば以下のような宣言文は全て「初期化付きの宣言」です。

short a=123;
double x=3.14159265*10.5;
int ret=foo();

 

4.まとめ

今回は主にC++の実行文について解説しました。処理手順は実行文を関数の中に記述することで指示します。
「アルゴリズム」の記述は実行文により行うイメージです。
そして、宣言文の触りを説明しました。こちらは主に関数や変数、型を定義したり使えるように宣言したりするための文です。C++言語仕様の大半をしめるのが宣言文ですので、今後等講座の解説のほとんどは宣言文についてとなります。

ところで、個人的にちょっと意外だったのですが、ラベルも文であることに驚きました。ラベルの後ろに ;を書かないといけない場合があることに違和感を感じていたのですが、めったに使わないのでついサボって調べてませんでした。今回、解説を書くために調べてやっと納得できました。お恥ずかしい。

さて来週は、関数、そしてC++で取り扱う4大メモリの1つスタックについて解説します。お楽しみに。