From 7efa6a992419e4b93c5b34573a8df4857cfc927d Mon Sep 17 00:00:00 2001 From: Ryou Ezoe Date: Thu, 26 Sep 2019 17:23:48 +0900 Subject: [PATCH] fix typo --- 020-array.md | 2 +- docs/index.html | 127 ++++++++++++++++++++++++------------------------ 2 files changed, 64 insertions(+), 65 deletions(-) diff --git a/020-array.md b/020-array.md index bcff526..457f269 100644 --- a/020-array.md +++ b/020-array.md @@ -54,7 +54,7 @@ int main() ~~~cpp int main() { - std::size_T N{} ; + std::size_t N{} ; std::cin >> N ; // エラー diff --git a/docs/index.html b/docs/index.html index e42dcf3..95b6040 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1177,7 +1177,7 @@

入門用の環境構築

.PHONY : run clean

makeでコンパイル。make runで実行。make cleanでコンパイル結果の削除。

Makefile全体は以下のようになる。

-
gcc_options = -std=c++17 -Wall --pedantic-error
+
gcc_options = -std=c++17 -Wall --pedantic-errors
 
 program : main.cpp all.h all.h.gch
     g++ $(gcc_options) -include all.h $< -o $@
@@ -2900,7 +2900,7 @@ 

リダイレクト

これは"hello"と標準出力するだけの簡単なプログラムだ。このプログラムをコンパイルしたプログラム名をprogramとしよう。標準出力の出力先はデフォルトで、ユーザーのターミナルになる。

$ ./program
 hello
-

リダイレクトを使えば、この出力先をファイルにできる。リダイレクトを使うには"プログラム \> ファイル名"とする

+

リダイレクトを使えば、この出力先をファイルにできる。リダイレクトを使うには"プログラム \> ファイル名"とする。

$ ./program > hello.txt
 $ cat hello.txt
 hello
@@ -2916,7 +2916,7 @@

リダイレクト

std::cout << mass / (height*height) ; }
-

このプログラム名をbmiとして、通常通り実行すると以下のようになる。

+

このプログラム名をbmiとして、通常どおり実行すると以下のようになる。

$ ./bmi
 1.63
 73
@@ -3081,7 +3081,7 @@ 

無限ループ

  1. 関数helloが呼ばれる
  2. goto文でラベルloopまで飛ぶ
  3. -
  4. 1に戻る
  5. +
  6. 1.に戻る

という処理を行う。

終了条件付きループ

@@ -3421,7 +3421,7 @@

無限ループ

std::cout << sum << "\n"s ; } }
-

重要なのは以下の4行だ。

+

重要なのは以下の5行だ。

