Hello Worldの解説
前回、コンソールに hello, world! と表示させるプログラムを作成して実行しました。
コードの意味については全く触れなかったので今回はその解説をしてみたいと思います。
念の為言っておくと、ここで取り扱うのはあくまで表面的なC++のコード部分だけです。というのも、こんな小さなプログラムであっても真に理解するのはとても大変だからです。つまり、コンパイラがどのようなオブジェクトコードを生成し、システムと連携してコンソールに文字を表示するという結果に至るのか、内部的な仕組みを理解するのは大変なのです。もしそういうところに興味があればまさに「ハロー"Hello,World": OSと標準ライブラリのシゴトとしくみ」というタイトルの本がでていて、この分野のさわりが比較的やさしく解説されてます。アセンブリ言語も少しは読めないと厳しいくらいの難易度ですがおもしろいのでおすすめです。
Hello Worldのソースコードは次のようなものでした。
#include <iostream>
int main()
{
std::cout << "hello, world!\n";
}
1行ずつ解説していきたいと思います。
1行目: #include <iostream>
まず1行目の部分です。これはiostreamというファイルをインクルードするという意味です。インクルードとはどういうことか言うと、そのままファイルの内容をこのhello.cppのファイルに取り込んでしまうということです。iostreamというファイルはGCCをインストールしたときシステムにインストールされています。
#include
というのはC++言語の中でもやや特殊なもので、プリプロセッサディレクティブと呼ばれます。この部分はC++の解析を行う前にプリプロセッサというプログラムによって処理されます。なのでC++コンパイラにはこの部分は渡りません。ごっそり別のテキストに置き換えられたものが渡されます。
プリプロセッサディレクティブは他にもあります。#define
、#ifdef
、#endif
などはよく使います。
iostreamというのはC++の入出力に関連する機能がまとめられたコードです。このようにインクルードして利用するコードのファイルのことをヘッダファイル(あるいは単にヘッダ)といいます。
実際にどのように置き換えられるのか見たければ、
g++ -E hello.cpp
とすることでインクルードされるコードに置き換えられた内容を確認することが出来ます。これは膨大な量のコードになります。
以上のことがあまり理解できなくも、最初のうちは必要になったら#include <ヘッダファイル>
とすると覚えておけば大丈夫かもしれません。自分でもヘッダを書くようになれば自然と理解できると思います。自分でヘッダを書くのはすぐにごく普通に行うようになります。
ちなみに自分で書いた、システムにインストールされたものではないヘッダをインクルードするときは、通常#include "ヘッダファイル"
のように二重引用符で囲みます。
2行目: 空行
2行目は空行です。プログラムの動作には全く影響を与えません。ソースコードを見やすくするために空行を入れることはごく普通に行われます。ここではインクルードするところと、次の3行目からのまとまりとをはっきりと分けたいので空行をいれたという意味に取れます。人によっては空行を入れなかったり、2行続けて空行を入れたりすることもあるかもしれません。こうしなければならないという決まりはないですが、統一されるようにした方がよいかと思います。
3行目: int main()
これはmainという名前の関数の定義の始まりです。実行可能なC++のプログラムはmainという名前の関数を定義しなければなりません。main関数はエントリポイントと呼ばれることもあります。エントリというと最初に実行される部分かと思ってしまうかもしれませんが、実際はそうではありません。
この関数のシグネチャから、戻り値の型がint型で、名前がmainで、0個の引数を取る、ということが分かります。main関数にはこの他にもバリエーションがあります。引数を2つ取るもの int main(int argc, char** argv)
と、3つ取るもの int main(int argc, char** argv, char** envp)
です。3つの方はあまり使いません。
シグネチャ、戻り値、引数といった用語、そもそも関数とはなんなのかという説明は別のところでしたいと思います。
正確にはmain関数が最初に実行される処理ではないのですが、プログラム実行時に処理の開始地点のひとつとしてプログラマが最も明確に記述できるのはmain関数になります。当面の間main関数から始まるとしておいても差し支えないかもしれません。
4行目: {
上のmain関数のブロックの始まりです。ここから閉じ括弧までがmain関数の内容になります。
必ず新しい行に{
を書かないといけない訳ではないです。int main() {
のように関数シグネチャの後ろに続けて書くスタイルもあります。
関数の括弧に位置についてちょっと興味深いコメントがあったので引用しておきます。
ただし、関数定義の括弧だけは例外で、開始括弧は次の行の始まりに置きます -
int 関数名(int x) { 関数の中身 }ところが世界中の異教徒たちは、この一貫性の無さが、...そうですね...一貫性が無いと文句を言っています。しかし、正しい思想を持った人々は (a) K&R は正しく、しかも(b) K&R が正しいのだとわかっています。それに、とにかく関数定義というのは特別なものなのです(C言語の中で関数定義のネストはできません)。
Linux カーネル コーディング規約
個人的にはどちらにするか決めていなくて気分によって変えたりします。しかし、少なくともプロジェクト単位では統一したほうがいいかと思われます。
5行目: std::cout << "hello, world!\n";
ここが hello, world というテキストを表示させている部分です。
インデント
左には4つの空白文字(半角スペース)が入っています。これはインデントと呼ばれます。プログラムの動作には一切影響しませんが、ソースコードの構造を把握しやすく、読みやすくするために続くコードを右に寄せておきます。インデントの幅にもいくつかのスタイルが存在します。例えば、一つのインデントレベルにつき
- 空白文字2つ
- 空白文字3つ
- 空白文字4つ
- タブ文字1つ
- 空白文字とタブ文字を混ぜて使う
空白文字とタブ文字を混ぜて使うスタイルは、Emacsというテキストエディタのデフォルトになっているため知らずに使い続けてしまうことがあります。自分から好んで使いたいと思うことはないでしょう。C++で主流なのは空白2つか4つのように見られます。
std::cout
ここからは勉強不足のため解説が雑になります。書き直し予定。
std::coutを見てみます。これは2つのパートからなってます。最初のパートstdはC++標準ライブラリの名前空間です。std::何とかとすると、何とかはstdという名前空間に入っていると解釈されます。標準ライブラリで定義されるものの名前は、ユーザーが付ける名前と衝突しないように、stdという名前空間の中に入ってます。
次のパートcoutはこれは変数のようなものです。名前の由来はおそらくConsole OUTではないかなと思うのですが確証はありません。標準ライブラリのどこかに入出力のための用意された変数とみなして良いと思います。
<<
これは演算子です。左側と右側に値(式)を取る二項演算子です。
"hello, world!\n"
二重引用符で囲まれたテキストは文字列リテラルと呼ばれます。\n
は改行を表します。
まとめると...
std::cout << "hello, world!"
をイメージで解釈してみます。なんとなく<<
の左側のstd::coutに向けて、hello, world! というテキストを流し込んでいるように見えないでしょうか。 std::coutはだいたいそんなイメージ通りの動作をします。
少し高度な話題
<<
の実体はオーバーロードされた演算子で、さらに関数テンプレートです。
ostreamというヘッダファイルに次のような記述があります。
template<class _Traits>
inline basic_ostream<char, _Traits>&
operator<<(basic_ostream<char, _Traits>& __out, const char* __s)
オーバーロードされた演算子は関数なので、operator<<(std::cout, "hello, world!\n")
としても同じ結果が得られます。
もう一つ注目しておきたいのはArgument Dependent Lookupという仕組みが使われている点です。
std::cout << "hello, world\n"
は実際には関数呼び出しです。しかし、operator<<(...) は名前空間std::の中に存在しているので、本来なら std::operator<<(...) として呼び出す必要があります。これを仮に演算子の形で呼び出すとすると
std::cout std::<< "hello, world\n"
のようになってしまいます。こんな書き方は誰もしたくないし、実際C++では出来ません。そこで、引数のcoutが名前空間std::にあることから察知して、
<<
も std::から探してきてくれます。
6行目: }
main関数のブロックの終わりです。最初の{から順に処理が行われ対応する}のところに到達すると関数が終了します。
ところで、main関数はintを返すと宣言されていました。しかし、このプログラムでは値を返していません。実のところ、main関数に限っては値を返すのを省略することができるようになっています。明示的に値を返す文return 0;
を書いても書かなくても同じ結果になります。ちなみにプログラムの実行元に終了状態を通知するために、正常終了したときは0を返し、それ以外のときは0以外の値を返す慣習になっています。