From 7a31947088d863a9fb945bfb96fbee8dd948cc2a Mon Sep 17 00:00:00 2001 From: Ryou Ezoe Date: Thu, 19 Jul 2018 19:28:43 +0900 Subject: [PATCH] fix example code. --- 003-guide-to-c++.md | 4 +- ...the-restaurant-at-the-end-of-the-branch.md | 2 +- 007-standard-input.md | 4 +- 008-loop.md | 16 +- 009-vector.md | 2 +- 010-debug-printf.md | 2 +- 011-integer.md | 17 +- 012-floating-point.md | 8 +- 013-names.md | 8 +- 014-iterator.md | 15 +- Makefile | 2 +- bin/sample-code-checker.cpp | 14 +- docs/index.html | 678 +++++++++++++++++- 13 files changed, 718 insertions(+), 54 deletions(-) diff --git a/003-guide-to-c++.md b/003-guide-to-c++.md index be91da1..24dacc1 100644 --- a/003-guide-to-c++.md +++ b/003-guide-to-c++.md @@ -355,7 +355,7 @@ int main() x = x + 5 ; // 15 - std::cout << c ; + std::cout << x ; } ~~~ @@ -700,7 +700,7 @@ void f() ただし、戻り値の型については、具体的な型の代わりに`auto`を書くこともできる。その場合、return文で同じ型さえ返していれば、気にする必要はない。 -~~~cpp +~~~c++ // void auto a() { } // int diff --git a/005-the-restaurant-at-the-end-of-the-branch.md b/005-the-restaurant-at-the-end-of-the-branch.md index d693daa..db80cb0 100644 --- a/005-the-restaurant-at-the-end-of-the-branch.md +++ b/005-the-restaurant-at-the-end-of-the-branch.md @@ -611,7 +611,7 @@ int main() { std::cout << std::boolalpha ; auto print = [](auto b) - { std::cout << b << "\n"s } ; + { std::cout << b << "\n"s ; } ; print( true == true ) ; // true print( true == false ) ; // false diff --git a/007-standard-input.md b/007-standard-input.md index 6aaf3a9..243a4da 100644 --- a/007-standard-input.md +++ b/007-standard-input.md @@ -23,7 +23,7 @@ int main() double mass = 73.0 ; // BMIの計算 - dublle bmi = mass / (height*height) ; + double bmi = mass / (height*height) ; // BMIの出力 std::cout << "BMI=" << bmi << "\n"s ; @@ -90,7 +90,7 @@ int main() double mass = 80.0 ; // BMIの計算 - dublle bmi = mass / (height*height) ; + double bmi = mass / (height*height) ; // BMIの出力 std::cout << "BMI=" << bmi << "\n"s ; diff --git a/008-loop.md b/008-loop.md index b78ad06..6980b4c 100644 --- a/008-loop.md +++ b/008-loop.md @@ -304,7 +304,7 @@ int f( int x ) { return x ; } 関数の定義は一度しか書けない。 -~~~cpp +~~~c++ // 定義 void f() {} // エラー、再定義 @@ -313,7 +313,7 @@ void f() {} なぜ関数は宣言と定義とに別れているかというと、C++では名前は宣言しないと使えないためだ。 -~~~cpp +~~~c++ int main() { // エラー @@ -875,7 +875,7 @@ int main() ~~~cpp int main() { - std::cout << 4\t8\t12\n5\t10\t15"s ; + std::cout << "4\t8\t12\n5\t10\t15"s ; } ~~~ @@ -1087,12 +1087,12 @@ int main() 変数もカンマで複数宣言できると知った読者は、以下のように書きたくなるだろう。 -~~~cpp +~~~c++ int main() { for ( int a = 1, b = 1 ; a <= 9 ; - +++a, ++b, + ++a, ++b, std::cout << "\n"s ) std::cout << a*b << "\t"s ; @@ -1106,7 +1106,7 @@ int main() ~~~cpp int main() { - bool b = true + bool b = true ; // for文による変数宣言は使わない for ( ; b ; b = false ) std::cout << "hello"s ; @@ -1524,7 +1524,7 @@ int main() 負数と正数の違いを考えるのは面倒だ。ここでは正数を引数として与えると10進数から2進数へ変換した答えを返してくる魔法のような関数`solve`をすでに書き終えたと仮定しよう。我々はまだ関数`solve`を書いていないが、その問題は未来の自分に押し付けよう。 -~~~cpp +~~~c++ // 1,0のみを使った10進数から // 2進数へ変換する関数 int solve( int n ) ; @@ -1723,7 +1723,7 @@ int main() ~~~cpp void f() { } // gに戻る -vopd g() { f() ; } // mainに戻る +void g() { f() ; } // mainに戻る int main() { g() ; } ~~~ diff --git a/009-vector.md b/009-vector.md index 1a3df82..e6acf46 100644 --- a/009-vector.md +++ b/009-vector.md @@ -40,7 +40,7 @@ int main() std::vector vi ; // 浮動小数点数型doubleの値を保持するvector - ~std::vector vd ; + std::vector vd ; // 文字列型std::stringの値を保持するvector std::vector vs ; diff --git a/010-debug-printf.md b/010-debug-printf.md index 1d27c63..85d0e05 100644 --- a/010-debug-printf.md +++ b/010-debug-printf.md @@ -172,7 +172,7 @@ debug: v = { 1, 8, 2, 5, 6, 9, 4, 1, 7, } 大小比較も問題ないようだ。では最終的に見つけた最も小さい値は、本当に最も小さい値だろうか。 -~~~cpp +~~~c++ // 最小値を探すループ for ( std::size_t index = head+1 ; index != size ; ++index ) { diff --git a/011-integer.md b/011-integer.md index e6d8432..d50e79a 100644 --- a/011-integer.md +++ b/011-integer.md @@ -39,7 +39,7 @@ int octal = 0123 ; ~~~cpp // 10進数で5 -int binary = 0b1010 +int binary = 0b1010 ; // 0bと0Bは同じ int a = 0B1010 ; @@ -398,23 +398,14 @@ auto a = 123ll ; // long long int auto b = 123LL ; // unsigned long long int -auto c = 123ull +auto c = 123ull ; ~~~ ### short int型 `short int型`は`int型`より小さい範囲の値を扱う整数型だ。`long`, `long long`と同様に、`unsigned short int`型もある。単に`short`と書くと、`short int`と同じ意味になる。 -整数リテラルでは末尾にs/Sと書くと`short int型`になる。 - -~~~cpp -// short int -auto a = 123s ; -// short int -auto b = 123S ; -// unsigned short int ; -auto c = 123us ; -~~~ +整数リテラルで`short int`型を表現する方法はない。z2 ### char型 @@ -631,7 +622,7 @@ int main() int main() { int x = 123 ; - short y = static_cast(x) ; + short y = static_cast(x) ; } ~~~ diff --git a/012-floating-point.md b/012-floating-point.md index b34bac2..6388c98 100644 --- a/012-floating-point.md +++ b/012-floating-point.md @@ -59,13 +59,13 @@ int main() 変数aの値は1万、変数bの値は1万分の1だ。変数cの値はa+bで10000.0001となるはずだが結果はどうだろう。 -~~~cpp +~~~ 10000 0.0001 10000 ~~~ -変数cの値は10000.0001ではない。 +変数cの値は10000.0001ではない。この謎は浮動小数点数を学べば明らかになる。 ## 浮動小数点数リテラル @@ -111,7 +111,7 @@ $$a \times 10^{b}$$ auto a = 1.23456e2 ; auto b = 123456e-3 ; auto c = 123.456e0 ; -auto d = 123.456E0 +auto d = 123.456E0 ; ~~~ この文法は、`a`と`b`をe/Eで挟むことによって浮動小数点数の値を指定する。 @@ -237,7 +237,7 @@ int main() bool b = NaN != 0.0 ; bool c = NaN == NaN ; bool d = NaN != NaN ; - bool e = Nan < 0.0 ; + bool e = NaN < 0.0 ; } ~~~ diff --git a/013-names.md b/013-names.md index d6e2607..1a0242d 100644 --- a/013-names.md +++ b/013-names.md @@ -109,7 +109,7 @@ C++では多くの名前は宣言と定義に分かれている。宣言と定 int plus_one( int x ) ; // 関数の定義 -inf plus_one( int x ) // 宣言部分 +int plus_one( int x ) // 宣言部分 // 定義部分 // 関数の本体 { @@ -150,7 +150,7 @@ int main() } // 定義 -int plus_one( int x ) ; +int plus_one( int x ) { return x + 1 ; } @@ -427,7 +427,7 @@ int g() 名前空間エイリアスを名前空間の中で宣言すると、宣言以降の名前空間内で有効になる。 -~~~cpp +~~~c++ namespace ns { namespace A { int x { } ; } namespace B = A ; @@ -753,7 +753,7 @@ void f() 名前空間も同じだ。 -~~~cpp +~~~c++ // グローバル名前空間スコープ namespace ns { diff --git a/014-iterator.md b/014-iterator.md index 41489e9..3253b6b 100644 --- a/014-iterator.md +++ b/014-iterator.md @@ -124,8 +124,8 @@ int main() ++x ; // xは1番目の要素を指す。 - bool b1 = (x == y) ; // false - bool b2 = (x != y) ; // true + bool b3 = (x == y) ; // false + bool b4 = (x != y) ; // true } ~~~ @@ -169,11 +169,10 @@ int main() std::vector v = {1,2,3,4,5} ; auto i = std::end(v) ; - --i ; // 最後の要素を指す - *i ; // 5 - ++i ; 最後の次の要素を指す - *i ; // エラー - + --i ; // 最後の要素を指す + *i ; // 5 + ++i ; // 最後の次の要素を指す + *i ; // エラー } ~~~ @@ -541,7 +540,7 @@ int main() for ( auto i = std::begin(v), j = std::end(v) ; i != j ; ++i ) { - std::cout << i ; + std::cout << *i ; } } ~~~ diff --git a/Makefile b/Makefile index 5b1b556..e39c4ce 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ retest : test-tool test-tool : bin/sample-code-checker bin/sample-code-checker : bin/sample-code-checker.cpp - g++ -D _ISOC11_SOURCE -std=c++14 --pedantic-errors -Wall -pthread -O2 -o bin/sample-code-checker bin/sample-code-checker.cpp + g++ -D _ISOC11_SOURCE -std=c++17 --pedantic-errors -Wall -pthread -O2 -o bin/sample-code-checker bin/sample-code-checker.cpp book : docs/index.html diff --git a/bin/sample-code-checker.cpp b/bin/sample-code-checker.cpp index 31d0199..4b113ab 100644 --- a/bin/sample-code-checker.cpp +++ b/bin/sample-code-checker.cpp @@ -100,7 +100,7 @@ std::string create_temp_source_file( std::string sample_code ) bool compile_check_gcc( std::string const & file_name ) { - std::string command = std::string("g++ -fsyntax-only -D _ISOC11_SOURCE -std=c++1z --pedantic-errors -Wall -pthread -include /tmp/sample-code-checker/all.h ") + file_name ; + std::string command = std::string("g++ -fsyntax-only -D _ISOC11_SOURCE -std=c++17 --pedantic-errors -Wall -pthread -include /tmp/sample-code-checker/all.h ") + file_name ; int result = system( command.c_str() ) ; @@ -110,7 +110,7 @@ bool compile_check_gcc( std::string const & file_name ) bool compile_check_clang( std::string const & file_name ) { - std::string command = std::string("clang++ -fsyntax-only -D _ISOC11_SOURCE -std=c++1z -stdlib=libc++ --pedantic-errors -Wall -pthread -include-pch /tmp/sample-code-checker/all.h.pch ") + file_name ; + std::string command = std::string("clang++ -fsyntax-only -D _ISOC11_SOURCE -std=c++17 -stdlib=libc++ --pedantic-errors -Wall -pthread -include-pch /tmp/sample-code-checker/all.h.pch ") + file_name ; int result = system( command.c_str() ) ; @@ -123,9 +123,9 @@ bool compile_check_clang( std::string const & file_name ) bool compile_check( std::string const & file_name ) { return - compile_check_gcc( file_name ) - && - compile_check_clang( file_name ) ; + compile_check_gcc( file_name ) ; + // && + //compile_check_clang( file_name ) ; } // check if a given sample code is a well-formed C++ code fragment @@ -188,9 +188,9 @@ bool prepare_compile() precompiled_header.write( all_std_headers.c_str(), all_std_headers.size() ) ; } - int r1 = std::system("g++ -D _ISOC11_SOURCE -std=c++1z --pedantic-errors -Wall -pthread -x c++-header -o /tmp/sample-code-checker/all.h.gch /tmp/sample-code-checker/all.h") ; + int r1 = std::system("g++ -D _ISOC11_SOURCE -std=c++17 --pedantic-errors -Wall -pthread -x c++-header -o /tmp/sample-code-checker/all.h.gch /tmp/sample-code-checker/all.h") ; - int r2 = std::system("clang++ -D _ISOC11_SOURCE -std=c++14 --pedantic-errors -Wall -pthread -x c++-header -o /tmp/sample-code-checker/all.h.pch /tmp/sample-code-checker/all.h") ; + int r2 = std::system("clang++ -D _ISOC11_SOURCE -std=c++17 --pedantic-errors -Wall -pthread -x c++-header -o /tmp/sample-code-checker/all.h.pch /tmp/sample-code-checker/all.h") ; return r1 == 0 && r2 == 0 ; } diff --git a/docs/index.html b/docs/index.html index f9ef69a..0057e78 100644 --- a/docs/index.html +++ b/docs/index.html @@ -245,7 +245,16 @@