while( true )
 {
     sum += input() ;
@@ -5891,7 +5891,7 @@ 

スコープ

スコープ(scope)というのはやや説明が難しい概念だ。名前空間や関数はスコープを持っている。とてもおおざっぱに説明するとカーリブラケット{}で囲まれた範囲がスコープだ。

-

関数output_allのコードは何も変えていないのに、さまざまなイテレーターに対応できる。イテレーターというお作法に乗っ取ることで、さまざまな処理が可能になるのだ。

+

関数output_allのコードは何も変えていないのに、さまざまなイテレーターに対応できる。イテレーターというお作法にのっとることで、さまざまな処理が可能になるのだ。

これは出力にも言えることだ。関数output_allstd::coutに出力していた。これをイテレーターに対する書き込みに変えてみよう。

auto output_all = []( auto first, auto last, auto output_iter )
 {
@@ -6531,7 +6531,7 @@ 

const

// constの付いている型のオブジェクト const int x = 0 ; - // エラー、constがない。 + // エラー、constがない int & ref = x ; // OK、constが付いている @@ -7380,7 +7380,7 @@

remove

auto last = std::remove( std::begin(v), std::end(v), 2 ) ; - // "12" + // "13" std::for_each( std::begin(v), last, [](auto x) { std::cout << x ; } ) ; @@ -7701,7 +7701,7 @@

変数をまとめる

クラスデータメンバーの定義は変数ではない。オブジェクトではない。つまり、それ自体にストレージが割り当てられてはいない。

クラスの変数を定義したときに、その変数のオブジェクトに紐付いたストレージが使われる。

@@ -8655,7 +8655,7 @@

std::array

一方、arrayはコンパイル時に要素数を決める。標準入力から得た値は実行時のものなので、使うことはできない。

int main()
 {
-    std::size_T N{} ;
+    std::size_t N{} ;
     std::cin >> N ;
 
     // エラー
@@ -8743,7 +8743,7 @@ 

プログラマーの三大美徳

コンピューターが怠惰であるときにプログラマーが感ずる怒り。短気によって書かれたプログラムは、単に労力を削減するばかりではなく、事前に解決しておく。少なくとも、すでに解決済みのように振る舞う。これがプログラマーの第二の美徳である。

傲慢
-

ゼウスも罰したもう過剰なまでの奢り。他人がそしりを入れられぬほどのプログラムを書く推進剤。これがプログラマーの第三の美徳である。

+

ゼウスも罰したもう過剰なまでの驕り。他人がそしりを入れられぬほどのプログラムを書く推進剤。これがプログラマーの第三の美徳である。

これから学ぶarrayを実装するためのC++の機能を学ぶときに、このプログラマーの三大美徳のことを頭に入れておこう。

@@ -8938,7 +8938,7 @@

問題点

double storage[1] ; double & operator []( std::size_t i ) { return storage[i] ; } -} +} ; // array_double_2, array_double_3, ...

これは怠惰で短気なプログラマーには耐えられない作業だ。C++にはこのような退屈なコードを書かなくても済む機能がある。しかしその前に、引数について考えてみよう。

@@ -9862,7 +9862,7 @@

イテレーターの中身

std::cout << ++i ; // 1 // 後置 std::cout << i++ ; // 1 - std::cout << i // 2 + std::cout << i ; // 2 }

int型では、前置operator ++はオペランドの値を1加算した値にする。後置operator ++はオペランドの値を1加算するが、式を評価した結果は前のオペランドの値になる。

++i ; // i+1
@@ -10368,7 +10368,7 @@ 

例外を投げる

if ( i >= size() ) { // エラー検出 - // しかし何をreturnすればいいのだろう。 + // しかし何をreturnすればいいのだろう } return storage[i] ; @@ -12170,30 +12170,29 @@

データメ 8

筆者の環境では、xはクラスの先頭アドレスからオフセット0バイトに、yはオフセット4バイトに、zはオフセット8バイトに配置されているようだ。

確かめてみよう。

- +

筆者の環境では以下のように出力される

123456789

このプログラムの実行結果は環境によって変わる。読者の使っている環境でデータメンバーへのポインターが筆者の環境と同じように実装されているとは限らない。

@@ -12457,7 +12456,7 @@

出力イテレーター

iterator_traits

@@ -13209,7 +13208,7 @@

advance( i, n ): n移動する// iは移動しない std::advance(i, 0) ;

nが正数の場合は前方(i+1の方向)に、nが負数の場合は後方(i-1の方向)に、それぞれn回移動させる。

-

advance(i,n)はi自体が書き換わる。

+

advance(i,n)i自体が書き換わる。

@@ -13951,7 +13950,7 @@

ネストされた型名

{ public : using allocator_type = Allocator ; -}
+} ;

size_typeは要素数を表現する型だ。

void f( std::vector<int> & v )
 {
@@ -14528,9 +14527,9 @@ 

reserveの実装

つまり動的メモリー確保をしたあとに、既存の要素を新しいストレージにコピーしなければならないということだ。

まとめよう。

    -
  1. すでに指定された要素数以上に予約されているなら何もしない。
  2. -
  3. まだ動的メモリー確保が行われていなければ動的メモリー確保をする。
  4. -
  5. 有効な要素がある場合は新しいストレージにコピーする。
  6. +
  7. すでに指定された要素数以上に予約されているなら何もしない
  8. +
  9. まだ動的メモリー確保が行われていなければ動的メモリー確保をする
  10. +
  11. 有効な要素がある場合は新しいストレージにコピーする

古いストレージから新しいストレージに要素をコピーするとき、古いストレージと新しいストレージが一時的に同時に存在しなければならない。

疑似コード風に記述すると以下のようになる。

@@ -14606,7 +14605,7 @@

reserveの実装

{ destroy( &*riter ) ; } - // scope_exitによって自動的にストレージが破棄される。 + // scope_exitによって自動的にストレージが破棄される }

ここではまだ学んでいないムーブの概念が出てくる。これはムーブセマンティクスの章で詳しく学ぶ。

resize

@@ -14672,9 +14671,9 @@

resize

}

まとめるとresizeは以下のように動作する。

    -
  1. 現在の要素数より少なくリサイズする場合、末尾から要素を破棄する。
  2. -
  3. 現在の要素数より大きくリサイズする場合、末尾に要素を追加する。
  4. -
  5. 現在の要素数と等しくリサイズする場合、何もしない。
  6. +
  7. 現在の要素数より少なくリサイズする場合、末尾から要素を破棄する
  8. +
  9. 現在の要素数より大きくリサイズする場合、末尾に要素を追加する
  10. +
  11. 現在の要素数と等しくリサイズする場合、何もしない

実装しよう。

    void resize( size_type sz )
@@ -14699,7 +14698,7 @@ 

resize

要素を破棄する場合、破棄する要素数だけ末尾から順番に破棄する。

要素を増やす場合、reserveを呼び出してメモリーを予約してから、追加の要素を構築する。

sz == size()の場合は、どちらのif文の条件にも引っかからないので、何もしない。

-

size(sz, value)は、追加の引数を取るほか、construct( iter )の部分がconstrcut( iter, value )に変わるだけだ。

+

size(sz, value)は、追加の引数を取るほか、construct( iter )の部分がconstruct( iter, value )に変わるだけだ。

-

これはまずz = 0が評価される。変数zの値は0になり、式を評価した結果の値はzへのlvalueリファレンスだ。なので、y = z = 0というのは、y = (z=0)となる。z=0についてはzであるので、y = zとなる。ここでのz0を代入されたあとのzなので、値は0だ。その結果変数yの値は0になる。変数xの場合も同様だ。

+

これはまずz = 0が評価される。変数zの値は0になり、式を評価した結果の値はzへのlvalueリファレンスだ。なので、y = z = 0というのは、y = (z=0)となる。z=0についてはzであるので、y = zとなる。ここでのz0を代入されたあとのzなので、値は0だ。その結果変数yの値は0になる。変数xの場合も同様だ。

以下のような例も見てみよう。

+} ;

