# C++ヒッチハイクガイド

プログラミング言語の個々の機能の解説を理解するためには、まず言語の全体像を掴まなければならない。この章ではC++のさまざまなコードをひと通り観光していく。ここではコードの詳細な解説はしない。

# 最小のコード

以下はC++の最小のコードだ。

int main(){}

暗号のようなコードで訳がわからないが、これが最小のコードだ。mainというのはmain関数のことだ。C++ではプログラムの実行はmain関数から始まる。

ソースコードにコメントを記述して、もう少しわかりやすく書いてみよう。

int     // 関数の戻り値の型
main    // 関数名
()      // 関数の引数
{       // 関数の始まり
        // 実行される処理
}       // 関数の終わり

//から行末まではコメントだ。コメントには好きなことを書くことができる。

このコードと1つ前のコードは、コメントの有無を別にすれば何の違いもない。このコードで使っている、intとかmainとか記号文字の1つ1つをトークン(token)と呼ぶ。C++ではトークンの間に空白文字や改行文字をいくら使ってもよい。

なので、

int main(){ }

と書くこともできるし、

int    main    (    )    {   }

と書くこともできるし、紙に印刷する都合上とても読みづらくなるかもしれないが

int
main
(
)
{
}

と書くこともできる。

ただし、トークンの途中で空白文字や改行文字を使うことはできない。以下のコードは間違っている。

i
nt ma in(){}

# 標準出力

// helloと改行を出力するプログラム
int main()
{
    std::cout << "hello"s ;
}

標準出力はプログラムの基本だ。C++で標準出力する方法はいくつもあるが、<iostream>ライブラリを利用するものが最も簡単だ。

std::coutは標準出力を使うためのライブラリだ。

<<operator <<という演算子だ。C++では演算子にも名前が付いていて、例えば+operator +となる。<<も演算子の一種だ。

"hello"sというのは文字列で、二重引用符で囲まれた中の文字列が標準出力に出力される。

セミコロン;は文の区切り文字だ。C++では文の区切りは明示的にセミコロンを書く必要がある。ほかの言語では改行文字を文脈から判断して文の区切りとみなすこともあるが、C++では明示的に文の区切り文字としてセミコロンを書かなければならない。

セミコロンを書き忘れるとエラーとなる。

int main()
{
    // エラー! セミコロンがない
    std::cout << "error"s
}

複数の文を書いてみよう。

int main()
{
    std::cout << "one "s ;
    std::cout << "two "s ;
    std::cout << "three "s ;
}

C++はほかの多くの言語と同じように、逐次実行される。つまり、コードは書いた順番に実行される。そして標準出力のような外部への副作用は、実行された順番で出力される。このコードを実行した結果は以下のとおり。

one two three 

"three two one ""two one three "のような出力結果にはならない。

C++を含む多くの言語でa + b + cと書けるように、operator <<a << b << cと書ける。operator <<で標準出力をするには、左端はstd::coutでなければならない。

int main()
{
    std::cout << "aaa"s << "bbb"s << "ccc"s ;
}

出力はaaabbbcccとなる。

# 文字列

二重引用符で囲まれた文字列を、文字どおり文字列という。文字列には末尾にsが付くものと付かないものがある。これには違いがあるのだが、わからないうちはsを付けておいた方が便利だ。

int main()
{
    // これは文字列
    std::cout << "hello"s ;
    // これも文字列、ただし不便
    std::cout << "hello" ;
}

文字列リテラルの中にバックスラッシュを書くと、エスケープシーケンスとして扱われる。最もよく使われるのは改行文字を表す\nだ。

int main()
{
    std::cout << "aaa\nbbb\nccc"s ;
}

これは以下のように出力される。

aaa
bbb
ccc

バックスラッシュを文字列で使いたい場合は\\と書かなければならない。

int main()
{
    std::cout << "\\n is a new-line.\n"s ;
}

文字列は演算子operator +で「足す」ことができる。「文字列を足す」というのは、「文字列を結合する」という意味だ。

int main()
{
    std::cout << "hello"s + "world"s ;
}

# 整数と浮動小数点数

iostreamは文字列のほかにも、整数や浮動小数点数を出力できる。さっそく試してみよう。