2018-02-27

  • なんでもイテレーター
  • イテレーターと添字の範囲
  • -
  • ポップカルチャーリファレンス
  • +
  • lvalueリファレンスとconst
  • +
  • アルゴリズム
  • Cプリプロセッサー
    • #includeディレクティブ
    • #define
        @@ -5927,9 +5936,674 @@

        イテレーターと添字の範 } }

        変数vは空なのでi != jはfalseとなり、for文の中の文は一度も実行されない。

        -

        ポップカルチャーリファレンス

        +

        lvalueリファレンスとconst

        ポップカルチャーリファレンスというのは流行の要素をさり気なく作品中に取り入れることで、流行作品を知っている読者の笑いを誘う手法である
         -- キャプテン・オブビウス ポップカルチャーリファレンスについて
        +

        lvalueリファレンス

        +

        変数に変数を代入すると、代入元の値が代入先にコピーされる。代入先の値を変更しても、コピーされた値が変わるだけで、代入元には一切影響がない。

        +
        int main()
        +{
        +    int a = 1 ;
        +    int b = 2 ;
        +
        +    b = a ;
        +    // b == 1
        +
        +    b = 3 ;
        +    // a == 1
        +    // b == 3
        +}
        +

        これは関数も同じだ。

        +
        void assign_3( int x )
        +{
        +    x = 3 ;
        +}
        +
        +int main()
        +{
        +    int a = 1 ;
        +    assign_3( a ) ;
        +
        +    // a == 1
        +}   
        +

        しかし、時には変数の値を直接書き換えたい場合がある。この時lvalueリファレンス(reference)が使える。lvalueリファレンスは変数に&を付けて宣言する

        +
        int main()
        +{
        +    int a = 1 ;
        +    int & ref = a ;
        +
        +    ref = 3 ;
        +
        +    // a ==  3  
        +    // refはaなので同じく3
        +}
        +

        この例で、変数refは変数aへの参照(リファレンス)なので、変数aと同じように使える。

        +

        lvalueリファレンスは必ず初期化しなければならない。

        +
        int main()
        +{
        +    // エラー
        +    int & ref ;
        +}
        +

        lvalueリファレンスは関数でも使える。

        +
        void f( int & x )
        +{
        +    x = 3 ;
        +}
        +
        +int main()
        +{
        +    int a = 1 ;
        +    f( a ) ;
        +
        +    // a == 3
        +}
        +

        選択ソートで2つの変数の値を交換する必要があったことを覚えているだろうか。

        +
        int main()
        +{
        +    std::vector<int> v = {3,2,1,4,5} ;
        +
        +    // 0番目と2番目の要素を交換したい
        +    auto temp = v.at(0) ;
        +    v.at(0) = v.at(2) ;
        +    v.at(2) = temp ;
        +}
        +

        いちいち交換のために別の変数tempを作って3回代入を書くのは面倒だ。これを関数にしてしまいたい。

        +
        // 値を交換
        +swap( v.at(0), v.at(2) ) ;
        +

        このような関数swapは普通に書くことはできない。

        +
        // この実装は正しくない
        +auto swap = []( auto a, auto b )
        +{
        +    auto temp = a ;
        +    a = b ;
        +    b = temp ;
        +} ;
        +

        この実装では、変数は単にコピーされるだけなので、関数の呼び出し元には何の影響もない。

        +

        これをlvalueリファレンスに変えると、関数の呼び出し元の変数の値を交換する関数swapが作れる。

        +
        // lvalueリファレンス
        +auto swap = []( auto & a, auto & b )
        +{
        +    auto temp = a ;
        +    a = b ;
        +    b = temp ;
        +} ;
        +

        C++の標準ライブラリにはstd::swapがあるので、読者はわざわざこのような関数を自作する必要はない。

        +
        int main()
        +{
        +    int a = 1 ;
        +    int b = 2 ;
        +
        +    std::swap( a, b ) ;
        +
        +    // a == 2
        +    // b == 1
        +}
        +

        ところで、この章では一貫してlvalueリファレンスと書いているのに気がついただろうか。lvalueとは何なのか、lvalueではないリファレンスはあるのか。その疑問は後の章で解決する。

        +

        const

        +

        値を変更したくない変数は、constをつけることで変更を禁止できる。

        +
        int main()
        +{
        +    int x = 0 ;
        +    x = 1 ; // OK、変更できる
        +
        +    const int y = 0 ;
        +    y = 0 ; // エラー、変更できない。
        +}
        +

        constはちょっと文法が変わっていて混乱する。例えば、const intでもint constでも意味が同じだ。

        +
        int main()
        +{
        +    // 意味は同じ
        +    const int x = 0 ;
        +    int const y = 0 ;
        +}
        +

        constはlvalueリファレンスと組み合わせることができる。

        +
        int main()
        +{
        +    int x = 0 ;
        +
        +    int & ref = x ;
        +    // OK
        +    ++ref ;
        +
        +    const int & const_ref = x ;
        +
        +    // エラー
        +    ++const_ref ;
        +}
        +

        constは本当に文法が変わっていて混乱する。const int &int const &は同じ意味だが、int & constはエラーになる。

        +
        int main()
        +{
        +    int a = 0 ;
        +
        +    // OK、意味は同じ
        +    const int & b = a ;
        +    int const & c = a ;
        +
        +    // エラー
        +    int & const d = a ;
        +}
        +

        これはとても複雑なルールで決まっているので、こういうものだと諦めて覚えるしかない。

        +

        constがついていない型のオブジェクトをconstなlvalueリファレンスで参照することができる。

        +
        int main()
        +{
        +    // constのついていない型のオブジェクト
        +    int x = 0 ;
        +
        +    // OK
        +    int & ref = x ;
        +    // OK、constはつけてもよい
        +    const int & cref = x ;
        +}
        +

        constのついている型のオブジェクトをconstのついていないlvalueリファレンスで参照することはできない。

        +
        int main()
        +{
        +    // constのついている型のオブジェクト
        +    const int x = 0 ;
        +
        +    // エラー、constがない。
        +    int & ref = x ;
        +
        +    // OK、constがついている
        +    const int & cref = x ;
        +}
        +

        constのついているlvalueリファレンスは何の役に立つのかというと、関数の引数を摂るときに役に立つ。

        +

        例えば以下のコードは非効率的だ。

        +
        void f( std::vector<int> v )
        +{
        +    std::cout << v.at(1234) ;
        +}
        +
        +int main()
        +{
        +    // 10000個の要素を持つvector
        +    std::vector<int> v(10000) ;
        +
        +    f( v ) ;
        +}
        +

        なぜかというと、関数の引数に渡すときに、変数vはコピーされるからだ。

        +

        リファレンスを使うと不要なコピーをしなくて済む。

        +
        void f( std::vector<int> & v )
        +{
        +    std::cout << v.at(1234) ;
        +}
        +

        しかし、リファレンスで受け取ると、うっかり変数を変更してしまった場合、その変更が関数の呼び出し元に反映されてしまう。

        +
        // 値を変更するかもしれない
        +void f( std::vector<int> & v ) ;
        +
        +int main()
        +{
        +    // 要素数10000のvector
        +    std::vector<int> v(10000) ;
        +
        +    f(v) ;
        +
        +    // 値は変更されているかもしれない
        +}
        +

        このとき、constなlvalueリファレンスを使うと、引数に取った値を変更しないことを保証できる。

        +
        void f( std::vector<int> const & v ) ;
        +

        アルゴリズム

        +

        アルゴリズムは難しい。アルゴリズム自体の難しさに加え、アルゴリズムを正しくコードで表記するのも難しい。そこでC++ではアルゴリズム自体をライブラリにしている。ライブラリとしてのアルゴリズムを使うことで、読者はアルゴリズムを自前で実装することなく、すでに正しく実装されたアルゴリズムを使うことができる。

        +

        for_each

        +

        例えばvectorの要素を先頭から順番に標準出力するコードを考えよう。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    for (
        +        auto i = std::begin(v),
        +             j = std::end(v) ;
        +        i != j ;
        +        ++i  )
        +    {
        +        std::cout << *i ;
        +    }
        +}
        +

        このコードを書くのは難しい。このコードを書くには、イテレーターで要素の範囲を取り、ループを実行するごとにイテレーターを適切にインクリメントし、イテレーターが範囲内であるかどうかの判定をしなければならない。

        +

        アルゴリズムを理解するだけでも難しいのに、正しくコード書くのは更に難しい。例えば以下はコンパイルが通る完全に合法なC++だが間違っている。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    for (
        +        auto i = std::begin(v),
        +             j = std::end(v) ;
        +        i == j ;
        +        ++i  )
        +    {
        +        std::cout << i ;
        +    }
        +}
        +

        間違っている箇所がわかるだろうか。

        +

        まず比較の条件が間違っている。i != jとなるべきところがi == jとなっている。出力する部分も間違っている。イテレーターiが指し示す値を得るには*iとしなければならないところ、単にiとしている。

        +

        毎回このようなイテレーターのループをするfor文を書くのは間違いの下だ。ここで重要なのは、要素のそれぞれに対してstd::cout << *i ;を実行するということだ。要素を先頭から末尾まで順番に処理するというのはライブラリにやってもらいたい。

        +

        そこでこの処理を関数に切り出してみよう。イテレーター[first,last)を渡すと、イテレーターを先頭から末尾まで順番に処理してくれる関数は以下のように書ける。

        +
        auto print_all = []( auto first, auto last )
        +{
        +    // ループ
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        // 重要な処理
        +        std::cout << *iter ;
        +    }
        +} ; 
        +
        +int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    print_all( std::begin(v), std::end(v) ) ;
        +}
        +

        関数printf_allは便利だが、重要な処理がハードコードされている。例えば要素の集合のうち100以下の値だけ出力したいとか、値を2倍して出力したいとか、値を出力するたびに改行を出力したいという場合、それぞれに関数を書く必要がある。

        +
        // 値が100以下なら出力
        +auto print_if_le_100 = []( auto first, auto last )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    { // 特別な処理
        +        if ( *iter <= 100 )
        +            std::cout << *iter ;
        +    }
        +} ; 
        +
        +
        +// 値を2倍して出力
        +auto print_twice = []( auto first, auto last )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    { // 特別な処理
        +        std::cout << 2 * (*iter) ;
        +    }
        +} ; 
        +
        +
        +// 値を出力するたびに改行を出力
        +auto print_with_newline = []( auto first, auto last )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    { // 特別な処理
        +        std::cout << *iter << "\n"s ;
        +    }
        +} ; 
        +

        これを見ると、for文によるイテレーターのループは全く同じコードだとわかる。

        +

        全く同じfor文を手で書くのは間違いの元だ。同じコードはできれば書きたくない。ここで必要なのは、共通な処理は一度書くだけで済ませ、特別な処理だけを記述すればすむような方法だ。

        +

        この問題を解決するには、問題を分割することだ。問題を「for文によるループ」と「特別な処理」に分けることだ。

        +

        ところで、関数は変数に代入できる。

        +
        int main()
        +{
        +    // 変数に代入された関数
        +    auto print = []( auto x ) { std::cout << x ; } ;
        +
        +    // 変数に代入された関数の呼び出し
        +    print(123) ;
        +}
        +

        変数に代入できるということは、関数の引数として関数に渡せるということだ。

        +
        int main()
        +{
        +    // 関数を引数にとり呼び出す関数
        +    auto call_func = []( auto func )
        +    {
        +        func(123) ;
        +    } ;
        +
        +    // 引数を出力する関数
        +    auto print = []( auto x ) { std::cout << x ; } ;
        +
        +    call_func( print ) ;
        +
        +    // 引数を2倍して出力する関数
        +    auto print_twice = []( auto x ) { std::cout << 2*x ; } ;
        +
        +    call_func( print_twice ) ;
        +}
        +

        すると、要素ごとの特別な処理をする関数を引数で受け取り、要素ごとに関数を適用する関数を書くとどうなるのか。

        +
        auto for_each = []( auto first, auto last, auto f )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        f( *iter ) ;
        +    }
        +}
        +

        この関数はイテレーターをループで回す部分だけを実装していて、要素ごとの処理は引数に取った関数に任せている。早速使ってみよう。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 引数を出力する関数
        +    auto print_value = []( auto value ) { std::cout << value ; } ;
        +
        +    for_each( std::begin(v), std::end(v), print_value ) ;
        +
        +    // 引数を2倍して出力する関数
        +    auto print_twice = []( auto value ) { std::cout << 2 * value ; } ;
        +
        +    for_each( std::begin(v), std::end(v), print_twice ) ;
        +
        +    // 引数を出力した後に改行を出力する関数
        +    auto print_with_newline = []( auto value ) { std::cout << value << "\n"s ; } ;
        +
        +    for_each( std::begin(v), std::end(v), print_with_newline ) ;
        +}
        +

        関数は変数に代入しなくても使えるので、上のコードは以下のようにも書ける。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 引数を出力する
        +    for_each( std::begin(v), std::end(v),
        +        []( auto value ) { std::cout << value ; } ) ;
        +
        +    // 引数を2倍して出力する
        +    for_each( std::begin(v), std::end(v),
        +        []( auto value ) { std::cout << 2 * value ; } ) ;
        +
        +    // 引数を出力した後に改行を出力する関数
        +    for_each( std::begin(v), std::end(v),
        +        []( auto value ) { std::cout << value << "\n"s ; } ) ;
        +}
        +

        わざわざfor文を書かずに、問題の本質的な処理だけを書くことができるようになった。

        +

        このイテレーターを先頭から末尾までループで回し、要素ごとに関数を呼び出すという処理はとても便利なので、標準ライブラリにはstd::for_each( first, last, f)がある。使い方は同じだ。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    std::for_each( std::begin(v), std::end(v),
        +        []( auto value ) { std::cout << value ; } ) ;
        +}
        +

        C++17の時点ではまだ使えないが、将来のC++では、イテレーターを渡さずに、vectorを直接渡すことができるようになる予定だ。

        +
        // C++20予定
        +
        +int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    std::for_each( v, []( auto value ) { std::cout << value ; } ) ;
        +}
        +

        ところでもう一度for_eachの実装を見てみよう。

        +
        auto for_each = []( auto first, auto last, auto f )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        f( *iter ) ;
        +    }
        +}
        +

        f(*iter)がとても興味深い。もし関数fがリファレンスを引数に取っていたらどうなるだろうか。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 引数をリファレンスでとって2倍にする関数
        +    auto twice = [](auto & value){ value = 2 * value ; } ;
        +
        +    std::for_each( std::begin(v), std::end(v), twice ) ;
        +
        +    // 引数を出力する関数
        +    auto print = [](auto & value){ std::cout << value << ", "s ; } ;
        +
        +    // 2, 4, 6, 8, 10, 
        +    std::for_each( std::begin(v), std::end(v), print ) ;
        +}
        +

        元のvectorを書き換えることもできる。

        +

        all_of/any_of/none_of

        +

        他のアルゴリズムも実装していくことで学んでいこう。

        +

        all_of(first, last, pred)は、[first,last)の間のイテレーターiterのそれぞれに対して、pred(*iter)がすべてtrueを返すならばtrue、そうではないならばfalseを返すアルゴリズムだ。

        +

        このall_ofは要素が全て条件を満たすかどうかを調べるのに使える。

        +
        // 要素が全て偶数かどうか調べる関数
        +auto is_all_of_odd( auto first, auto last )
        +{
        +    return std::all_of( first, last,
        +        []( auto value ) { return value % 2 == 0 ; } ) ;
        +}
        +
        +// 要素が全て100以下かどうか調べる関数
        +auto is_all_of_le_100( auto first, auto last )
        +{
        +    return std::all_of( first, last,
        +        []( auto value ) { return value <= 100; } ) ;
        +}
        +

        ところで、もし要素がゼロ個の、つまり空のイテレーターを渡した場合どうなるのだろうか。

        +
        int main()
        +{
        +    // 空のvector
        +    std::vector<int> v ;
        +
        +    bool b = std::all_of( std::begin(v), std::end(v),
        +        // 特に意味のない関数
        +        [](auto value){ return false ; } ) ;
        +}
        +

        この場合、all_ofはtrueを返す。

        +

        実装は以下のようになる。

        +
        auto all_of = []( auto first, auto last, auto pred )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        if ( pref( *iter ) == false )
        +            return false ;
        +    }
        +
        +    return true ;
        +} ;
        +

        [first,last)が空かどうかを確認する必要はない。というのも、空であればループは一度も実行されないからだ。

        +

        any_of(first, last, pred)[first,last)の間のイテレーターiterそれぞれに対して、pred(*iter)'がひとつでもtrueならばtrueを返す。空の場合、すべてfalseの場合はfalse`を返す。

        +

        any_ofは要素に一つでも条件を満たすものがあるかどうかを調べるのに使える。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 要素に一つでも3が含まれているか?
        +    // true
        +    bool has_3 = std::any_of( std::begin(v), std::end(v),
        +        []( auto x ) { return x == 3 ;}  ) ;
        +
        +    // 要素に一つでも10が含まれているか?
        +    // false
        +    bool has_10 = std::any_of( std::begin(v), std::end(v),
        +        []( auto x ) { return x == 10 ;}  ) ;
        +}
        +

        これも実装してみよう。

        +
        auto any_of = []( auto first, auto last, auto pred )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        if ( pred( *iter ) )
        +            return true ;
        +    }
        +    return false ;
        +} ;
        +

        none_of(first, last, pred)[first,last)の間のイテレーターiterそれぞれに対して、pred(*iter)がすべてfalseならばtrueを返す。空の場合のtrueを返す。それ以外はfalseを返す。

        +

        none_ofはすべての要素が条件を満たさない判定に使える。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 値は100か?
        +    auto is_100 = [](auto x){ return x == 100 ; } ;
        +
        +    bool b = std::none_of( std::begin(v), std::end(v), is_100 ) ;
        +}
        +

        これも実装してみよう。

        +
        auto none_of( auto first, auto last, auto pred )
        +{
        +    for ( auto iter = first ; first != last ; ++iter )
        +    {
        +        if ( pred(*iter) )
        +            return false ;
        +    }
        +    return true ;
        +}
        +

        find/find_if

        +

        find( first, last, value )はイテレーター[first,last)からvalueに等しい値を見つけて、そのイテレーターを返すアルゴリズムだ。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 3を指すイテレーター
        +    auto pos = std::find( std::begin(v), std::end(v), 3 ) ;
        +
        +    std::cout << *pos ;
        +}
        +

        要素が見つからない場合はlastが返る。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    auto pos = std::find( std::begin(v), std::end(v), 0 ) ;
        +
        +    if ( pos != std::end(v) )
        +    {
        +        std::cout << "Found."s ; 
        +    }
        +    else
        +    {
        +        std::cout << "Not found."s ;
        +    }
        +}
        +

        イテレーターがlastかどうかは実際にlastと比較すればよい。

        +

        アルゴリズムを理解するには、自分で実装してみるとよい。さっそくfindを実装してみよう。

        +
        auto find = []( auto first, auto last, auto const & value )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        // 値を発見したらそのイテレーターを返す
        +        if ( *iter == value )
        +            return iter ;
        +    }
        +    // 値が見つからなければ最後のイテレーターを返す
        +    return last ;
        +}
        +

        valueauto const & valueになっているのは、リファレンスによってコピーを回避するためと、変更が必要ないためだ。しかし、intやdoubleのような単純な型については、わざわざconstなlvalueリファレンスを使う必要はない。

        +

        find_if(first, last, pred)はイテレーター[first,last)から、要素を関数predに渡したときにtrueを返す要素へのイテレーターを探すアルゴリズムだ。

        +

        関数predについてはもう少し解説が必要だ。predとはpredicateの略で、以下のような形をしている。

        +
        auto pred = []( auto const & value ) -> bool ;
        +

        関数predは値を一つ引数にとり、bool型を返す関数だ。

        +

        早速使ってみよう。

        +
        int main()
        +{
        +    std::vector<int> v = {1,3,5,7,9,11,13,14,15,16} ;
        +
        +    // 偶数ならばtrueを返す
        +    auto is_even = []( auto value )
        +    {
        +        return value % 2 == 0 ;
        +    } ;
        +    // 奇数ならばtrueを返す
        +    auto is_odd = []( auto value )
        +    {
        +        return value % 2 == 1 ;
        +    } ;
        +
        +    // 最初の偶数の要素
        +    auto even = std::find_if( std::begin(v), std::end(v), is_even ) ;
        +    // 最初の奇数の要素
        +    auto odd = std::find_if( std::begin(v), std::end(v), is_odd ) ;
        +}
        +

        実装はどうなるだろうか。

        +
        auto find_if = []( auto first, auto last, auto pred )
        +{
        +    for ( auto iter = first ; iter != last ; ++iter )
        +    {
        +        // predがtrueを返した最初のイテレーターを返す
        +        if ( pred( *iter ) )
        +            return iter ;
        +    }
        +
        +    return last ;
        +}
        +

        値との比較が関数になっただけだ。

        +

        つまりある値と比較する関数を渡したならば、find_ifはfindと同じ動きをするということだ。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,3,4,5} ;
        +
        +    // 引数が3の場合にtrueを返す関数
        +    auto is_3 = []( auto value ) { return x == 3 ; } ;
        +
        +    // 最初に関数がtrueを返す要素へのイテレーターを探すfind_if
        +    auto i = std::find_if( std::begin(v), std::end(v), is_3 ) ;
        +
        +    // 最初に3と等しい要素へのイテレーターを返すfind
        +    auto j = std::find( std::begin(v), std::end(v), 3 ) ;
        +
        +    // 同じイテレーター
        +    bool b = (i == j) ;
        +}
        +

        実は、関数は特別な[=]を使うことで、関数の外側の値をコピーして使うことができる

        +
        int main()
        +{
        +    int value = 123 ;
        +
        +    auto f = [=]{ return value ; } ;
        +
        +    f() ; // 123
        +}
        +

        特別な[&]を使うことで、関数の外側の値をリファレンスで使うことができる。

        +
        int main()
        +{
        +    int value = 123 ;
        +
        +    auto f = [&]{ ++value ; } ; 
        +
        +    f() ;
        +    std::cout << value ; // 124
        +}
        +

        ということは、findfind_ifで実装することもできるということだ。

        +
        auto find = []( auto first, auto last, auto value )
        +{
        +    return std::find_if( first, last,
        +        [&]( auto elem ) { return value == elem ; } ) ;
        +}
        +

        count/count_if

        +

        count(first, last, value)[first,last)の範囲のイテレーターiから*i == valueになるイテレーターiの数を数える。

        +

        countは指定した値と同じ要素の数を数える関数だ。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,1,1,3,3} ;
        +
        +    // 3
        +    auto a = std::count( std::begin(v), std::end(v), 1 ) ;
        +    // 1
        +    auto b = std::count( std::begin(v), std::end(v), 2 ) ;
        +    // 2
        +    auto b = std::count( std::begin(v), std::end(v), 3 ) ;
        +}
        +

        実装してみよう。

        +
        auto count( auto first, auto last, auto value )
        +{
        +    auto counter = 0u ;
        +    for ( auto i = first ; iter != last ; ++iter )
        +    {
        +        if ( *i == value )
        +            ++counter ;  
        +    }
        +    return counter ;
        +}
        +

        count_if(first, last, pred)[first, last)の範囲のイテレーターiからpred(*i) != falseになるイテレーターiの数を返す。

        +

        count_ifは要素を数える対象にするかどうかを判定する関数を渡せるcountだ。

        +
        int main()
        +{
        +    std::vector<int> v = {1,2,1,1,3,3} ;
        +
        +    // 奇数の数: 5
        +    auto a = std::count_if( std::begin(v), std::end(v),
        +        [](auto x){ return x%2 == 1 ; } ) ;
        +
        +    // 偶数の数: 1
        +    auto b = std::count_if( std::begin(v), std::end(v),
        +        [](auto x){ return x%2 == 0 ; } ) ;
        +
        +    // 2以上の数: 3
        +    auto a = std::count_if( std::begin(v), std::end(v),
        +        [](auto x){ return x >= 2 ; } ) ;
        +}
        +

        実装してみよう。

        +
        auto count( auto first, auto last, auto pred )
        +{
        +    auto counter = 0u ;
        +    for ( auto i = first ; iter != last ; ++iter )
        +    {
        +        if ( pred(*i) != false )
        +            ++counter ;  
        +    }
        +    return counter ;
        +}

        Cプリプロセッサー

        CプリプロセッサーはC++がC言語から受け継いだ機能だ。CプリプロセッサーはソースコードをC++としてパースする前に、テキストをトークン単位で変形する処理のことだ。この処理はソースファイルをC++としてパースする前処理として行われる。CプリプロセッサーはC++ではなく別言語として認識すべきで、そもそもプログラミング言語ではなくマクロ言語だ。

        C++ではCプリプロセッサーが広く使われており、今後もしばらくは使われるだろう。読者がC++で書かれた既存のコードを読む時、Cプリプロセッサーは避けて通れない。Cプリプロセッサーはいずれ廃止したい機能ではあるが、C++は未だに廃止できていない。