このクラスのコピーコンストラクターの定義は以下のように書ける。

template < typename T >
 dynamic_array<T>::dynamic_array( const dynamic_array & r )
@@ -15770,7 +15769,7 @@ 

xvalue

{ X x{} ; int && r = static_cast<X &&>(x).data_member ; -} ;
+} @@ -15791,7 +15790,7 @@

rvalue

// lvalueなオブジェクト int lvalue { } ; - // OK、lvalueリファレンスはlvalueで初期化できる。 + // OK、lvalueリファレンスはlvalueで初期化できる int & l_ref = lvalue ; // OK、rvalueリファレンスはrvalueで初期化できる @@ -16695,7 +16694,7 @@

単項演算子

{ *ptr = -*ptr ; } -} +} ;

幸い、クラスIntegerはムーブコンストラクターを実装しているので、

auto b = -a ;

というコードは、式-aによって生成された一時オブジェクトが変数bにムーブされる。

@@ -17436,7 +17435,7 @@

std::basic_string

// 少なくともchar [5]を格納できるだけのストレージを動的確保する std::string hello("hello") ; // helloが破棄される - // デストラクターはストレージを解放する。 + // デストラクターはストレージを解放する }

コピーはストレージの動的確保、ムーブはストレージの所有権の移動になる。

int main()
@@ -17911,7 +17910,7 @@ 

疑似乱数

「これはぜんぜん乱数ではない。予測可能じゃないか」と考えるかもしれない。しかし中でどのように乱数が生成されているかわからなければ、外部からは乱数のように見える。これが擬似乱数の考え方だ。

乱数エンジン

乱数エンジンは生の乱数を生成するライブラリだ。クラスで実装されている。

-

乱数エンジンはメンバー関数min()で最小値を、メンバー関数max()で最大値を、operator()で最小値から最大値の間の乱数を返す

+

乱数エンジンはメンバー関数min()で最小値を、メンバー関数max()で最大値を、operator()で最小値から最大値の間の乱数を返す。

