この記事は 初心者C++er Advent Calendar 2016 6日目の記事です。
"Hello, World!" とは
この記事は初心者向けの記事ではありますが、"Hello, World!" くらいは目にしたことのある方がほとんどだろう、と思います。
とはいえ聞いたことのない方の為に一応説明すると、この文字列はプログラミング言語の入門時に例として使われる文字列です。
どういう風に使うかというと、コマンドラインツールを作る場合はあの黒い画面に"Hello, World!"と出力したり、
"Hello, World!"とだけ書かれたウィンドウを作ってみたり、
"Hello, World!"と表示するダイアログを出してみたり、
ぶっちゃけ文字列の内容に意味はありません。
他にどんな物があるかを知りたければ、Wikipediaのこの記事とか見るといいかもしれません。
(2018年4月追記:上記の記事は削除されてしまっていました)
C++の"Hello, World!"
さて、多分に漏れず、C++にも典型的な"Hello, World!"プログラムがあります。
全くの初心者でなければ、見たことがある方がほとんどでしょう。以下のようなプログラムです。
#include <iostream>
int main() {
using namespace std;
cout << "Hello, World!" << endl;
return 0;
}
細かい差異はあれど、大体こんなところでしょう。
よくある入門者向けの講座などで、こういう言葉を目にすることがあるかと思います。
「ここはおまじないだと思ってください」
便利な言葉ですね。おまじない。よくわからないかも知れないけど、とりあえずそういうことにしておけ、という乱暴な説明です。
別に、おまじないだからって、唱えればなんでも解決できる訳じゃないんです。
ちゃんと意味はあるのだけど、今はそこまで説明すると大変だから、後回しにするよ、ということを「おまじない」と言っているわけですね。
便利な言葉ではあるのですが、おまじないをおまじないのままで覚えてしまうと、後から応用が利かなくなってしまいます1。
おまじないをおまじないのままにしておくのはやめましょう。
そんなわけで、この記事は「徹底解説」と銘打つことにしました。"Hello, World!"という入門中の入門プログラムを、頭から尻尾まで詳しく見ていきたいと思います。
念の為に申し添えておきますが、この文章には巷の入門講座を否定するつもりではありません。プログラミング言語はどれもある程度の複雑性を有していて、分かりやすくするためにある程度の省略を行うのは仕方がありません。
本当に厳密さを求めるなら、規格書を読めば良いという話になりますし、私もそこまで厳密な解説は書けないでしょう。
#include <iostream>
まずは一行目。これも「おまじない」とされることが多いです。
実際、この行はC++のプログラムの中ではやや異色な位置にあります。入門講座の執筆者にとっては、別のことから解説したくなることが多いのではないでしょうか。
でもそんなの関係無え。
なんたって徹底解説ですからね。これだってちゃんと解説しますよ。
この#include
を始め、#
で始まる行には特別な意味があります。**プリプロセッサー命令(Preprocessing-directives)**です。
これを理解するためにはまず、C++の実装でソースコードがどのような過程を辿って実行ファイルに変換されるのかが分かっていないといけません。
C++のソースコードを実行ファイルに変換する過程を、大雑把に「コンパイル」と呼ぶことがあります。
しかし、実際には、C++のソースコードは複数回の処理を経て、ようやく実行ファイルに変換できます。
具体的には、
- プリプロセス
- コンパイル
- アセンブル
- リンク
などの処理があります2。
実際、これら全ての過程は、特にオプションを指定しなければコンパイラがコマンド一つで自動的にやってくれます。ただ、内部的にはこれらの過程を経ているのです。
上記の過程の一つに、「プリプロセス」があります。プリプロセッサーはこのプリプロセスを行うプログラムを指し、プリプロセッサー命令は文字通りプリプロセッサーに対する命令です。
つまり、
#include <iostream>
という文は、プリプロセッサーに対して、「<iostream>
をinclude
しろ」という命令を書いている訳です。
さて、ではプリプロセッサーはどのような処理を行っているのかを説明します。
pre-processという言葉は、日本語で言うなら「前処理」でしょうか。あまり具体的じゃないですね。結局何を処理しているのかと。
プリプロセッサーがやっているのは、主に文字列処理です。プリプロセッサーにテキストを読みこませると、一定のルールに従って文字列を変換したテキストが出力されます。
一定のルールとは
- マクロ定義された文字列が現れた場合、置き換えを行う
- 連続する文字列リテラルがあった場合、結合を行う
-
#
で始まる行がある場合、以下の処理を行う-
#define
: マクロを登録する -
#undef
: 登録されているマクロを削除する -
#include
: 解析中のファイルとの相対パス、もしくはインクルードパスからファイルを検索し、その行に展開する -
#if
,#ifdef
,#ifndef
,#elif
,#else
,#endif
: 条件分岐を行う。条件が偽となった部分は出力されない。条件式はネストできる。 -
#line
: 現在の行数を変更する。 -
#pragma
: プリプロセッサーの実装ごとに固有の命令を書くことができる。この命令はプリプロセッサーで完結しない場合がある。 -
#error
: 指定されたエラーメッセージを出力し、コンパイルエラーを起こす - それ以外: 不正なプリプロセッサー命令と判断し、コンパイルエラーを起こす(実装ごとの拡張による例外あり)
-
- それ以外の場合はそのまま出力する
おおむね上記のようなものになります。
つまり、#include
命令は、他のファイルの内容を読み込むために用意されています。
この時、読み込まれたファイルも連鎖的にプリプロセッサーで処理されます。
プリプロセッサーの出力のみを見たい場合、g++/clang++なら、
$ g++ -E -P ファイル名
$ clang++ -E -P ファイル名
Visual C++なら、
> cl.exe /EP ファイル名
とすることで、プリプロセス済みの文字列を得られます。
以下に例を示します。
a.cpp start
#include "b.hpp"
a.cpp end
b.hpp start
#include "c.hpp"
b.hpp end
c.hpp
プリプロセスのみを行うなら、C++の文法的に正しくないものでも構いません。
出力は次のようになります。
a.cpp start
b.hpp start
c.hpp
b.hpp end
a.cpp end
"c.hpp"の中身が"b.hpp"内部の#include "c.hpp"
の部分に置き換わり、さらに置換が行われた"b.hpp"の中身が"a.cpp"内部の#include "b.hpp"
の部分に置き換わっているのが分かるでしょうか。
さて、ここで疑問に思っている方もいるかもしれません。例では#include
の後のファイル名は""
で囲んでいますが、"Hello, World!"の中では#include <iostream>
のように<>
で囲んでいました。
#include
命令には、""
を使ってファイル名を指定する方法と、<>
を使って指定する方法の2種類が用意されていて、それぞれ挙動が異なります。
#include "path/to/file"
と書いた場合、プリプロセッサーはまず、処理中のファイルからの相対パスでファイルを探し、見つからなかった場合、事前に組み込まれた標準ライブラリのパスや、追加で渡されたインクルードパスを探します。
一方、#include <path/to/file>
と書いた場合は、プリプロセッサーは相対パスから検索を行わず、インクルードパスの中だけを検索します。
インクルードパスは、g++/clang++なら-I
オプションで、MSVCなら/I
オプションで渡すことができます。
試しに、このようなディレクトリ構造でファイルを配置してみましょう。
/a.cpp
/b.hpp
/c.hpp
/include/b.hpp
/include/c.hpp
#include "b.hpp"
#include <b.hpp>
#include "c.hpp"
#include "d.hpp"
#include <d.hpp>
b.hpp
c.hpp
d.hpp
include/b.hpp
include/d.hpp
$ g++ -E -P -I/include a.cpp
b.hpp
include/b.hpp
c.hpp
include/d.hpp
include/d.hpp
相対パスとインクルードパスの双方に存在する"b.hpp"というファイルは別々の物が読み込まれ、インクルードパスにしか存在しない"d.hpp"は同じものが読み込まれています。
ちなみに、相対パス上にしか存在しない"c.hpp"は、#include <c.hpp>
と書くとエラーになります。
さあ、これで一行目の意味は分かったでしょう。
#include <iostream>
は、インクルードパス(この場合は特に、事前に組み込まれた標準ライブラリのパス)から、iostream
という名前のファイルを読み込んで、その場所に展開するというプリプロセッサーの処理を書いたものでした。
ついでながら、他のプリプロセッサー命令についてもざっくりと説明します。
#define
プリプロセッサーマクロを定義します。プリプロセッサーマクロには、以下の2種類の形式があります
オブジェクト形式マクロ
#define MACRO_NAME some strings
この例では、ソースファイル中に出現するMACRO_NAME
という文字列が、some strings
という文字列に置き換えられます。
マクロ名は空白や記号(_
を除く)で区切られた文字列が該当します。従って、ソースファイル中に例えばNOT_MACRO_NAME
という文字列が出現しても、NOT_some string
に置換されることはありません。
関数形式マクロ
#define MACRO_FUNCTION(x) My name is x.
この例では、ソースファイル中に、例えばMACRO_FUNCTION(kazatsuyu)
という文字列が出現した時に、My name is kazatsuyu.
に置き換えられます。
関数形式マクロの定義では、マクロ名と(
の間に空白を入れてはいけません。空白を入れると、オブジェクト形式マクロと判断されます。
// "(x) My name is x."という文字列に置き換えられるオブジェクト形式マクロ"MACRO_FUNCTION"が定義されてしまう
#define MACRO_FUNCTION (x) My name is x.
また、関数形式マクロは複数の引数をとることもできます。その場合
#define MACRO_FUNCTION(x, y) My name is x, and I'm y years old.
などのように、カンマで区切ってください。
関数形式マクロを使うといろいろ複雑な処理ができるのですが、ここではこれ以上深追いはしません。
反プリプロセッサーマクロ組織の構成員より一言
と、ここまでマクロの説明をしましたが、マクロは基本的に使用してはいけません。いくつか理由があるので以下に示します。
- グローバル空間にしか定義できない
C++には名前空間という、別のライブラリと名前が衝突しないようにする機能がありますが、プリプロセッサーマクロはそういうことができません。特に、短い名前のマクロを定義してしまうと、非常に衝突の可能性が高くなります。その上、せっかく名前空間内で定義したC++の識別子と衝突することがあります3。
どうしても使用する場合は、固有のプレフィックスを付けて、なるべく衝突しない名前にしましょう。
- コードが読みにくくなる
マクロは非常に強力です。強力過ぎて、マクロを使えば何でも置き換えができてしまいます。C++の文法に合わないような記法も簡単にできてしまいます。
それこそ、
なんてこともできてしまいます(C#に見えるかも知れませんが、C++のコードです)。 もちろんこれはジョークですが、マクロを多用すると何でもできすぎてしまうのです。@arutemyan argsが惜しいとの指摘を受けたので修正しました。無駄に頭使った。 pic.twitter.com/uV7OaRfpOU
— あるてみかん@三日目る22a (@arutemyan) 2016年11月1日
※ネタばらし
@arutemyan 一応ネタばらししておきますね pic.twitter.com/PlrPksPoXE
— あるてみかん@三日目る22a (@arutemyan) 2016年11月1日
マクロが必要になる箇所
ただ、どうしてもマクロを使わなければならない箇所もあります。一つはインクルードガードです。
インクルードガードとは、
#ifndef INCLUDE_GUARD
#define INCLUDE_GUARD
// Some C++ codes
#endif
と書いておくことで、同じファイルが複数回インクルードされても、#ifndef
〜#endif
の間は一回しか展開されないようにできます。(#ifndef
や#endif
に関しては次項参照)
ただし、現在主流の実装では、#pragma once
と書くことで、ファイル単位でインクルードガードと同等の効果があります。
問題は、#pragma once
は実装ごとの拡張機能であり、標準化されていないため、全ての実装で使えるとは限らない点です。
他にも、コンパイラ・OS・アーキテクチャの判定や、デバッグ時のみ有効なトレースポイントの挿入など、マクロを利用しないと実現できないこともあります。
デバッグで関数名・ファイル・行数などを取得したい場合も(今のところは)同様です。
マクロ定義オプション
プリプロセッサーに定義済みマクロを追加するオプションがあります。
g++/clang++なら-D
、MSVCなら/D
を使います。
例えば、
$ g++ -Dfoo=bar a.cpp
とすると、事前に
#define foo bar
と定義されたように振る舞います
#undef
定義済みのマクロ定義を削除します。一時的に定義したマクロを削除する、気に食わない定義済みマクロを消すなどのことが可能です。
#undef min
#undef max
とか。
……やるときは、やっても大丈夫かどうか、ちゃんと検討してね?
#if
, #ifdef
, #ifndef
, #elif
, #else
, #endif
条件分岐です。
#if
は、その後ろに現れる条件が偽なら、#else
, #elif
, #endif
のいずれかが出るまでの間を出力しません。条件が真なら、出力を続けます。
条件が偽となった場合、出力されない部分の内容は全て無視されます。不正なプリプロセッサー命令などが存在しても無視されます。
#if
の条件式には、比較演算や論理演算を使用することができます。
また、defined
キーワードを使って、マクロ定義の有無を調べることができます。
#if 1
出力される
#endif
#if 0
出力されない
#endif
#if true
出力される
#endif
#if false
出力されない
#endif
#if 1 > 0
出力される
#endif
#if 1 <= 0
出力されない
#endif
#if 1 || 0
出力される
#endif
#if 1 && 0
出力されない
#endif
#define a
#if defined(a)
出力される
#endif
#if defined(b)
オプションでマクロ定義されていない限り出力されない
#endif
#elif
は#if
命令が偽だった時に実行される条件分岐です。条件式は#if
と同等です。
#else
は直前の#if
命令や#elif
命令が偽だった場合に真になる分岐です。
#endif
は条件分岐の終了を示します。
#ifdef
, #ifndef
はそれぞれ、マクロが定義されているか、あるいは定義されていないか実行する条件分岐です。
#ifdef a
は#if defined(a)
, #ifndef a
は#if !defined(a)
と同じ意味になります。
#line
この文章を書くにあたってプリプロセッサーを調べなおしていたら見つけました。今まで知りませんでした。
#line 10000
と書くと、次の行が10000行目ということになります。たとえそこが2行目であっても。
ちなみに、現在の行数は__LINE__
と書くと得ることができるので、
#line 10000
__LINE__
#line 1
__LINE__
と書いてプリプロセッサーを実行すると、
10000
1
となります。
行を変えたところで何か意味があるかというと、ほとんどの場合何の意味もない上に、デバッグがうまく行かなくなる可能性が高いのではないかと思います。つまり使う必要はないです。
#pragma
実装ごとに拡張命令を加えることができます。有名どころは、#pragma once
や、MSVCでの#pragma warnings
, #pragma comment
などでしょうか。互換性が保証されていないので、使用する際はよく検討しましょう。
さて、だいぶ関係ない範囲も解説しましたが、これにて1行目は終わりです。まだ1行目です。なんか文字数数えたら9000字近いみたいですが、続きます。
int main() {
main
関数です。main
関数は、C++のプログラムの初期化が終わった後に、最初に呼び出される関数です。
なんて言うと、「WinMainは?」と思う方もいるかもしれませんが、あれはMSVC独自の規格です。
C++の国際規格では、プログラムの開始点はmain
関数であり、それはint ()
またはint (int, char**)
という型であることが推奨されています4。
とは言え、そもそも「関数って何? 型って何?」という方もいるでしょう。読者の方にはいないかもしれませんが、徹底解説と銘打った以上、そこから解説しなければなりません。
どのプログラミング言語にも、「関数」「型」「変数」「値」といった基本的な概念が存在します。
これが複雑に絡み合っていて、一つだけを解説する、ということがとても難しい。だから、「とりあえずこう書いておけ」という解説が多いのです。
main
は関数なので、まず関数の説明から入りましょう。
C++における"関数"とは、
「何らかの値を受け取って(受け取らないこともある)、何らかの処理をして(しないこともある)、何らかの値を返す(返さないこともある)もの」
です。
まって。怒らないで。だって本当にそうなんです。
と言っても、これでは全くもって何のことか分からないと思いますので、もうちょっと具体的に見てみましょう。
関数(function)という言葉は、もともと数学の関数から借りてきています。
数学的な関数だと、例えば
f\left(x\right)=x^2
とかそういうものが思い浮かぶでしょう。C++の(というよりプログラミング言語全般における)関数は、この関数の概念を拡張したものなので、逆に言えば、数学的な関数を(それなりに)表すことができます。
上記の関数の場合、
int f(int x) {
return x * x;
}
と書けば、(int
で表せる数値の範囲であれば)数学の関数と同じ結果が得られる訳です。
さっきから歯に物が挟まったような言い方をしているのには訳があります。
当然のことですが、コンピューターの使えるリソースには限界があって、どんな実装であれ、その限界を超えて表すことのできる数はありません。
例えば、グラハム数などを表わせともし言われてもまず不可能です。また、無理数を表すこともできません。無理数の場合は、どうしても近似値を扱うことになります。
さて、上記の関数f
ですが、int
型の値を一つ受け取り、int
型の値を返す関数です。
「型(type)」という用語を持ちだしたからには、型とは何か、について説明しなければなりません。
「型」というものを正確に説明するのは、なかなか骨の折れることだと私は思っています。
「型」という考え方は多分に抽象的なもので、これといった具体例を示すのが難しいからです。
だから、なるべく分かりやすく型の説明をしようとして具体例を出すと、車が走ったり犬が鳴いたりするのです。
犬や車で例えるのも分からなくはないのですが、具体例と言うのなら既にint
は型であるという例があるので、これを解説しようかと思います。
int
は整数を表す型です。もっと言うなら、一定範囲の整数を表す型です。整数は(可算)無限個存在しますが、無限個の整数を表すことのできる方法はありません。なので、コンピューターで表せる整数の範囲は一定の限界があります。
その限界がどの程度であるのかは、これはC++の規格では実装依存とされているので一概には言えないのですが、少なくとも、int
型は整数以外の値を表すことはできません。
逆に言えば、ある値の型がint
であれば、「その値は整数である」ということが保証されます。
C++では、全ての値には型があります。そして、型が異なれば、その値が表すものも異なります。
関数の引数にも型があるので、異なる型の値を受け取ることはできません5。
改めて、
int f(int x) {
return x * x;
}
という関数を見てみましょう。
関数f
はint
型の値をx
という名前で受け取ります。このx
を仮引数と呼びます。
そして、x * x
という式を実行します。*
は乗算を行う演算子なので6、つまりx
の2乗が得られます。
return
というキーワードは、呼び出し元に結果を返す時に使います。返された値を戻り値と言います。
戻り値の型は、関数名の直前に書かれた型が表しています。つまり、この場合はint
です。
C++では全ての値に型があります。関数も例外ではありません。関数の型は、戻り値の型 (引数の型)
という表記をします。引数は複数個取ることができますが、その場合は,
で区切ります。
関数f
の型はint (int)
である、ということになりますね。
上記の例のように、{}
の中に関数が行う処理を書いたものを、関数の**定義(definition)と言います。
一方で、定義を書かずに名前と型だけを書くこともあります。これを宣言(declaration)**と言います。宣言を書くときは、
int f(int x);
と、このように{}
を書かずに;
を置いて終わらせます。
C++では、関数を呼び出すときは、事前に定義または宣言が書かれていないといけません。
関数の宣言は、関数プロトタイプ宣言と呼ばれることもあります。f
のプロトタイプはint f(int x);
である、といった使い方をされたりもします。
int g() {
return f(2);
}
という関数を定義した時に、f
が宣言も定義もされていなかったらコンパイルエラーになります。
int f(int x);
int g() {
return f(2);
}
と書いてあれば、関数g
に関してはエラーなくコンパイルできます。
宣言は「こういう関数があるよ」ということを知らせるために書くもので、定義は「この関数はこういう処理をするよ」ということを決めるために書くものです。
同じ関数に対する宣言は何度書いても良いですが、原則として定義は一回でなければなりません。これを**ODR(One Definition Rule)**と言います7。
さて、main
関数の話に戻りましょう。
入出力であったり、画像処理であったり、通信であったり、およそC++のプログラムが行う処理のほとんどは関数の中に記述されます。一つの関数は、別の関数を内部で呼び出すことができます。
そして、C++のプログラムで最初に呼び出されるのがmain
関数です。C++のプログラムのほとんどの処理は、main
関数が始まってから終わるまでの間に実行されます8。
先程も述べた通り、main
関数の型はint ()
もしくは、int (int, char**)
のどちらかであることが推奨されています(厳密には実装依存です)。
つまり、main
のプロトタイプはint main()
もしくはint main(int, char**)
になります。
では、この引数をとる方のmain
関数についても説明しましょう。
慣習的に、引数をとるmain
関数は、
int main(int argc, char** argv) {
/* ... */
}
という仮引数名で書かれることが多いです。
この引数はそれぞれ、「コマンドライン引数の数」と「コマンドライン引数リスト」を表しています。
コマンドライン引数は、プログラムを起動する時にOSが受け取るパラメーターです。
bashやcmd.exeなどの中でコマンドを実行するときは、コマンド名に続けてスペース区切りで様々な文字列を入力しますが、コマンド名を含めた全ての入力文字列がコマンドライン引数になり、スペース区切りになった状態でC++のプログラムに渡ってきます。
例えば、
git commit -a
というコマンドを実行した場合、git
というプログラムは"git", "commit", "-a"という3つのコマンドライン引数を受け取ります。引数が3個なのでargc
は3になるわけですね。
仮引数argv
ですが、これは知らないと良くわからない型に見えるかな、と思います。
char
というのは文字型の一種なのですが、その後ろにある**
は何なのだろう、と思うかも知れません。
*
という記号は、先程は乗算を行う演算子として現れましたが、今回は別物です。
まず、型名の後ろに*
が付いた型は、ポインター(pointer)型と言います。
ポインタ型は変数の**アドレス(address)**を指し示す型です。
変数とかアドレスとかポインターの説明
その前に、変数とは何かをちゃんと説明していませんでした。
**変数(variable)**は、これまた数学の変数から借りてきた言葉です。英語のvariableは、vary(変える)+able(可能)なので、「変えることができるもの」といった意味になるでしょうか。
先ほどの型の説明で、**値(value)**という言葉を出しました。これもちゃんと説明していませんでした。
例えば0
という数。これはint
型の値です。もちろん1
もint
型の値だし、42
もint
型の値です。
列挙していけばキリがな――いえ、キリはあるのですが数が膨大過ぎて無理だし何の役にも立たない情報になってしまうのでここには書きませんが、int
型で表現された任意の整数は、int
型の値である、ということになります。
コンピューター上の状態は全て何かしらの値であると捉えることができます。ハードウェアから考えると、プログラムが直接扱うのは、主にレジスタと呼ばれるCPUが直接扱う高速で小さな記憶域と、RAM(Random Access Memory)と呼ばれるけっこう高速でそれなりの大きさがある記憶域です。RAMは一般には単にメモリと言われることが多いですね。昔は256MBで大容量とか言っていましたが、最近は16GB積んでいても基本的人権扱いされる風潮のようです*[要出典]*。
メモリ領域はそれなりの大きさがあるので、プログラム実行中の内部状態のほとんどはメモリ領域に保持されます。ハードウェアやOSの細かい動きまではさすがにこの文書では説明を省きますが、プログラムが扱うメモリ領域は広いので、番号を使って位置を特定します。これがアドレスです。
メモリ上のデータは、全てその位置を番号で表すことができるのですが、これを人間が直接扱うのは不可能ではないとは言え、とても困難です。
なので、プログラム言語では番号の代わりに名前でデータを特定できるようになっています。これを変数と言います。逆に言えば、変数は何らかの値を表していることになります。ある変数にアドレスを結びつけることを変数の**束縛(binding)**と言います。
変数を新たに定義するとき、多くの場合は
型名 変数名 = 初期値;
型名 変数名{初期値};
といった記法を使います。初期値がない場合はデフォルト値で初期化されるか、未初期化状態になるかのどちらかです9。
例えば、int
型の変数を新たに定義する場合は、
int hoge{42};
int piyo;
などとします。この例だと、hoge
は42
という値で初期化されますが、piyo
は未初期化です10
変数がどこの位置にあるのかはコンパイラが決めてくれます。実行するまでどこの位置に作られるか分からない変数もあります。
しかし、せっかく名前を付けてアドレスを隠蔽しても、実際に変数がどの位置にあるのかを知りたくなることもよくあります。そのため、C++ではアドレスを取る方法と、アドレスを表す型が用意されています。それがポインターです。アドレスを指し示す(point)ものなので、ポインターです。
アドレスとポインターを混同してしまう人がいるかもしれません。アドレスは、変数と値の関係で言えば値の方です。ポインターは変数です。ポインターが束縛する値はアドレスを表しますが、アドレスはポインターではありません。混乱しやすいので、注意してください。
さて、先ほど、型名の後ろに*
をつけると、ポインター型になると言いました。
その型は、*
をつけない型の変数のアドレスを示すポインターの型になります。
つまり、char*
という型であれば、任意のchar
型の変数のアドレスを示すポインターの型、ということになります。
例を見てみましょう。
char a = 'a';
char* b = &a;
'a'
はUTF-8文字リテラルというもので、char
型の値を表します。これは実装定義の1バイト文字です11。
&
は、変数のアドレスを取得する演算子です。&a
であれば、変数a
のアドレスを取得しています。
char* b = &a
という式全体で、「char
型のポインター変数b
を定義して、char
型の変数a
のアドレスで初期化する」という意味になります。
ポインターは変数なので、当然ポインター自身もアドレスを持ちます。
ポインターのアドレスを束縛する変数は、「ポインターのポインター」であるということになります。*
を付けるとポインター型になるので、ポインターのポインター型を表すには、*
を2つつければ良いことになります。
char**
という型は、「char
型のポインターのポインター」ということになります。
当然、char**
型の変数のポインターはchar***
になりますし、さらにどこまでも*
を増やして、char*************************************************************
みたいな型を使うことも出来ます。
なお、そんな型が登場するコードを見たら私はブチ切れます。ポインターのポインターに親を殺された復讐鬼になって世の中のポインターが全て参照かstd::unique_ptr
かstd::shared_ptr
かstd::weak_ptr
かstd::vector
かstd::basic_string
かstd::optional
に置き換わるまで戦いをやめないマサカリマシーンと化すので、先鋭的なカルトC++erを怒らせないように気をつけましょう。
お前は何を言っているんだ。
実際のところ、ポインターの機能の多くは今挙げたような標準ライブラリの機能(一部C++17で提案されているものも含む)で代替可能です。気をつけて開発を行えば、生のポインターを使う機会はそれほど多くはないでしょう。
アドレスは*
演算子で**参照する(dereference)**ことができます。まことにややこしい話ですが、*
演算子はさっき出てきた乗算の演算子ではなく、また、型名の後ろにつけてポインター型にする*
とも異なります。
*
演算子でポインターを参照すると、そのポインターに入れられたアドレスが示す位置の変数の値を取ることができます。
先ほどの例で言えば、変数b
は変数a
のアドレスで初期化されているので、*b
とするとa
の値を取得できます。
ポインター型を作る時の*
記号は、型名との間にスペースがあってもよく、また、変数名との間にスペースがなくても良いです。
つまり、char* b
という書き方はchar *b
という書き方と同義です。すると、「b
の型はchar*
である」という情報の他に、「*b
の型はchar
である」、と見ることもできます。
配列の説明
さて、まだ回り道が続きます。
ポインターは任意の位置のアドレスを示しますが、単なる変数ではなく、**配列(array)**の先頭を表していることがあります。
配列と言うのは、同じ型の値を連続して並べた領域を指します。配列は、通常の変数と異なり、その要素数(配列の長さ)を変数名の後ろに[]
に入れて宣言します。
例えば、
int a[3];
という方法で定義された変数a
は、int
型の長さ3
の配列になります。
この配列の先頭は、a[0]
という方法で参照できます。同じように、「配列の先頭+n」番目の要素には[n]
を使って参照できます。配列a
は長さ3
なので、a[0]
, a[1]
, a[2]
の3つの要素を参照することができます。
ここで気をつけて欲しいのですが、配列の長さは3
ですが、a[3]
にアクセスしてはいけません。配列の要素を参照する際に[]
演算子の中に入れる値を**添字(index)**と言いますが、これは0
を基準にしているので、配列の末尾の要素は「要素数-1」になります。なので、ここでa[3]
を参照すると何が入っているか分かりませんし、何が起こるかもわかりません。プログラムがクラッシュする可能性もあります。
配列の要素数を超えた位置にアクセスを行なってはいけません。
さて、配列の要素にアクセスする方法ですが、実はa[0]
と*a
は同じ要素を参照しています。
おや、見覚えのある演算子が出てきました。これは、先ほどポインタの参照を行う時に使ったのと同じものです。つまり、配列変数の指す値を参照すると、配列の先頭の要素が得られるのです。
これはどういうことかと言うと、配列変数は、配列の先頭アドレスを示すからです。配列変数はポインタではないため、アドレスは固定で書き換えはできませんが、参照を行うことはできます。
配列の先頭アドレスをポインターに代入することができます。
int a[3];
int* b = a;
このようにすると、ポインター変数b
は配列a
の先頭アドレスを示すことになります。
逆に、ポインターb
から配列の[]
演算子を利用して配列a
の要素にアクセスすることができます。
int a[3];
int* b = a;
b[2] = 10; // この場合、a[2] = 10;と書くのと同じ
つまり、ポインターは単一の変数のアドレスを指していることもあるのですが、配列のアドレスを指していることもあります。どちらの意味でポインターが利用されているかは、説明やコードを見てみないと分かりません。これは非常にややこしいため、ポインター以外の方法を使ったほうがいいことが多々あります。
私は常々ポインターはなるべく使うべきではないと考えていますが、それはそれとして、歴史的な経緯によりポインターを使わなければならないケースもあります。main
関数の引数もその一つです。
というわけで、main
関数の引数はchar**
というややこしい型が使用されています。
main
関数では、char** argv
はchar*
型の配列のアドレスを示しています。
配列の先頭アドレスだけを示されても、長さはわかりません。
先ほど言ったように、配列の要素数を超えた位置にアクセスを行ってはいけませんが、長さが分からないとどこまでアクセスしても良いかわかりません。
そこで、main
関数の場合は、配列の要素数を第一引数で渡しています。argc
はargv
の要素数を示しているのです。
では、argv
の示す配列に並べられたchar*
型の値は何を示しているのでしょう?
main
関数の引数では、char*
型の値もまた、char
型の配列のアドレスを示しています。
char
型の配列は、必ずしもそうであるという訳ではありませんが、多くの場合は文字列を表します。この場合もそうです。
先ほど説明した通り、main
関数にはコマンドラインで渡された文字列が渡ってくるわけですが、ここでその文字列は、argv
の各要素に順番に格納されます。
文字列も配列なので、要素数を超えた場所にアクセスしてはいけません。ところが、argv
が示すchar*
型のポインター配列の要素数はargc
で得ることができますが、各文字列の要素数は渡されていません。
実は、C++には(C言語からの遺産として)「長さの不明な文字列」がよく出現します。
長さ不明といっても、実際には長さはあるのですが、先頭から辿っていかないと長さを求められないのです。
そういった文字列はnull終端されています。つまり、文字配列の一番最後にchar型の値で言えば0
、文字リテラルで表すなら'\0'
に相当する文字が入っています。なので、その最後の文字が出現するまで、先頭から順に数えていかないといけません。
C++では、もっと文字列が扱いやすい標準ライブラリの文字列クラスが用意されていますので、そちらを使ったほうが良いでしょう。null終端された文字列から変換することも可能です。
さて、ここまででmain
関数の説明は8割方終えることができたと思います。まとめましょう。
-
main
関数は、プログラムの初期化終了後に呼ばれる関数である -
main
関数は、int main()
もしくはint main(int argc, char** argv)
という形のどちらかで定義する -
main
関数が引数をとる場合、第一引数はコマンドライン引数の数、第二引数はコマンドライン引数の文字列の配列である
えー、なんか現時点で文字数が2万字に近くなっているようですが、続けます。
using namespace std;
プログラミングを行っていると、色々なところで名前が登場します。main
関数はmain
という名前を持った関数ですし、argc
やargv
はそういう名前を付けられた仮引数です。
こうした名前を**識別子(identifier)**と言います。
さて、C++は世界中の人に利用されていて、ありがたい事に素晴らしいライブラリが無料で公開されています12。おかげで私のような平凡プログラマーでも楽に開発ができるのですが、他の人が作ったライブラリを利用するということは、他の人が定義した識別子を自分のプログラムに取り込むということでもあります。
そして、何の相談もせずに作られたライブラリで、同名の識別子が使われているのは非常によくあることです。それどころか、一人の人が作っていても、うっかり別の場所で同じ名前を使ってしまう、なんてこともないとは言えません。
そういった、名前の衝突を避けるために、**名前空間(namespace)**というものが用意されています。
名前空間を作るのは簡単です。
namespace ns {
}
このように書くと、{}
で囲まれたブロックの内部が、ns
という名前空間になります。名前空間で定義された識別子は、他の名前空間で定義された識別子と別物になります。また、名前空間はネストさせることができます。
ちなみに、一番外側の空間は**グローバル名前空間(global namespace)**と呼ばれます。
int a; // (1)
namespace x {
int a; // (2)
}
namespace y {
int a; // (3)
}
上記の例では、(1), (2), (3)のa
は全て異なる変数です。
実際にa
という名前の変数を使おうとした時に、C++のコンパイラーは**名前探索(name lookup)**を行います。名前探索では、現在のブロック内で宣言された識別子が優先的に見つかります。上記の例で言うなら、x
名前空間内で名前探索を行った場合、まず(2)が見つかりますし、y
名前空間内で名前探索を行った場合、(3)が見つかります。そして、x
の中でもy
の中でもない場所で名前探索を行うと、(1)が見つかります。
逆に、名前空間内の識別子を名前空間の外から使おうとすると、一部の例外を除いて名前探索は行われません13。その場合、名前空間を明示して識別子を利用することができます。
上記の例では、x::a
と書くと(2)を、y::a
と書くと(3)を示すことになります14。また、グローバル空間にあることを明示したければ、::a
と書くと、必ず(1)になります。
ライブラリ製作者は、名前の衝突を防ぐために、自作の関数や型などは全て名前空間に入れるようにします。少なくとも、行儀のいいライブラリならそうなっています。こうすれば、名前空間名さえ衝突しなければ、名前の衝突を気にする必要はありません。
C++の標準ライブラリもまた、名前空間の中に入れられています。標準ライブラリの名前空間はstd
です。std
名前空間の他に、std<n>
(<n>は0以上の整数)という名前は標準ライブラリの将来の実装のために予約されているので、標準ライブラリ以外が使ってはいけません。std2
とかstd42
とかstd65535
とかも含めて全てです。今のところ、std
しか使われていませんが。
さて、こうして名前空間に入れることで衝突を回避した名前ですが、一々名前空間まで含めた名前を書くのは面倒だという向きもあります。そこで、using namespace
文が用意されています。
using namespace std;
と書くと、std
名前空間内の識別子は、名前空間外であっても名前探索の対象となります。その代わり、std
名前空間の識別子と他の識別子が衝突した場合、エラーが発生することもあります。
main
関数の中でusing namespace std;
を書くと、
-
std
名前空間で定義されている識別子を利用する -
std
名前空間が名前探索の対象となるのはmain
関数だけである
ということになります。
よくある"Hello, World!"プログラムで、using namespace std;
が「おまじない」としてグローバル名前空間に書いてあることがあります。以下のようなコードです。
#include <iostream>
using namespace std;
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
グローバル空間でusing namespace
は使うべきではありません。これを行ってしまうと、せっかく名前空間の中に閉じ込めたのが無駄になってしまいます。
それでも、ソースファイル一個の中であれば他の翻訳単位に影響を及ぼさないためまだマシですが、ヘッダーファイルのグローバル名前空間でusing namespace
を使うのは最悪です。
この問題に関して、私はグローバル名前空間でusing namespace std;
を乱用している入門記事が悪いと常々思っています。
強い思想が漏れました。
それはそれとして、using namespace
は、狭い範囲で使うならそれなりに便利です。たとえば関数一個の中に限定するとか、そういう使い方なら問題ありません。
また、C++11から入ったユーザー定義リテラルを使用するためにusing namespace std::literals::chrono;
といったコードを書くのも、どこからもincludeされないソースファイル内や自分が定義した名前空間内であれば構わないでしょう。
ただ、std
名前空間程度であれば、わざわざusing namespace std;
を使うより、std::
を付けて書いても良いのではないか、と私は思っています。
それと、using namespace
で名前空間全体を取り込んでしまうと問題が発生することもありますので、使いたい識別子が限定されているなら、using std::size_t;
といったように、using namespace
文ではなく、using
文を使うこともできます。
また、世の中には多重に入れ子になった長い名前空間があって、そういう時は「名前空間を一々書くのは面倒だ、using namespace
してしまえ」となることもあるでしょう。
ですが、例えばvery::very::very::loooooooong::name_space
などといった長い名前を使う時でも、
namespace ns = very::very::very::loooooooong::name_space;
という方法で名前空間エイリアスを使うことができますので、基本的にはそちらを推奨します。
cout << "Hello World!" << endl;
この文は、意味としては、「標準出力に『Hello World!』という文字列を書き出して改行する」ということなのですが、きちんと理解するためには、
- リテラルについて
-
cout
が何者なのか - テンプレートについて
- オペレーターオーバーロードについて
- マニピュレーターについて
といったことを知らなければいけません。
リテラルについて
リテラルというもの全般について、まだちゃんと解説していなかった気がします。
C++の文法には、データを直接埋め込むことができる方法があります。これまでにもちょっと出現した'a'
のような文字リテラルや、0
, 42
といった数値も、ソースコード上に現れたものは整数リテラルといいます。
ユーザー定義リテラルというものもあるので、リテラルの種類はそれこそ無限にあるのですが、基本的に、コア言語機能としては以下のものが用意されています。
- 整数リテラル
- 10進整数リテラル
- 8進整数リテラル
- 16進整数リテラル
- 2進整数リテラル
- 浮動小数点数リテラル
- 文字リテラル
- 文字列リテラル
- 真偽値リテラル
- ヌルポインタリテラル
整数リテラル
整数リテラルは、文字通り整数型の値を表すリテラルです。何度も例に出した0
や42
といったものは、すべてint
型の整数リテラルです。
整数リテラルには前述のリストのように複数の種類があります。
10進整数リテラル
1〜9で始まり、その後に0〜9の数字が続くリテラルは10進整数リテラルです。
例えば、1
、2
、42
などが該当します。
ここで注意したいのは、01
や08
などは10進整数リテラルではないということです。特に後者は、正しい整数リテラルでもありません。
また、このルールに従うと、0
は10進整数リテラルではありません。
8進整数リテラル
では0
は何かと言うと、これは8進整数リテラルに分類されます。
8進整数リテラルは、0から始まり、その後に0〜7の数字が続くリテラルです。
数値を8進数で表すので、010
は10進リテラルにおける8
と同じことになります。07
の次は桁上がりをしてしまうので、08
や09
はありません。
10進整数リテラルと紛らわしいのであまり使われませんが、定義により0
は8進整数リテラルなので、このことを知らない人も知らず知らずの内に使っているのではないかと思います。
16進整数リテラル
先頭が0x
で、その後に0〜9もしくはa〜fが続くリテラルを16進整数リテラルと言います。アルファベットの大文字小文字は区別されません。
コンピューターが扱う数値は2進数なので、2の4乗を基本とする16進数は扱いやすい数値です。
例えば0xff
という16進整数リテラルは、10進整数リテラルの255
と同じ数値を表します。
2進整数リテラル
先頭が0b
で、その後に0もしくは1が続くリテラルを2進整数リテラルと言います。bは大文字でも小文字でも構いません。
例えば、0b1101
というリテラルは、10進整数リテラルでは13
になります。0b11111111
と、ここまで1を並べてようやく0xff
と同じ値です。
ビット演算の際には出現するかもしれません。
リテラルサフィックス
C++には整数型がいくつかあり、それぞれ表せる値の範囲も異なります。また、符号付きの型と、符号なしの型があります。
符号付きと符号なしというのはどういうことかと言うと、その整数型で負の数を表せるかどうかということです。負の数を表すことができるのが符号付きの型、負の数を表すことができないのが符号なしの型です。
標準で用意されている型は、以下のようになります15。
- 符号付きの型
signed char
short
int
long
long long
- 符号なしの型
unsigned char
unsigned short
unsigned int
unsigned long
unsigned long long
それぞれの型が表せる数値の範囲は、
signd char
≦ short
≦ int
≦ long
≦ long long
unsignd char
≦ unsigned short
≦ unsigned int
≦ unsigned long
≦ unsigned long long
となっていて、消費するメモリ量は、
signd char
= unsignd char
≦ short
= unsigned short
≦ int
= unsigned int
≦ long
= unsigned long
≦ long long
= unsigned long long
になります。
このうち、int
, long
, long long
, unsigned int
, unsigned long
, unsigned long long
に関しては、整数リテラルで表すことができます16。
実は今まで説明してきたリテラルはすべてint
型のリテラルでしたが、それぞれのリテラルに**サフィックス(suffix)**をつけることで、別の型のリテラルにできます。サフィックスには以下の種類があります。
-
u
:符号なしの型にします -
l
:long
型にします -
ll
:long long
型にします
サフィックスのu
とl
またはll
は同時に使うことができます。また、順番はどちらでも構いません。
以下、0
にサフィックスをつけた例と型名を挙げます。
サフィックス | 例 | 型名 |
---|---|---|
l | 0l |
long |
ll | 0ll |
long long |
u | 0u |
unsigned int |
ul, lu | 0ul |
unsigned long |
ull, llu | 0ull |
unsigned long long |
浮動小数点数リテラル
浮動小数点数は、整数では表すことの出来ない小数点以下の数や、逆にとても大きな数までを表すことのできる型です。
C++には、以下の3つの浮動小数点数型が用意されています。
float
double
long double
浮動小数点数リテラルは、以下のように表記することができます。
整数部分.小数部分e指数
eは大文字でも小文字でも構いません。
このうち、整数部分と小数部分のどちらかおよびeから後ろの部分は省略可能です。
例えば、以下のようなリテラルが浮動小数点数リテラルになります。
3.14
1.5e10
3.8e+2
2.3e-10
1.
.45
指数部分は、10を底とした指数を表します。例えば、1.5e10
であれば、
1.5 \times 10^{10}
を表すので、150億ということになります。
逆に2.3e-10
は
2.3 \times 10^{-10} = 2.3 / 10^{10}
ということになるので、0.00000000023を表していることになります。
省略した場合は、どの部分も0であると解釈されます。
1.
は1.0e0
, .45
は0.45e0
と同義です。
浮動小数点数リテラルにもサフィックスがあります。
浮動小数点数の場合は、f
もしくはl
のどちらかです。
f
を付けるとfloat
型、l
を付けるとlong double
型のリテラルになります。どちらも付いていなければdouble
型です。
文字リテラル
既に登場していますが、文字リテラルは'a'
のような''で囲まれた文字で表されるリテラルです。
文字には文字コードという番号が割り当てられていて、文字リテラルは実際にはその番号を表します。
文字を表す型の一つにchar
型がありますが、char
型の値は実際には整数です。
ただし、char型の文字がどのようなエンコーディングであるのかは、C++の規格では定められていません。
いわゆるASCII互換の文字コードでは'a'
はchar(0x61)
ですが、そうでない環境もあるかもしれません。
C++の文字型には下記のものがあります。
char
wchar_t
char16_t
char32_t
このうち、char16_t
とchar32_t
は、文字のエンコーディングが定められています。それぞれ、UTF-16とUTF-32です。
一方、char
とwchar_t
はどのようなエンコーディングの文字列か定められていません。また、wchar_t
はワイド文字型と呼ばれ、char
より大きいサイズの型であることは定められていますが、環境によって大きさが異なるため、環境依存性のないコードを書くときにはあまり使えません。
UTF-8の文字型はないのかって? ないです(2016/12/6現在)
頼むから……入れてくれ……。
さて、'a'
といったリテラルはchar
型の文字リテラルですが、wchar_t
, char16_t
, char32_t
の文字リテラルも存在します。
整数リテラルの場合、サフィックスを付けると別の型にできましたが、文字リテラルの場合は**プレフィックス(prefix)**をつけると別の文字型になります。
プレフィックスには以下の種類があります。
-
L
:wchar_t
型の文字を表す -
u
:char16_t
型の文字を表す -
U
:char32_t
型の文字を表す
プレフィックスは大文字と小文字を区別します。
L'a'
, u'a'
, U'a'
というリテラルはそれぞれwchar_t
, char16_t
, char32_t
型の値を表しています。
なお、C++17では文字リテラルにもu8
プレフィックスが導入される予定です。これは、UTF-8でエンコーディングされたchar
型の値1つに収まる文字を表します。
UTF-8は可変長エンコードで、1文字を表すのに2バイト以上必要な場合もあります。したがって、u8
プレフィックスを付けても表せない文字もあります17。
文字列リテラル
先程も触れましたが、文字列は文字の配列です。
文字型がchar
, wchar_t
, char16_t
, char32_t
と存在するので、それぞれの型に対する文字列がやはりあります。
文字列リテラルは、""
で囲まれた範囲で表されます。
"abcde"
というリテラルは、'a'
, 'b'
, 'c'
, 'd'
, 'e'
, '\0'
という6個のchar
型の値が並んだ配列を表します。
最後に追加されている'\0'
はnull文字です。CやC++では、文字列はnull終端されていることが前提になっていることが多いため、文字列リテラルもnull終端のために1文字追加されます。歴史的な経緯というやつです。
文字型と同じように、文字列型にもプレフィックスをつけることができます。
プレフィックスの種類も先ほど紹介したものと同じです。ただし、u8
プレフィックスは文字列リテラルにはC++11の時点で導入されているので、今でも使えます。
プレフィックスのない文字列リテラルと、L
プレフィックスが付けられたワイド文字リテラルは、文字型がそうであるのと同じようにエンコーディングの指定がありません。
u
とU
プレフィックスがついた文字列リテラルは、それぞれchar16_t
型とchar32_t
型で、UTF-16とUTF-32にエンコーディングされます18。
u8
プレフィックスがついた文字列リテラルは、文字型はchar
の配列ですが、エンコードだけはUTF-8になります。エンコードが違うのに、例えば文字列を引数として受け取っても型は同じなので判別できません。
標準化委員会の文字コードに関する理解が浅くて設計が悪いのだと思います。
raw文字列リテラル
C++のソースコードでは、\記号を使うことで**エスケープ(escape)**ができます。これは文字リテラルの中でも同様に使えます。これらをエスケープシーケンスといいます。19。
全てを列挙はしませんが、
- \n : 改行(LF)
- \r : 改行(CR)
- \t : タブ文字
- \\ : \記号そのもの
- \" : "記号
- \' : '記号
- \ooo : 文字コードの8進数指定
- \xhh : 文字コードの16進数指定
などがあります。中にはあまり使わない文字もありますが、"\\n"
あたりは使うことも多いのではないでしょうか。
エスケープシーケンスは特殊な文字を埋め込む等には便利なのですが、一方で、わざわざエスケープを行わなければいけなくなってしまうこともあり、文字列中に"
が多数出現する場合(例:JSON文字列を埋め込む時)など、可読性を下げてしまいます。
そのため、raw文字列リテラルというものが用意されています。
raw文字列リテラルは、R"(文字列)"
という形式で書かれた文字列で、この内部では\
文字がエスケープされることもありませんし、"
や改行などもそのまま書けます。
R"(
から始まったraw文字列は)"
で終了するので、)"
という文字の連続が現れることはできません。ただし、"
と(
の間、)
と"
の間には任意の同じ文字列を入れることができて、例えばR"a()a"
;といった書き方をすれば、)a"
という文字の並びが出てこない限り終了しません。
R"(abc\def)"
は"abc\\def"
と同じ文字列になりますし、
R"a(abc)"def)a"
は"abc)\"def"
と同じ文字列を表します。
const char * xhtml = u8R"(<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>Hello, World!</title></head>
<body><div>Hello, World!</div></body>
</html>)";
なんてものを書くこともできます。
上記の例でお分かりかと思いますが、raw文字列リテラルにもプレフィックスをつけることができます。意味は同じです。
文字列リテラルは配列なのでアドレスがあり、メモリ上のどこかに保存されています。実行中に書き換えられてはいけないので文字列リテラルの型にはconst
修飾子がつきます。const
修飾子が付いた型に対して書き換え操作を行おうとすると、コンパイルエラーになります。
文字列リテラルの型は、const 文字型[文字列長+1]
というものになります。+1は終端のnull文字の分です。
例えば、"abcde"
という文字列は、大抵文字コードならアルファベットはchar
型一個に収まるので、const char[6]
という型になります。
真偽値リテラル
C++には、真もしくは偽のどちらかの値しか取ることができない真偽値(boolean)型というものが用意されています。型名はbool
です。
bool
型は、真の状態をtrue
、偽の状態をfalse
という値で表します。真偽値リテラルは、まさにtrue
もしくはfalse
のどちらかです。
どちらかというと単なる識別子のように見えるかもしれませんが、true
やfalse
は変数ではなくリテラルです。ただの値なのでアドレスは存在しません。
nullポインタリテラル
C++には、nullポインターを表すリテラルが用意されています。nullポインターとは、有効なアドレスを示していないポインターのことです。
ポインターに何らかの変数のアドレスを入れる前に、nullポインターで初期化されることがあります。
nullポインタリテラルはnullptr
という文字列です。これもまた、真偽値リテラルと同じように変数ではないのでアドレスは存在しません。
cout
が何者なのか
cout
は、正確に名前空間まで含めて書くと、::std::cout
です。std
名前空間の中にあるので、標準ライブラリで定義されていることが分かると思います。
using namespace std;
を使っているので、名前空間は省略できます。
cout
が何かと言うと、まずこれは変数です。もっと言うなら、グローバル変数です。
cout
の型はstd::ostream
というものです。std::ostream
は、標準ライブラリで定義されたストリーム出力クラスです。
標準入出力・ファイル入出力・ネットワーク・ソケットなど、プログラムは様々な方法でデータを入出力します。そこら辺はOSが機能として提供してくれるので、一般的なアプリケーション開発では内部実装まで詳しく気にする必要はそれほどありません。
C++の標準ライブラリでは、そういった入出力操作を「ストリーム」というものに抽象化することで、出力先がなんであろうと同じ方法で出力をできるようにしています。
std::ostream
は**クラス(class)**です。厳密に言うなら実体化されたテンプレートクラスですが、テンプレートの解説はこの後に行うことにします。
クラスは、開発者が新しく型を定義するための機能です。これまでに出てきたint
やchar
といった型はC++の言語機能として予め用意された型ですが、クラスは開発者が定義を行うことができます。
クラスは、データメンバーとメンバー関数を持つことができます。
データメンバーは、クラスの内部状態を保存するための変数で、メンバー関数は、クラス型の値に対する操作を定義した関数です。
例えば、std::ostream
は出力を行うクラスなので、そのためのメンバー関数write
が用意されています。これを使って、一定の長さのデータを出力することができます。
cout.write("abcdef", 3);
クラスのメンバーには上記のように、.
演算子を利用してアクセスできます。
write
メンバー関数の第一引数は出力するデータ(const char
の配列)のアドレスで、第二引数は出力するデータの個数です。上記のような例であれば、"abc"の3文字が出力されることになります。
しかし、データの書き込みの度に、一々write
を呼び出して、データのポインタと長さを与えて、という操作を行うのは不便です。
そこで、ostream
クラスに対する<<
という演算子が用意されています。
オペレーターオーバーロードについて
これまでにも、**演算子(operator)**というものはいくつか登場しました。
int
型のような組み込み型には、あらかじめ用意された演算子があります。
それとは別に、開発者がクラスなどの独自定義した型に対し、演算子を定義することができます。これをオペレーターオーバーロードと言います。
演算子は、組み込み型のものを除けば、ちょっと特殊な関数もしくはメンバー関数であると考えて差し支えありません。
C++の関数は、同じ名前のものであっても、引数の型が異なれば複数個定義できます。これを**オーバーロード(overload)**と言います。オペレーターオーバーロードも同じです。
演算子をオーバーロードするには、
- クラスのメンバー関数として定義する
- フリー関数(グローバル関数)として定義する
という2種類の方法があります。
それぞれ、例を示します。
struct A {
int i;
A operator +(A rhs) { return {i + rhs.i}; }
};
struct B {
int i;
};
B operator +(B lhs, B rhs) { return { lhs.i + rhs.b }; }
struct
キーワードは、クラスを定義する際に使うキーワードの一つです。クラスを定義するキーワードにはもう一つ、class
キーワードもあります。struct
はもともとC言語から存在していたもので、struct
キーワードを使って定義されたクラスは一般的に構造体と呼ばれます。class
とstruct
は、デフォルトのアクセス指定子20がpublic
かprivate
かの違いがありますが、他に差はありません。
構造体A
と構造体B
は、それぞれint
型のデータメンバーi
を持っています。構造体A
は、メンバー関数としてoperator +(A)
を持っていて、構造体B
にはメンバー関数がありませんが、フリー関数のoperator +(B, B)
があります。
演算子が定義されていれば以下のように、
A a1{10}, a2{15};
A a3 = a1 + a2;
B b1{20}, b2{25};
B b3 = b1 + b2;
という風に、を使うことができます。ここでは他の演算子は定義されていないので、a1 - a2
のような計算はできず、コンパイルエラーとなります。
ちなみに、独自定義した演算子は、
a3 = a1.operator +(a2);
b3 = operator +(b1, b2);
などのように、関数として呼び出すこともできます。しかし、普通はこの方法が必要になることはないでしょう。
さて、オペレーターオーバーロードについてざっくり説明したところで、std::ostream
の<<
演算子について説明します。
std::ostream
の値に対して<<
演算子を利用すると、渡したオブジェクトが文字列として出力されます。
"Hello, World!"
なら元々文字列ですが、例えばint
型の値であっても、文字列に変換してから出力されます。
とは言え、C++のオブジェクトはいくらでも定義できるし、operator <<
が全てのオブジェクトに対して定義されている訳ではありません。operator <<
の定義されていない型の値を渡すとエラーになります。
ただし、operator <<
の定義を追加することはできます。
std::cout << "Hello, World!"
という式を実行すると、"Hello, World!"がそのまま表示されます。これは、std::ostream
がconst char*
型を受け取るoperator <<
を実装しているからです。
さて、std::ostream
に対する<<
演算子は、std::ostream
の**参照(reference)**を返します。独自に定義した演算子であっても、同じようにするべきだろうと思います。参照とは、ざっくりと言ってしまえば、コピーを行わずに変数の受け渡しを行うために用意されている機能です。
std::ostream
に対する<<
演算子は、出力操作を行った後、出力先となったstd::ostream
自身を返します。すると、<<
演算子で再び出力を行うことができます。
つまり、cout << "Hello, World!" << endl;
という文は、
(cout << "Hello, World!") << endl;
という風に書いても同じであり、もっと変形するなら
cout.operator <<("Hello, World!").operator <<(endl);
と書いてもよいのです。
冗長なのでそんな書き方誰もしないと思いますが。
マニピュレーターについて
cout << "Hello, World!"
までは解説しました。
しかし、この文はまだ終わっていません。後ろに<< endl;
があります。
<<
が演算子なのは既にお分かりだと思います。endl
についてですが、これもstd
名前空間の中で定義されている識別子です。ですので、using namespace std;
を使わない場合は、std::endl
と書かないといけません。
std::cout
が変数だったので、これも変数かな?と思うかも知れませんが、違います。
では何かといいますと、std::endl
は関数です。
std::ostream
には関数を受け取るoperator <<
が実装されていて、そこに渡す関数をマニピュレーターと言います。
マニピュレーターは標準で何種類も用意されていて、それぞれ別の挙動をします。そのため一概にどうとは言えないのですが、std::endl
に関して言えば、
- 改行文字を出力する
- 出力ストリームのバッファをフラッシュする
という操作を行います。
実は、std::ostream
は内部にバッファを持っていて、<<
演算子の呼び出し時にはバッファに書き込みを行い、出力は行わないことがあります。最終的にはいずれバッファの中身は出力されますが、バッファにデータが貯められている間は、出力先はデータを受け取ることができません。
そのため、flush
というメンバー関数が用意されていて、そちらを利用することでバッファの中身を強制的に出力させることができます。std::endl
マニピュレーターは、改行すると同時にflush
も実行してくれるので、例えば対話式のCLIツールを作る場合など、メッセージを一行出力してから入力待ちを行うようにしたりできます。
標準ライブラリには、他にも様々なマニピュレーターが用意されていて出力フォーマットを変えたりできますが、一番良く使うのはstd::endl
でしょう。
テンプレートについて
さて、std::endl
は関数だと言いましたが、実はこれはテンプレート関数です。
また、std::cout
の型もテンプレートクラスであると先ほど述べました。
C++の文字型にはchar
, wchar_t
, char16_t
, char32_t
の4種類があります。
それぞれ別々の型なので、それぞれの型の文字列に対して出力操作を考えると、普通に考えれば4種類の実装が必要ということになります。
しかし、文字列を出力するというのは要するに一定長のデータの配列を出力するということで、大体同じような操作になるわけです。
それなのに、4種類の実装を作るのは無駄が多いです。
ではどうするか。
テンプレートを使いましょう、というのがC++の解決策です。
テンプレートは、クラスや関数、変数などを、複数の型ごとに定義するための雛形です。
例えば、std::ostream
はこのように定義されています。
namespace std {
typedef basic_ostream<char> ostream;
}
typedef
というキーワードは、型の別名を定義するためのものです。std::ostream
は実はbasic_ostream<char>
の別名なのです。
そして、basic_ostream
というのが、テンプレートクラスです。
namespace std {
template<typename CharT, typename Traits = char_traits<CharT>>
class basic_ostream;
}
というのが、std::basic_ostream
の宣言です。
このbasic_ostream
をchar
型に関して実体化したものの別名が、std::ostream
です。
std::endl
もテンプレート関数です。
namespace std {
template<typename CharT, typename Traits>
basic_ostream<
CharT, Traits
> &endl(basic_ostream<CharT, Traits>&);
}
テンプレート関数は、引数によって型が推論されます。なので、std::endl(std::cout)
を実行すると、std::endl<char>
が呼ばれます。
また、逆に、std::ostream
の<<
演算子は、ostream& (ostream&)
型の関数へのポインタを受け取るので、std::cout << std::endl
を実行すると型が推論されて、std::cout.operator<<(std::endl<char>)
に解決されます。
さて、これでようやく、"Hello, World!"を出力するところまで来ました。あと少しです。
return 0;
普通の関数であれば、戻り値は呼び出し側で受け取って、その後何らかの処理に使うことができます。
ではmain
関数の場合は?
main
関数は、int
型の戻り値を返さなければなりません。
main
関数の終了後、C++のプログラムは自動的にstd::exit
関数を実行します。main
関数の戻り値は、ここで使用されます。
std::exit
関数は、
namespace std {
[[noreturn]] void exit(int status);
}
という関数です。新しく[[noreturn]]
というものが現れましたが、これは**属性(attribute)**というC++11から追加された機能です。[[noreteurn]]
属性は、「この関数は呼び出したら戻ってこない」ということを示しています。
std::exit
は、グローバル変数のデストラクタ呼び出しなどの終了処理を行った後、プログラムを終了させる関数です。std::exit
に渡されたint
型の値は、終了ステータスとしてホストプロセス21が受け取ります。
プログラムが正常終了したことを通知するには、0
もしくはEXIT_SUCCESS
マクロを渡す必要があります。また、異常終了した場合はEXIT_FAILURE
マクロを渡さなければいけません。それ以外の値がどう振る舞うかは実装依存です。注意しましょう。
通常、関数は戻り値を返すために、return
文を書かなければいけません。戻り値の型がvoid
である22場合以外は、何かを返す必要があるのに何も返さないと、コンパイルエラーになる場合が多いですし、未定義動作を引き起こす可能性があります。
ただし、main
関数に関してのみ、例外的にreturn
文を書かないことが許されていて、return
文を書かない場合は0
を返すのと同じ意味になります。
つまり、実はreturn 0;
は書かなくても良いです。あえて書いたのは、このことを説明するためでした。
'}'
関数ブロックの閉じ括弧です。
さすがにこれを説明する必要はあまりないかと思うので、あとがきでも書きます。
現在、12/6 22:34です。
超ギリギリです。すみません。思いの外大ボリュームになってしまい、いくら書いても解説が終わらない地獄に陥りました。
それでもかなり駆け足気味になったし分かりにくい部分も多いと思うので、いずれもっとちゃんと整理したいなと思っています。
そんな訳で、一応ここまでにしようかと思います。
それではみなさん、良いクリスマスを。
明日は、@wolf_cppさんの「深夜テンションで再帰について書くことになった」です。
-
私は、その最たる例が
using namespace std;
の濫用だと思っています。 ↩ -
ただし、これは実装によりけりで、必ずしもこれらの過程が一致するわけではありません。最終的に、正しく動作する実行ファイルが出来上がればいいわけですからね。 ↩
-
例:Win32 APIの
min
,max
マクロと標準ライブラリのstd::min
,std::max
関数 ↩ -
フリースタンディング環境を除く ↩
-
関数が呼び出される前に、暗黙的に別の型の値に変換されることはあります。 ↩
-
少なくともこの場合は ↩
-
テンプレートやinline関数など、例外もあります。 ↩
-
グローバル変数のコンストラクタとデストラクタや初期化式など、main関数が呼ばれる前や終わった後に行われる処理もあります。 ↩
-
ユーザー定義のデフォルトコンストラクタを持たない型は、初期化を行わない場合、内部にどのような値が入っているかは分かりません。パフォーマンス上の理由であえて未初期化状態のままにすることもありますが、基本的には必ず行うようにするべきです。 ↩
-
global変数やstatic変数など、初期化式を書かなくても自動的に
0
に初期化される場合もあります。 ↩ -
'a'
は"a"という文字を表しますが、実装定義なので厳密に互換性があるとは言えません。C++17で提案されているu8文字リテラルでは必ずUTF-8にエンコーディングされるので、そちらを使うべきですが、C++17はまだ標準化されていないので例に示していません。 ↩ -
C++に限った話ではありませんが ↩
-
関数名の探索時には、ADL(argument dependent lookup: 実引数依存の名前探索)というルールで名前空間内の関数が呼び出されることがあります。 ↩
-
正確には、この例では
x
やy
といった名前は探索されます。完全に名前を特定するためには、::x::a
という風に、グローバル空間から完全修飾された名前を指定する必要があります。 ↩ -
いくつかのルールにより同じ型に対して複数の記法がとれる(例:
short
をsigned short int
と書いても良い)ものもありますが、簡単にするために省略してあります。 ↩ -
signed char
,unsigned char
,short
,unsigned short
に関しては、リテラルで表すことはできません。int
型から代入を行う場合は暗黙的な型変換が行われますが、どうしてもそれらの型の値がほしい場合は、キャストするしかないでしょう。 ↩ -
もっとも、UTF-16やUTF-32でもサロゲートペアや合字など、表せない文字はあります。 ↩
-
文字列リテラルの場合はサロゲートペア利用文字等、単一の文字型で表せない文字も含めることができます。 ↩
-
勘違いされることが多いのですが、エスケープシーケンスは文字列外でも利用できます。 ↩
-
アクセス指定子は、メンバーがクラスの外から参照できるかどうかを示すキーワードです。
private
指定されたデータメンバーやメンバー関数は、クラス外から利用できません。 ↩ -
プログラムを実行した親プロセスのこと。 ↩
-
これは戻り値を返さないという意味になります ↩