コードの最小単位
どんなに複雑に書かれたプログラムも、トークン(token)と呼ばれる最小単位のテキストに分解することができます。トークンは英語における英単語に相当するもので、プログラミング言語の命令となる文は、複数のトークンによって構成されています。トークンは、それ以上分解することができません。
プログラミング言語を機械語に翻訳するまでにいくつかの工程を挟みます。コンパイラに入力されたテキストは、ソースコードに書かれたテキストが C++ 言語で定めている文法に従っているかどうかを調べるためにトークンの列に分解されます。この工程を字句解析と呼びます。
トークンにはいくつか種類があり、プログラミング言語の仕様で定められたキーワード(Visual Studio では青色で表示されるトークン)、操作対象の名前を表す識別子、計算記号などの演算子などがあります。トークンは、次の 5 つに分類されます。
- 識別子(identifer)
- キーワード(keyword)
- リテラル(literal)
- 演算子(operator)
- 区切り子(punctuator)
キーワードとは C++ 言語が仕様レベルで予約している名前(英単語)のことで、前回のプログラムで紹介した return などはキーワードに属します。Visual C++ のテキストエディタでは青色で表示されます。
識別子とは、操作するデータや命令を識別するためにソース上で命名された名前です。例えば、main などの関数名は識別子です。
リテラルとは、ソース上で指定された固定的な値でのことです。例えば 10 や 3.14 というような数値はリテラルです。
演算子は、計算のために用いる記号のことで + や - 記号は演算子に属します。
区切り子とは [ ] ( ) { } , : ; などの記号のことで、何らかの要素を区切ったりまとめたりすることを表す記号として用いられています。一部の記号は演算子と同じですが、記号が出現する位置によって演算子なのか区切り子なのかを判断できます。main 関数の名前の後にある括弧や、命令の末尾を表すセミコロン ; などは区切り子に分類されます。
個々のトークンの意味は、C++ の学習を進めることで少しずつ理解していけるでしょう。重要なのは、ソースコード上に書かれるあらゆる記号やアルファベットは、上記のトークンのいずれかに分類することができるということです。
コンパイル時に文法などでエラーが出る場合は、書かれているコードが正しいトークンの並びであるかどうかを調べることで間違いを探しやすくなるでしょう。慣れた技術者であれば、意識することなくトークンの種類や並びを把握してコンパイラに頼ることなくコードが正しいかどうかを判断できます。
英文と同じように、トークンは空白によって区切られます。正確には、ホワイトスペースと呼ばれる次の文字によってトークンを区切ることができます。
- スペース
- 水平タブ
- 垂直タブ
- 改行
- 改ページ
このうちの垂直タブと改ページは、主にプリンタを対象とした専用の文字であり、現在の一般的な PC やテキストエディタでは使われません。Visual Studio による開発において利用するホワイトスペースはスペース、水平タブ、改行のいずれかになるでしょう。
コード1
int
main (
){
return
0;
}
上記のコード1は、コードがめちゃくちゃになっているように見えますが正常にコンパイルして実行できます。空白や改行位置がバラバラなので読みにくいですが、個々の単語や記号の並びをトークンの列として見ると正しいことが確認できます。
同様に、トークンの並びが正しければ水平タブや改行を使わずに 1 行でプログラムを書くこともできます。
コード2
int main(){return 0;}
演算子や区切り子と呼ばれる記号のトークンは、前後のトークンを区切る性質を持ち合わせています。よって丸括弧 ( ) や中括弧 { }、セミコロン ; などの前後のトークンには空白が無くても問題ありません。
一方で、トークンの区切りが適切に行われていなければコンパイラエラーとなるでしょう。例えば、次のようなコードは構文エラーとなります。
intmain(){return0;}
この場合、冒頭のキーワード int と関数名 main が区切られていないため intmain という 1 つのトークンとして解釈されてしまいます。また、関数内の return キーワードと数値 0 の間も区切られていないため return0 というトークンとして解釈されます。コンパイラは、この名前を処理できずにエラーを報告することになります。
逆に区切ってはならない部分にホワイトスペースが挿入された場合でも、トークンが余分に分離されてしまいエラーとなってしまうでしょう。
int mai n(){ retu rn 0; }
上記のコードは、関数名となる識別子 main の途中がスペースによって区切られてしまい mai た n という 2 つのトークンに分けられています。コンパイラは、識別子 mai の直後に現れた n が構文不正であると判断してエラーを報告します。同様に return キーワードもスペースによって retu と rn という 2 つのトークンに分解されてしまっています。コンパイラは retu や rn という名前を解決できないため、やはりエラーとなります。
文(ステートメント)
複数のトークンからなる 1 つの実行単位を文(statement)と呼びます。つまり、コンピュータに対する命令は文単位であり、関数は文の集合であると考えることができます。多くの文はセミコロン ; で終了するため、コンパイラはセミコロンを見つけることによって文が終了したと認識できます。例えば次のプログラムは 2 つのステートメントで成り立っています。
std::cout << "Kitty on your lap\n";
return 0;
画面にテキストを表示するための std::cout から始まる行と return キーワードから始まる行の末尾に ; があります。これらは、ここで文が終了していることを表しています。コードを見やすくするために文が終わると改行を入れますが、改行自体は意味を持たないので望むのであれば 1 行で複数の文を記述できます。
コード3
# include <iostream>
int main() { std::cout << "Kitty on your lap\n"; return 0; }
コード3の main() 関数内では複数の文を改行せずに記述していますが、コンパイラはセミコロンで文の終端を認識できるため問題なくコンパイルができます。ただし、最初の行の #include プリプロセッサディレクティブは文ではないので改行で終了させる必要があります。
文といってもいくつかの種類が存在し、大きく次のように分類されています。
- 名札付き文(labeled-statement)
- 式文(expression-statement)
- 複合文(compound-statement)
- 選択文(selection-statement)
- 繰り返し文(iteration-statement)
- ジャンプ文(jump-statement)
- 宣言文(declaration-statement)
- try-block
このうち、一般的な計算や機能の呼び出しは式文に分類されます。関数の中に記述される文の多くが式文となるでしょう。式文やジャンプ文などでは末尾にセミコロン ; をつけなければならない決まりになっているのですが、必ずしもすべての文にセミコロンが付けられるわけではありません。
例えば、複合文にはセミコロンをつけません。複合文とは関数本体(function-body)に用いている { } のことで、ブロックとも呼ばれています。関数本体は 1 つの複合文と考えることができますが、関数の末尾に ; をつける必要がないのは、複合文の末尾は開始を表す中括弧 { に対応する } で判断されるためです。
それぞれの文が具体的にどのようなものかは、これから順番に説明していきます。今は、文が上記のように分類されていることを覚えておいてください。