template < typename Engine >
 void f( Engine & e )
 {
@@ -18201,7 +18200,7 @@ 

乱数分布ライブラリ

  1. 3bitの生の乱数rを得る
  2. r\(0 \leq r \leq 5\)なら`r+1’が分布された乱数
  3. -
  4. それ以外の場合、1に戻る
  5. +
  6. それ以外の場合、1.に戻る

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

template < typename Engine >
@@ -18503,7 +18502,7 @@ 

幾何分布(std::geometric_distr
  • 6面ダイスを1の目が出るまで振った回数
  • 確率1%で当たるくじ引きをアタリが出るまで引いた回数
  • -

    コイントスの例で考えよう。コイントス1回をベルヌーイ試行とし、成功を表とする。表が出るまでコイントスをしてみよう。コイントスを何回する必要があるだろうか。運がよければ1回で表がでるので1回だ。運が悪ければ、5回コイントスをしても全部裏なこともあるだろう。100回コイントスをして表が一度も出ないことは、確率的にはあり得る。ただしその確率は\(\frac{1}{2^{100}}\)なので、およそあり得ない確率ではある。

    +

    コイントスの例で考えよう。コイントス1回をベルヌーイ試行とし、成功を表とする。表が出るまでコイントスをしてみよう。コイントスを何回する必要があるだろうか。運がよければ1回で表が出るので1回だ。運が悪ければ、5回コイントスをしても全部裏なこともあるだろう。100回コイントスをして表が一度も出ないことは、確率的にはあり得る。ただしその確率は\(\frac{1}{2^{100}}\)なので、およそあり得ない確率ではある。

    std::geometric_distribution<IntType>IntType型の乱数\(i\), \(i \geq 0\)を以下の離散確率関数に従って分布する。

    \[ P(i\,|\,p) = p \cdot (1-p)^{i} \text{ .} @@ -18555,7 +18554,7 @@

    幾何分布(std::geometric_distr

    確率1%のくじを当てるには、運が悪いと何百回も引かなければならない。

    負の二項分布(std::negative_binomial_distribution)

    負の二項分布(negative binomial distribution)は幾何分布に似ている。幾何分布がベルヌーイ試行が1回成功するまでに行ったベルヌーイ試行の回数を乱数として分布するのに対し、負の二項分布はベルヌーイ試行が\(k\)回成功するまでに行ったベルヌーイ試行の回数を乱数として分布する。

    -

    負の二項分布を具体的な例で考えよう

    +

    負の二項分布を具体的な例で考えよう。

    • コイントスを、10回、表が出るまで行った回数
    • 6面ダイスを、10回、1の目が出るまで振った回数
    • @@ -19128,11 +19127,11 @@

      内部状態の取得

      densities()の結果が正規化されているのは、ユーザーが指定した確率は\(w_k\)だが、ここで返すのは\(p_k\)だからだ。

      区分線形分布(std::piecewise_linear_distribution<RealType>)

      簡単な説明

      -

      区分線形分布(piecewise linear distribution)は区分定数分布と同じく、区間と確率(又の名を密度、ウエイト)を指定する。

      +

      区分線形分布(piecewise linear distribution)は区分定数分布と同じく、区間と確率(またの名を密度、ウエイト)を指定する。

      区間の指定は区分定数分布と同じだ。内部境界の集合で指定する。例えば{1.0, 2.0, 3.0}は2つの区間[1.0, 2.0)[2.0, 3.0)を指定する。

      区分線形分布における確率は、区間に対してではなく、内部境界に対して指定する。指定した全区間における値の出現確率は、内部境界から内部境界に向かって指定した確率の差の方向に線形に増加、もしくは減少する。

      例えば区分{0.0, 1.0}と確率{1.0, 2.0}を指定した場合、これは1つの区間[0.0, 1.0)について、内部境界0.0の確率は\(\frac{1}{3}\)、内部境界1.0の確率は\frac{2}{3}とし、\(0.0 \leq x < 1.0\)の範囲の乱数xを生成する。内部境界区間の範囲に注意。1.0未満なので、1.0は出ない。

      -

      そして、区間の間の値は、区間を区切る2つの内部境界の確率の差によって、線形に増加、もしくは減少する。例えば値0.25が出る確率は\(\frac{1.25}{3}\)0.5が出る確率は\(\frac{1.5}{3}\)、値1.75がでる確率は\(\frac{1.75}{3}\)だ。

      +

      そして、区間の間の値は、区間を区切る2つの内部境界の確率の差によって、線形に増加、もしくは減少する。例えば値0.25が出る確率は\(\frac{1.25}{3}\)0.5が出る確率は\(\frac{1.5}{3}\)、値1.75が出る確率は\(\frac{1.75}{3}\)だ。

      区分{0.0, 1.0, 2.0}と確率{1.0, 2.0, 1.0}の場合、2つの区間[0.0, 1.0)[1.0, 2.0)の範囲について、0.0から1.0に向かう区間についての確率は\(\frac{1}{4}\)から\(\frac{1}{2}\)に増加し、1.0から2.0に向かう区間についての確率は\(\frac{1}{2}\)から\(\frac{1}{4}\)に減少する。

      結果として、乱数値の分布をグラフに描画すると、1.0が最も出やすく、その前後±1.0の範囲で徐々に減少していく山のようなグラフになる。

      TODO: グラフ、横軸が乱数値、縦軸が確率

      @@ -20056,7 +20055,7 @@

      インライン関数/イン // bar.cpp #include "library.h"

    -

    inlineはODRを例外的に回避できるとは言え、強い制約がある。

    +

    inlineはODRを例外的に回避できるとはいえ、強い制約がある。

    1. 異なる翻訳単位に限る
    @@ -20405,7 +20404,7 @@

    GDBのチュートリアル

    4行目を実行し、5行目のブレイクポイントで止まる。4行目を実行したということは、変数valの値は10になっているはずだ。もう一度printコマンドで調べてみよう。

    (gdb) print val
     $2 = 10
    -

    値は10だ。GDBはprintの結果の履歴を記録している。$1$2というのはその記録を参照するための名前だ。その値はprintコマンドで確認できる

    +

    値は10だ。GDBはprintの結果の履歴を記録している。$1$2というのはその記録を参照するための名前だ。その値はprintコマンドで確認できる。

    (gdb) print $1
     $3 = 0
     (gdb) print $2