int main()
{
    std::cout
        << "Integer: "s << 42 << "\n"s
        << "Floating Point: "s << 3.14 ;
}

-1230123といった数値を整数という。3.14のような数値を浮動小数点数という。

数値を扱えるのだから、計算をしてみたいところだ。C++は整数同士の演算子として、四則演算(+-*/)や剰余(%)をサポートしている。

int main()
{
    std::cout
        << 3 + 5 << " "s << 3 - 5 << " "s
        << 3 * 5 << " "s << 3 / 5 << " "s
        << 3 % 5 ;
}

演算子は組み合わせて使うこともできる。その場合、演算子*/%は演算子+-よりも優先される。

int main()
{
    // 7
    std::cout << 1 + 2 * 3 ;
}

この場合、まず2*3が計算され6となり、1+6が計算され7となる。

1+2の方を先に計算したい場合、括弧()で囲むことにより、計算の優先度を変えることができる。

int main()
{
    // 9
    std::cout << (1 + 2) * 3 ;
}

これは1+2が先に計算され3となり、3*3が計算され9となる。

浮動小数点数同士でも四則演算ができる。剰余はできない。

int main()
{
    std::cout
        << 3.5 + 7.11 << " "s << 3.5 - 7.11 << " "s
        << 3.5 * 7.11 << " "s << 3.5 / 7.11 ;
}

では整数と浮動小数点数を演算した場合どうなるのだろう。さっそく試してみよう。

int main()
{
    std::cout << 1 + 0.1 ;
}

結果は1.1だ。整数と浮動小数点数を演算した結果は浮動小数点数になる。

そういえばC++には文字列もあるのだった。文字列と文字列は足すことができる。数値と数値も足すことができる。では数値と文字列を足すとどうなるのだろう。

int main()
{
    std::cout << 1 + "234"s ;
}

この結果はエラーになる。

いや待て、C++には末尾にsを付けない文字列もあるのだった。これも試してみよう。

int main()
{
    std::cout << 1 + "234" ;
}

結果はなんと34になるではないか。C++では謎の数学により1 + "234" = "34"であることが判明した。この謎はいずれ解き明かすとして、いまは文字列には必ず末尾にsを付けることにしよう。その方が安全だ。

# 変数(variable)

さあどんどんプログラミング言語によくある機能を見ていこう。次は変数だ。

int main()
{
    // 整数の変数
    auto answer = 42 ;
    std::cout << answer << "\n"s ;
    // 浮動小数点数の変数
    auto pi = 3.14 ;
    std::cout << pi << "\n"s ;

    // 文字列の変数
    auto question = "Life, The Universe, and Everything."s ;
    std::cout << question ;
}

変数はキーワードautoに続いて変数名を書き、=に続いて値を書くことで宣言できる。変数の宣言は文なので、文末にはセミコロンが必要だ。

auto 変数名 =;

変数名はキーワード、アンダースコア(_)で始まる名前、アンダースコア2つ(__)を含む名前以外は自由に名付けることができる。

変数の最初の値は、= 値の代わりに(値){値}と書いてもよい。

int main()
{
    auto a = 1 ;
    auto b(2) ;
    auto c{3} ;
}

この=, (), {}による変数の初期値の指定を、初期化という。

変数は使う前に宣言しなければならない。

int main()
{
    // エラー、名前xは宣言されていない
    std::cout << x ;
    auto x = 123 ;
}

変数の値は初期化したあとにも演算子=で変更できる。これを代入という。

int main()
{
    // 変数の宣言
    auto x
    // 初期化
    = 123 ;

    // 123
    std::cout << x ;

    // 代入
    x = 456 ;

    // 456
    std::cout << x ;

    // もう一度代入
    x = 789 ;
    // 789
    std::cout << x ;
}

代入演算子operator =は左辺に変数名を、右辺に代入する値を書く。面白いこととして、右辺には代入する変数名そのものを書ける。

int main()
{
    auto x = 10 ;
    x = x + 5 ;

    // 15
    std::cout << x ;
}

operator =は「代入」という意味で、「等号」という意味ではないからだ。x=x+5は、「xx+5は等しい」という独創的な数学上の定義ではなく、「変数xに代入前の変数xの値に5を加えた数を代入する」という意味だ。

変数のいまの値に対して演算した結果を変数に代入するという処理はとてもよく使うので、C++にはx = x + aと同じ意味で使える演算子、operator +=もある。

int main()
{
    auto x = 1 ;
    // x = x + 5と同じ
    x += 5 ;
}

operator +=と同様に、operator -=, operator *=, operator /=, operator %=もある。

C++の変数は、専門用語を使うと「静的型付け」になる。静的型付けと対比されるのが「動的型付け」だ。もっと難しく書くと、動的型付け言語の変数は、C++で言えば型情報付きのvoid *型の変数のような扱いを受ける。

C++の変数にはがある。というのは値の種類を表す情報のことだ。

例えば、以下は変数が動的型付けの言語JavaScriptのコードだ。

var x = 1 ;
x = "hello" ;
x = 2 ;

JavaScriptではこのコードは正しい。変数xは数値型であり、文字列型に代わり、また数値型に戻る。

C++ではこのようなコードは書けない。

int main()
{
    auto x = 1 ;
    // エラー
    x = "hello"s ;
    x = 2 ;
}

C++では、変数xは整数型であり、文字列型に変わることはない。整数型の変数に文字列型を代入しようとするとエラーとなる。

C++では型に名前が付いている。整数型はint、浮動小数点数型はdouble、文字列型はstd::stringだ。

int main()
{
    // iはint型
    auto i = 123 ;
    // dはdouble型
    auto d = 1.23 ;
    // sはstd::string型
    auto s = "123"s ;
}

実は変数の宣言でautoと書く代わりに、具体的な型を書いてもよい。

int main()
{
    int i           = 123 ;
    double d        = 1.23 ;
    std::string s   = "123"s ;
}

整数型(int)と浮動小数点数型(double)はそれぞれお互いの型の変数に代入できる。ただし、変数の型は変わらない。単に一方の型の値がもう一方の型の値に変換されるだけだ。

int main()
{
    // 浮動小数点数型を整数型に変換
    int a = 3.14 ;
    // 3
    std::cout << a << "\n"s ;

    // 整数型を浮動小数点数型に変換
    double d = 123 ;
    // 123
    std::cout << d ;
}

浮動小数点数型を整数型に変換すると、小数部が切り捨てられる。この場合、3.14の小数部0.14が切り捨てられ3となる。0.9999も小数部が切り捨てられ0になる。

int main()
{
    int i = 0.9999 ;
    // 0
    std::cout << i ;
}

整数型を浮動小数点数型に変換すると、値を正確に表現できる場合はその値になる。正確に表現できない場合は近い値になる。

int main()
{
    double d = 1234567890 ;
    // 正確に表現できるかどうかわからない
    std::cout << d ;
}

整数型と浮動小数点数型の挙動についてはあとの章で詳しく解説する。また、これ以外にも型はいくらでもあるし、読者が新しい型を作り出すこともできる。これもあとの章で詳しく解説する。

# 関数(function)

「変数ぐらい知っている。さっさと教えてもらいたい。どうせC++の関数は書きづらいのだろう」と考える読者の皆さん、お待たせしました。こちらがC++の関数でございます。

int main()
{
    // 関数
    auto print = [](auto x)
    {
        std::cout << x << "\n"s ;
    } ;

    // 関数呼び出し
    print(123) ;
    print(3.14) ;
    print("hello") ;
}

C++では関数も変数として扱える。auto print =までは変数だ。変数の初期化として関数を書いている。より正確にはラムダ式と呼ばれる関数を値として書くための文法だ。

ラムダ式は以下のような文法を持つ。

[] // ラムダ式導入部
() // 引数
{} // 本体

ラムダ式は[]で始まり、()の中に引数を書き、{}の中の文が実行される。

例えば以下は引数を2回標準出力する関数だ。

int main()
{
    auto twice = [](auto x)
    {
        std::cout << x << " "s << x << "\n"s ;
    } ;

    twice(5) ;
}

引数はauto 引数名で受け取れる。引数を複数取る場合は、カンマ,で区切る。

int main()
{
    auto print_two = []( auto x, auto y )
    {
        std::cout << x << " "s << y << "\n"s ;
    } ;

    print_two( 1, 2 ) ;
    print_two( "Pi is", 3.14 ) ;
}

引数を取らないラムダ式を書く場合は、単に()と書く。

int main()
{
    auto no_args = []()
    {
        std::cout << "Nothing.\n" ;
    } ;

    no_args() ;
}

関数は演算子operator ()を関数の直後に書いて呼び出す。これが演算子であるというのは少し不思議な感じがするが、C++では紛れもなく演算子だ。operator +とかoperator -などと同じ演算子だ。

int main()
{
    // 何もしない関数
    auto func = [](){} ;

    // operator ()の適用
    func() ;
    // これもoperator ()
    func    (   ) ;
}

演算子operator ()は、ラムダ式そのものに対して適用することもできる。

int main()
{
    // 変数fをラムダ式で初期化
    auto f = [](){} ;
    // 変数fを関数呼び出し
    f() ;

    // ラムダ式を関数呼び出し
    [](){}() ;
}

このコードを見ると、operator ()が単なる演算子であることがよくわかるだろう。[](){}がラムダ式でその直後の()が関数呼び出し演算子だ。

関数は値を返すことができる。関数から値を返すには、return文を使う。

int main()
{
    auto plus = []( auto x, auto y )
        { return x + y ; } ;

    std::cout
        << plus( 1, 2 ) << "\n"s
        << plus( 1.5, 0.5 ) << "\n"s
        << plus( "123"s, "456"s) ;
}

関数はreturn文を実行すると処理を関数の呼び出し元に返す。

int main()
{
    auto f = []()
    {
        std::cout << "f is called.\n" ;
        return 0 ; // ここで処理が戻る
        std::cout << "f returned zero.\n" ;
    } ;

    auto result = f() ;
}

これを実行すると以下のようになる。

$ make
f is called.

return文以降の文が実行されていないことがわかる。

# 本当の関数

実はラムダ式は本当のC++の関数ではない。本当の関数はとても書きづらいので心して読むべきだ。

読者は本書の冒頭で使ったmain関数という言葉を覚えているだろうか。覚えていないとしても、サンプルコードに必ずと言っていいほど出てくるmainという名前は気になっていたことだろう。

int main(){}

これを見ると、聡明な読者はラムダ式と似通ったところがあることに気付くだろう。

[](){}

末尾の(){}が同じだ。これは同じ意味だ。()は関数の引数で、{}は関数の本体だ。

では残りの部分はどうだろうか。intは関数の戻り値の型、mainは関数の名前だ。

C++の本当の関数は以下のような文法で定義される。

int     // 戻り値の型
main    // 関数名
()      // 関数の引数
{}      // 関数の本体

試しに、int型の引数を2つ取り足して返す関数plusを書いてみよう。

int plus( int x, int y )
{
    return x + y ;
}

int main()
{
    auto x = plus( 1, 2 ) ;
}

では次に、double型の引数を2つ取り足して返す関数plusを書いてみよう。

double plus( double x, double y )
{
    return x + y ;
}

int main()
{
    auto x = plus( 1.0, 2.0 ) ;
}

最後のstd::string型の引数を2つ取り足して返す関数plusは読者への課題とする。

これがC++の本当の関数だ。C++の関数では、型をすべて明示的に書かなければならない。型を間違えるとエラーだ。

しかも、C++の関数は、戻り値の型を正しく返さなければならない。

int f()
{
    // エラー、return文がない
}

もし、何も値を返さない関数を書く場合は、どの値でもないという特別な型、void型を関数の戻り値の型として書かなければならないという特別なルールまである。

void f()
{
    // OK
}

ただし、戻り値の型については、具体的な型の代わりにautoを書くこともできる。その場合、return文で同じ型さえ返していれば、気にする必要はない。

// void
auto a() { }
// int
auto b() { return 0 ; }
// double
auto c() { return 0.0 ; }
// std::string
auto d() { return ""s ; }

// エラー
// return文の型が一致しない。
auto e()
{
    return 0 ;
    return 0.0 ;
}