diff --git a/012-floating-point.md b/012-floating-point.md index 5de67a6..fe8cf88 100644 --- a/012-floating-point.md +++ b/012-floating-point.md @@ -225,16 +225,18 @@ int main() } ~~~ -NaNとの比較結果はすべて`false`となる。 +NaNとの比較結果はNaNと非NaNの非同値比較以外はすべて`false`となる。 ~~~cpp int main() { double NaN = std::numeric_limits::quiet_NaN() ; - // すべてfalse - bool a = NaN == 0.0 ; + // true bool b = NaN != 0.0 ; + + // false + bool a = NaN == 0.0 ; bool c = NaN == NaN ; bool d = NaN != NaN ; bool e = NaN < 0.0 ; diff --git a/docs/index.html b/docs/index.html index 4e3c1e6..4fa9c68 100644 --- a/docs/index.html +++ b/docs/index.html @@ -106,7 +106,7 @@ -
+

江添亮のC++入門

江添 亮

2018-02-27

@@ -769,7 +769,7 @@

コンパイル

GCCを使って先ほどのhello.cppをコンパイルするには以下のようにする。

$ g++ -o hello hello.cpp

GCCという名前のC++コンパイラーなのにg++なのは、gccはC言語コンパイラーの名前としてすでに使われているからだ。この慣習はClangも引き継いでいて、ClangのC++コンパイラーはclang++だ。

-

サンプルコードを間違いなくタイプしていれば、カレントディレクトリーにhelloとぃう実行可能ファイルが作成されるはずだ。確認してみよう。

+

サンプルコードを間違いなくタイプしていれば、カレントディレクトリーにhelloという実行可能ファイルが作成されるはずだ。確認してみよう。

$ ls
 hello hello.cpp

実行

@@ -1232,7 +1232,7 @@

標準出力

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

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

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

-

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

+

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

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

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

int main()
@@ -1249,7 +1249,7 @@ 

標準出力

}

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

one two three 
-

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

+

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

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

int main()
 {
@@ -1339,7 +1339,7 @@ 

整数と浮動小数点数

{ std::cout << 1 + "234" ; }
-

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

+

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

変数(variable)

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

int main()
@@ -1741,7 +1741,7 @@ 

文法エラー

auto y = x + 1 ;

コンパイラーにとって、改行は空白文字と同じくソースファイル中の意味のあるトークン(キーワードや名前や記号)を区切る文字でしかない。コンパイラーにとって、このコードは実質以下のように見えている。

auto x=1+1 auto y=x+1;
-

"1 auto"というのは文法エラーだ。なのでコンパイラーは文法エラーが発覚する最初の文字である'auto''a'を指摘したのだ。

+

"1 auto"というのは文法エラーだ。なのでコンパイラーは文法エラーが発覚する最初の文字である'auto''a'を指摘したのだ。

人間にとって自然になるように修正すると、コンパイラーが指摘した行の1つ上の行の行末に';'を追加すべきだ。

    auto x = 1 + 1 ;
     auto y = x + 1 ;
@@ -2107,7 +2107,7 @@

条件分岐

実行して確かめてみよう。ほとんどの読者の実行環境では以下のようになるはずだ。ほとんどの、というのは、そうではない環境も存在するからだ。読者がそのような稀有な環境を使っている可能性はまずないだろうが。

cat is smaller.
 Longcat is Looong.
-

なるほど。"cat"s"dog"sよりも小さく(?)、"longcat"s"cat"sよりも長い(大きい?)ようだ。なんだかよくわからない結果になった。

+

なるほど。"cat"s"dog"sよりも小さく(?)、"longcat"s"cat"sよりも長い(大きい?)ようだ。なんだかよくわからない結果になった。

これはどういうことなのか。もっと簡単な文字列で試してみよう。

-

これを実行するとaと出力される。すると"a"s"b"sより小さいようだ。

+

これを実行するとaと出力される。すると"a"s"b"sより小さいようだ。

もっと試してみよう。

-

これを実行すると、aaと出力される。すると"aa"s"ab"sより小さいことになる。

+

これを実行すると、aaと出力される。すると"aa"s"ab"sより小さいことになる。

文字列の大小比較は文字単位で行われる。まず最初の文字が大小比較される。もし等しい場合は、次の文字が大小比較される。等しくない最初の文字の結果が、文字列の大小比較の結果となる。

条件式

条件とは何だろう

-

if文の中で書く条件(condition)は、条件式(conditional expression)とも呼ばれている(expression)の一種だ。というのは例えば"1+1"のようなものだ。の中に書くことができ、これを式文(expression statement)という。

+

if文の中で書く条件(condition)は、条件式(conditional expression)とも呼ばれている(expression)の一種だ。というのは例えば"1+1"のようなものだ。の中に書くことができ、これを式文(expression statement)という。

-

"a==b""a\<b"のような条件なので、として書くことができる。

+

"a==b""a\<b"のような条件なので、として書くことができる。

-

C++では多くの式には型がある。たとえば"123"int型で、"123+4"int型だ。

+

C++では多くの式には型がある。たとえば"123"int型で、"123+4"int型だ。

-

とすると、"1==2""3!=3"のような条件式にも型があるのではないか。型があるのであれば変数に入れられるはずだ。試してみよう。

+

とすると、"1==2""3!=3"のような条件式にも型があるのではないか。型があるのであれば変数に入れられるはずだ。試してみよう。

int main()
 {
     if (  1 == 1 )
@@ -2172,7 +2172,7 @@ 

条件とは何だろう

else { std::cout << "1 == 1 is false.\n"s ; } }
-

"if(x)""if(1==1)"と書いた場合と同じように動く。

+

"if(x)""if(1==1)"と書いた場合と同じように動く。

変数に入れられるのであれば出力もできるのではないだろうか。試してみよう。

int main()
 {
@@ -2182,7 +2182,7 @@ 

条件とは何だろう

}
1
 0
-

なるほど、条件が正しい場合"1"になり、条件が間違っている場合"0"になるようだ。

+

なるほど、条件が正しい場合"1"になり、条件が間違っている場合"0"になるようだ。

ではif文の中に10を入れたらどうなるのだろうか。

// 条件が正しい値だけ出力される。
 int main()
@@ -2259,7 +2259,7 @@ 

bool型

bool型の演算

bool型にはいくつかの演算が用意されている。

論理否定: operator !

-

"!a"atrueの場合falseに、falseの場合trueになる。

+

"!a"atrueの場合falseに、falseの場合trueになる。

-

比較演算子の結果はbool値になるということを覚えているだろうか。"1 \< 2"trueになり、"1 \> 2"falseになる。

-

bool値同士も同値比較ができるということは、"(1 \< 2) == true"のように書くことも可能だということだ。

+

比較演算子の結果はbool値になるということを覚えているだろうか。"1 \< 2"trueになり、"1 \> 2"falseになる。

+

bool値同士も同値比較ができるということは、"(1 \< 2) == true"のように書くことも可能だということだ。

-

"(1\<2)"trueなので、"(1\<2)==true""true==true"と同じ意味になる。この結果はもちろん"true"だ。

+

"(1\<2)"trueなので、"(1\<2)==true""true==true"と同じ意味になる。この結果はもちろん"true"だ。

論理積: operator &&

-

"a && b"abがともにtrueのときにtrueとなる。それ以外の場合はfalseとなる。これを論理積という。

+

"a && b"abがともにtrueのときにtrueとなる。それ以外の場合はfalseとなる。これを論理積という。

表にまとめると以下のようになる。

@@ -2399,7 +2399,7 @@

論理積: operator &&

{ std::cout << "Bad.\n"s ; }}

論理和: operator ||

-

"a || b"abがともにfalseのときにfalseとなる。それ以外の場合はtrueとなる。これを論理和という。

+

"a || b"abがともにfalseのときにfalseとなる。それ以外の場合はtrueとなる。これを論理和という。

表にまとめると以下のようになる。

@@ -2484,8 +2484,8 @@

短絡評価

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

a
 false
-

関数呼び出し"a()"の結果はfalseなので、"b()"は評価されない。評価されないということは関数呼び出しが行われず、当然標準出力も行われない。

-

同様に、論理和では、"a || b"とある場合、abのどちらか片方でもtrueであれば、結果はtrueとなる。もし、atrueであった場合、bの結果如何にかかわらず結果はtrueとなるので、bは評価されない。

+

関数呼び出し"a()"の結果はfalseなので、"b()"は評価されない。評価されないということは関数呼び出しが行われず、当然標準出力も行われない。

+

同様に、論理和では、"a || b"とある場合、abのどちらか片方でもtrueであれば、結果はtrueとなる。もし、atrueであった場合、bの結果如何にかかわらず結果はtrueとなるので、bは評価されない。

int main()
 {
     auto a = []()
@@ -2505,10 +2505,10 @@ 

短絡評価

結果、

a
 true
-

"b()"が評価されていないことがわかる。

+

"b()"が評価されていないことがわかる。

boolの変換

bool型の値と演算はこれで全部だ。値はtrue/falseの2つのみ。演算は==, !=, !&&||の5つだけだ。

-

読者の中には納得のいかないものもいるだろう。ちょっと待ってもらいたい。boolの大小比較できないのだろうか。boolの四則演算はできないのか。"if(123)"などと書けてしまうのは何なのか。

+

読者の中には納得のいかないものもいるだろう。ちょっと待ってもらいたい。boolの大小比較できないのだろうか。boolの四則演算はできないのか。"if(123)"などと書けてしまうのは何なのか。

好奇心旺盛な読者は本書の解説を待たずしてすでに自分でいろいろとコードを書いて試してしまっていることだろう。

boolの大小比較はどうなるのだろうか。

int main()
@@ -2518,7 +2518,7 @@ 

boolの変換

bool b = true < false ; std::cout << b ; }
-

このコードを実行すると、出力は"false"だ。"true \< false"の結果が"false"だということは、truefalseより大きいということになる。

+

このコードを実行すると、出力は"false"だ。"true \< false"の結果が"false"だということは、truefalseより大きいということになる。

四則演算はどうか?

int main()
 {
@@ -2535,7 +2535,7 @@ 

boolの変換

1 1 0
-

不思議な結果だ。"true+true""2""true+false""1""false+false""0"。これはtrue1false0ならば納得のいく結果だ。大小比較の結果としても矛盾していない。

+

不思議な結果だ。"true+true""2""true+false""1""false+false""0"。これはtrue1false0ならば納得のいく結果だ。大小比較の結果としても矛盾していない。

すでに見たように、std::boolalphaを出力していない状態でboolを出力するとtrue1false0となる。

-

したがって、"if (0)""if (false)"と等しく、"if (1)""if(-1)"など非ゼロな値は"if (true)"と等しい。

+

したがって、"if (0)""if (false)"と等しく、"if (1)""if(-1)"など非ゼロな値は"if (true)"と等しい。

-

大小比較は単にboolを整数に変換した結果を比較しているだけだ。"true \< false""1 \< 0"と書くのと同じだ。

+

大小比較は単にboolを整数に変換した結果を比較しているだけだ。"true \< false""1 \< 0"と書くのと同じだ。

-

同様に四則演算もbool型を整数型に変換した上で計算をしているだけだ。"true + true""1 + 1"と書くのと同じだ。

+

同様に四則演算もbool型を整数型に変換した上で計算をしているだけだ。"true + true""1 + 1"と書くのと同じだ。

int main()
 {
     // 1 + 1
@@ -2643,7 +2643,7 @@ 

デバッグ: コン else std::cout << "x is NOT 123.\n"s ; }

-

これを実行すると、"x is 123.\n"と出力される。しかし、変数xの値は0のはずだ。なぜか0123は等しいと判断されてしまった。いったいどういうことだろう。

+

これを実行すると、"x is 123.\n"と出力される。しかし、変数xの値は0のはずだ。なぜか0123は等しいと判断されてしまった。いったいどういうことだろう。

この謎は警告メッセージを読むと解ける。

g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program
 main.cpp: In function ‘int main()’:
@@ -2675,7 +2675,7 @@ 

デバッグ: コン bool b0 = x = 0 ; if ( x = 0 ) ; }

-

つまり、"if(x=1)"というのは、"if(1)"と書くのと同じで、これは最終的に、"if(true)"と同じ意味になる。

+

つまり、"if(x=1)"というのは、"if(1)"と書くのと同じで、これは最終的に、"if(true)"と同じ意味になる。

警告メッセージの「括弧で囲むべき」というのは、括弧で囲んだ場合、この警告メッセージは出なくなるからだ。

int main()
 {
@@ -2712,7 +2712,7 @@ 

これまでのおさらい

// BMIの出力 std::cout << "BMI="s << bmi << "\n"s ; }
-

結果は"27.4756"となった。これだけでは太っているのか痩せているのかよくわからない。調べてみると、BMIの数値と肥満との関係は以下の表のとおりになるそうだ。

+

結果は"27.4756"となった。これだけでは太っているのか痩せているのかよくわからない。調べてみると、BMIの数値と肥満との関係は以下の表のとおりになるそうだ。

@@ -2855,7 +2855,7 @@

標準入力

$ make run -1 true
-

"true", "false"という文字列でtrue, falseの入力をしたい場合、std::cinstd::boolalphaを「入力」させる。

+

"true", "false"という文字列でtrue, falseの入力をしたい場合、std::cinstd::boolalphaを「入力」させる。

int main()
 {
     // bool型
@@ -2897,10 +2897,10 @@ 

リダイレクト

{ std::cout << "hello" ; }
-

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

+

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

$ ./program
 hello
-

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

+

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

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

リダイレクト

このうち、1.6373はユーザーによる入力だ。これを毎回手で入力するのではなく、ファイルから入力することができる。つまり以下のようなファイルを用意して、

1.63
 73
-

このファイルを例えば、"bodymass.txt"とする。手で入力する代わりに、このファイルを入力として使いたい。これにはリダイレクトとして"プログラム名 \< ファイル名"とする。

+

このファイルを例えば、"bodymass.txt"とする。手で入力する代わりに、このファイルを入力として使いたい。これにはリダイレクトとして"プログラム名 \< ファイル名"とする。

$ ./bmi < bodymass.txt
 27.4756

リダイレクトの入出力を組み合わせることも可能だ。

@@ -2966,7 +2966,7 @@

パイプ

$ ./bmi < fixed_bodymass.txt 27.4756

しかしこれではファイルが増えて面倒だ。この場合、パイプを使うとスッキリと書ける。

-

パイプはプログラムの標準出力をプログラムの標準入力とするの使い方は、"プログラム名 | プログラム名"だ。

+

パイプはプログラムの標準出力をプログラムの標準入力とするの使い方は、"プログラム名 | プログラム名"だ。

$ ./convert < bodymass.txt | ./bmi
 27.4756

ところで、すでに何度か説明なしで使っているが、POSIX規格を満たすOSにはcatというプログラムが標準で入っている。cat ファイル名は指定したファイル名の内容を標準出力する。標準出力はパイプで標準入力にできる。

@@ -2998,7 +2998,7 @@

これまでのおさらい

}

実行結果、

123
-

この実行結果が"123"以外の結果になることはない。C++ではプログラムは書かれた順番に実行されるからだ。

+

この実行結果が"123"以外の結果になることはない。C++ではプログラムは書かれた順番に実行されるからだ。

条件分岐は、プログラムの実行を条件付きで行うことができる。

int main()
 {
@@ -3020,7 +3020,7 @@ 

これまでのおさらい

goto文

ここでは繰り返し(ループ)の基礎的な仕組みを理解するために、最も原始的で最も使いづらい繰り返しの機能であるgoto文を学ぶ。goto文で実用的な繰り返し処理をするのは面倒だが、恐れることはない。より簡単な方法もすぐに説明するからだ。なぜ本書でgoto文を先に教えるかというと、あらゆる繰り返しは、けっきょくのところif文goto文へのシンタックスシュガーにすぎないからだ。goto文を学ぶことにより、繰り返しを恐れることなく使う本物のプログラマーになれる。

無限ループ

-

"hello\n"と3回出力するプログラムはどうやって書くのだろうか。"hello\n"を1回出力するプログラムの書き方はすでにわかっているので、同じ文を3回書けばよい。

+

"hello\n"と3回出力するプログラムはどうやって書くのだろうか。"hello\n"を1回出力するプログラムの書き方はすでにわかっているので、同じ文を3回書けばよい。

// 1回"hello\n"を出力する関数
 void hello()
 {
@@ -3034,7 +3034,7 @@ 

無限ループ

hello() ; }

10回出力する場合はどうするのだろう。10回書けばよい。コードは省略する。

-

では100回出力する場合はどうするのだろう。100回書くのだろうか。100回も同じコードを書くのはとても面倒だ。読者がVimのような優秀なテキストエディターを使っていない限り100回も同じコードを間違えずに書くことは不可能だろう。Vimならば1回書いたあとにノーマルモードで"100."するだけで100回書ける。

+

では100回出力する場合はどうするのだろう。100回書くのだろうか。100回も同じコードを書くのはとても面倒だ。読者がVimのような優秀なテキストエディターを使っていない限り100回も同じコードを間違えずに書くことは不可能だろう。Vimならば1回書いたあとにノーマルモードで"100."するだけで100回書ける。

実際のところ、100回だろうが、1000回だろうが、あらかじめ回数がコンパイル時に決まっているのであれば、その回数だけ同じ処理を書くことで実現可能だ。

しかし、プログラムを外部から強制的に停止させるまで、無限に出力し続けるプログラムはどう書けばいいのだろうか。そういった停止しないプログラムを外部から強制的に停止させるにはCtrl-Cを使う。

以下はそのようなプログラムの実行例だ。

@@ -3065,7 +3065,7 @@

無限ループ

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

13

2を出力すべき文の実行が飛ばされていることがわかる。

-

これだけだと"if (false)"と同じように見えるが、goto文はソースコードの上に飛ぶこともできるのだ。

+

これだけだと"if (false)"と同じように見えるが、goto文はソースコードの上に飛ぶこともできるのだ。

-

これは"hello\n"を無限に出力するプログラムだ。

+

これは"hello\n"を無限に出力するプログラムだ。

このプログラムを実行すると、

  1. 関数helloが呼ばれる
  2. @@ -3098,7 +3098,7 @@

    終了条件付きループ

    780

このプログラムは、

    -
  1. "\>"と表示してユーザーから整数値を入力
  2. +
  3. "\>"と表示してユーザーから整数値を入力
  4. これまでの入力との合計値を出力
  5. 1.に戻る
@@ -3120,9 +3120,9 @@

終了条件付きループ

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

関数input"\>"を表示してユーザーからの入力を得て戻り値として返すだけの関数だ。

-

"sum = sum + input()"は、変数sumに新しい値を代入するもので、その代入する値というのは、代入する前の変数sumの値と関数inputの戻り値を足した値だ。

-

このような変数xに何らかの値nを足した結果を元の変数xに代入するという処理はとても多く使われるので、C++では"x = x + n"を意味する省略記法"x += n"がある。

+

関数input"\>"を表示してユーザーからの入力を得て戻り値として返すだけの関数だ。

+

"sum = sum + input()"は、変数sumに新しい値を代入するもので、その代入する値というのは、代入する前の変数sumの値と関数inputの戻り値を足した値だ。

+

このような変数xに何らかの値nを足した結果を元の変数xに代入するという処理はとても多く使われるので、C++では"x = x + n"を意味する省略記法"x += n"がある。

int main()
 {
     int x = 1 ;
@@ -3159,7 +3159,7 @@ 

終了条件付きループ

}

うまくいった。このループは、ユーザーが0を入力した場合に繰り返しを終了する、条件付きのループだ。

インデックスループ

-

最後に紹介するループは、インデックスループだ。\(n\)"hello\n"sを出力するプログラムを書こう。問題は、この\(n\)はコンパイル時には与えられず、実行時にユーザーからの入力で与えられる。

+

最後に紹介するループは、インデックスループだ。\(n\)"hello\n"sを出力するプログラムを書こう。問題は、この\(n\)はコンパイル時には与えられず、実行時にユーザーからの入力で与えられる。

-

関数の宣言というのは戻り値の型や関数名や引数リストだけで、";"で終わる。

-

関数の定義とは、関数の宣言のあとの"{}"だ。この場合、宣言のあとに";"は書かない。

+

関数の宣言というのは戻り値の型や関数名や引数リストだけで、";"で終わる。

+

関数の定義とは、関数の宣言のあとの"{}"だ。この場合、宣言のあとに";"は書かない。

関数の定義は一度しか書けない。

// 定義
@@ -3219,7 +3219,7 @@ 

インデックスループ

// 名前fの定義 void f() { }
-

さて、話を元に戻そう。これから学ぶのは\(n\)"hello\n"sと出力するプログラムの書き方だ。ただし\(n\)はユーザーが入力するので実行時にしかわからない。すでに我々はユーザーから\(n\)の入力を受け取る部分のプログラムは書いた。

+

さて、話を元に戻そう。これから学ぶのは\(n\)"hello\n"sと出力するプログラムの書き方だ。ただし\(n\)はユーザーが入力するので実行時にしかわからない。すでに我々はユーザーから\(n\)の入力を受け取る部分のプログラムは書いた。

-

あとは関数hello_n(n)\(n\)"hello\n"sと出力するようなループを実行すればいいのだ。

-

すでに我々は無限回"hello\n"sと出力する方法を知っている。まずは無限回ループを書こう。

+

あとは関数hello_n(n)\(n\)"hello\n"sと出力するようなループを実行すればいいのだ。

+

すでに我々は無限回"hello\n"sと出力する方法を知っている。まずは無限回ループを書こう。

void hello_n( int n )
 {
 loop :
@@ -3282,7 +3282,7 @@ 

インデックスループ

ではどうやって書くのか。以下のようにする。

  1. 変数iを作り、値を0にする
  2. -
  3. 変数i", "sを出力する
  4. +
  5. 変数i", "sを出力する
  6. 変数iをインクリメントする
  7. goto 2.
@@ -3320,7 +3320,7 @@

インデックスループ

  • 3回目のif文実行のとき、i == 2
  • 4回目のif文実行のとき、i == 3
  • -

    ここではn == 3なので、3回まで実行してほしい。つまり3回目まではtrueになり、4回目のif文実行のときにはfalseになるような式を書く。そのような式とは、ズバリ"i != n"だ。

    +

    ここではn == 3なので、3回まで実行してほしい。つまり3回目まではtrueになり、4回目のif文実行のときにはfalseになるような式を書く。そのような式とは、ズバリ"i != n"だ。

    void hello_n( int n )
     {
         int i = 0 ;
    @@ -3478,7 +3478,7 @@ 

    終了条件付きループ

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

    ここではちょっと難しいコードが出てくる。whileの中の条件が、"( x = input() ) != 0"になっている。これはどういうことか。

    +

    ここではちょっと難しいコードが出てくる。whileの中の条件が、"( x = input() ) != 0"になっている。これはどういうことか。

    実は条件bool型に変換さえできればどんな式でも書ける。

    int main()
     {
    @@ -3501,7 +3501,7 @@ 

    終了条件付きループ

    }

    while文の方が圧倒的に書きやすいことがわかる。

    インデックスループ

    -

    \(n\)"hello\n"sと出力するプログラムをwhile文で書いてみよう。ただし\(n\)はユーザーが入力するものとする。

    +

    \(n\)"hello\n"sと出力するプログラムをwhile文で書いてみよう。ただし\(n\)はユーザーが入力するものとする。

    まずはgoto文でも使ったループ以外の処理をするコードから。

    void hello_n( int n ) ;
     
    @@ -3581,7 +3581,7 @@ 

    インデックスループ

    std::cout << "4\t8\t12\n5\t10\t15"s ; }

    エスケープ文字\nが改行文字に置き換わるように、エスケープ文字\tはタブ文字に置き換わる。

    -

    九九の表はどうやって出力すればよいだろうか。計算自体はC++では"a*b"でできる。上の表がどのように計算されているかを考えてみよう。

    +

    九九の表はどうやって出力すればよいだろうか。計算自体はC++では"a*b"でできる。上の表がどのように計算されているかを考えてみよう。

    1*1 1*2 1*3 1*4 1*5 1*6 1*7 1*8 1*9 
     2*1 2*2 2*3 2*4 2*5 2*6 2*7 2*8 2*9 
     3*1 3*2 3*3 3*4 3*5 3*6 3*7 3*8 3*9 
    @@ -3591,7 +3591,7 @@ 

    インデックスループ

    7*1 7*2 7*3 7*4 7*5 7*6 7*7 7*8 7*9 8*1 8*2 8*3 8*4 8*5 8*6 8*7 8*8 8*9 9*1 9*2 9*3 9*4 9*5 9*6 9*7 9*8 9*9
    -

    これを見ると、"a*b"のうちのa1から9までインクリメントし、それに対してb1から9までインクリメントさせればよい。つまり、9回のインデックスループの中で9回のインデックスループを実行することになる。ループの中のループだ。

    +

    これを見ると、"a*b"のうちのa1から9までインクリメントし、それに対してb1から9までインクリメントさせればよい。つまり、9回のインデックスループの中で9回のインデックスループを実行することになる。ループの中のループだ。

    @@ -3749,7 +3749,7 @@

    for文

    for (;;) std::cout << "hello\n"s ; }
    -

    このプログラムは"hello\n"sと無限に出力し続けるプログラムだ。"for(;;)""for(;true;)"と同じ意味であり、"while(true)"とも同じ意味だ。

    +

    このプログラムは"hello\n"sと無限に出力し続けるプログラムだ。"for(;;)""for(;true;)"と同じ意味であり、"while(true)"とも同じ意味だ。

    do文

    do文while文に似ている。

    @@ -3902,7 +3902,7 @@

    再帰関数

    }
    1. 関数mainは関数helloを呼び出す
    2. -
    3. 関数hello"hello\n"と出力して関数helloを呼び出す
    4. +
    5. 関数hello"hello\n"と出力して関数helloを呼び出す

    関数helloは必ず関数helloを呼び出すので、この実行は無限ループする。

    関数が自分自身を呼び出すことを、再帰(recursion)という。

    @@ -4785,7 +4785,7 @@

    実践例

    // printfデバッグ std::cout << "debug after : "s << v.at(head) << ", " << v.at(min) << "\n"s ;// printfデバッグ -

    "debug before:"は交換前、"debug after:"は交換後の2つの要素の値だ。

    +

    "debug before:"は交換前、"debug after:"は交換後の2つの要素の値だ。

    以下は実行結果の一部だ。

    debug: v = { 3, 8, 2, 5, 6, 9, 4, 1, 7, }
     debug before: 3,  1
    @@ -4857,10 +4857,10 @@ 

    std::cerr

    standard error output $ ./program | grep error standard error output
    -

    標準出力には"standard output\n"しか出力されていない。通常のリダイレクトやパイプで扱われるのも標準出力だけだ。そのため、/dev/nullにリダイレクトすると標準エラー出力しか見えないし、grepにパイプしても標準出力しか受け取らない。

    +

    標準出力には"standard output\n"しか出力されていない。通常のリダイレクトやパイプで扱われるのも標準出力だけだ。そのため、/dev/nullにリダイレクトすると標準エラー出力しか見えないし、grepにパイプしても標準出力しか受け取らない。

    標準出力と標準エラー出力を別々にリダイレクトする方法もある。

    $ ./program > cout.txt 2> cerr.txt
    -

    これを実行すると、ファイルcout.txtには"standard output\n"が、ファイルcerr.txtには"standard error output\n"が出力されている。

    +

    これを実行すると、ファイルcout.txtには"standard output\n"が、ファイルcerr.txtには"standard error output\n"が出力されている。

    これを使って先ほどのプログラムを書き直すと以下のようになる。

    -

    NaNとの比較結果はすべてfalseとなる。

    +

    NaNとの比較結果はNaNと非NaNの非同値比較以外はすべてfalseとなる。

    + // true + bool b = NaN != 0.0 ; + + // false + bool a = NaN == 0.0 ; + bool c = NaN == NaN ; + bool d = NaN != NaN ; + bool e = NaN < 0.0 ; +}

    整数であれば、'a == b'falseであるならば、'a != b'なのだと仮定してもよいが、こと浮動小数点数の場合、NaNの存在があるために必ずしもそうとは限らない。上の例でわかるように、NaNとの比較はすべてfalseになる。

    有効桁数

    浮動小数点数は正確な値のすべての桁数を表現できない。表現できるのは仮数部が何桁を正確に表現できるかに依存している。この有効桁数は、numeric_limits<T>::digits10で取得できる。

    @@ -7467,7 +7469,7 @@

    基本

    std::cout << "hello"s ; int x = 1 + 1 ;} ; -

    最後の文末の最後に付けるセミコロンだ。これは"1+1 ;"とするのと変わらない。"1+1""[](){}"で、を使うことができる。だけが入ったを専門用語では式文と呼ぶが特に覚える必要はない。

    +

    最後の文末の最後に付けるセミコロンだ。これは"1+1 ;"とするのと変わらない。"1+1""[](){}"で、を使うことができる。だけが入ったを専門用語では式文と呼ぶが特に覚える必要はない。

    ラムダ式なので式文の中に書くことができる。

    @@ -8148,9 +8150,9 @@

    より自然な初期化

    fractional b(1, 2) ; fractional c{1, 2} ;} -

    複数の引数を取るコンストラクターを呼び出すには"="は使えない。"()""{}"を使う必要がある。

    +

    複数の引数を取るコンストラクターを呼び出すには"="は使えない。"()""{}"を使う必要がある。

    上のコードを見ると、コンストラクターは引数の数以外にやっていることはほとんど同じだ。こういう場合、コンストラクターを1つにする方法がある。

    -

    実はコンストラクターに限らず、関数はデフォルト実引数を取ることができる。書き方は仮引数に"="で値を書く。

    +

    実はコンストラクターに限らず、関数はデフォルト実引数を取ることができる。書き方は仮引数に"="で値を書く。

    void f( int x = 0 )
     { }
     
    @@ -8238,7 +8240,7 @@ 

    より自然な初期化

    このプログラムを実行すると、以下のように出力される。

    constructor
     delegating constructor
    -

    まず"S()"が呼ばれるが、処理を"S(int)"にデリゲートする。"S(int)"の処理が終わり次第"S()"の関数の本体が実行される。そのためこのような出力になる。

    +

    まず"S()"が呼ばれるが、処理を"S(int)"にデリゲートする。"S(int)"の処理が終わり次第"S()"の関数の本体が実行される。そのためこのような出力になる。

    コンストラクターを減らすのはよいが、減らしすぎても不便だ。以下の例を見てみよう。

    -

    そのため、上の例で"x+y""y+x"を両方使いたい場合は、

    +

    そのため、上の例で"x+y""y+x"を両方使いたい場合は、

    も必要だ。

    現実のコードでは、二項演算子のオーバーロードは以下のように書くことが多い。

    @@ -10351,7 +10353,7 @@

    constなイテレーター: c

    傲慢なエラー処理: 例外

    例外を投げる

    std::arrayの実装方法はほとんど解説した。読者はstd::arrayの実装方法を知り、確固たる自信の元にstd::arrayを使えるようになった。ただし、1つだけ問題がある。

    -

    "std::array"のユーザーはあらかじめ設定した要素数を超える範囲の要素にアクセスすることができてしまう。

    +

    "std::array"のユーザーはあらかじめ設定した要素数を超える範囲の要素にアクセスすることができてしまう。

    -

    このプログラムを実行すると、非0を入力した場合、"Success!\n"が出力される。0を入力した場合、例外が投げられる。例外が投げられると、通常の実行はすっ飛ばされる。エラー処理はしていないので、プログラムは終了する。

    +

    このプログラムを実行すると、非0を入力した場合、"Success!\n"が出力される。0を入力した場合、例外が投げられる。例外が投げられると、通常の実行はすっ飛ばされる。エラー処理はしていないので、プログラムは終了する。

    std::arraystd::vectorのメンバー関数at(n)nが要素数を超える場合、例外を投げている。

    array::reference array::at( std::size_t n )
     {
    @@ -12577,7 +12579,7 @@ 

    出力イテレーター

    ostream_iteratorは出力ストリーム(ostream)に対するイテレーターだ。コンストラクターに出力先の出力ストリームを渡すことで値を出力先に出力してくれる。今回はstd::coutだ。

    上のような出力イテレーターがoperator =で以下のようなことをしていたらどうだろう。

    @@ -12733,7 +12735,7 @@

    入力イテレーター

    { bool b = std::cin.fail() ; }
    -

    std:cinが失敗状態になる理由はいくつかあるが、EOFが入力された場合や、指定した型の値を読み込めなかった場合、例えばint型を読み込むのに入力が"abcd"のような文字列だった場合にtrueになる。

    +

    std:cinが失敗状態になる理由はいくつかあるが、EOFが入力された場合や、指定した型の値を読み込めなかった場合、例えばint型を読み込むのに入力が"abcd"のような文字列だった場合にtrueになる。

    valuestd::cinから読み込んだ値だ。

    イテレーターから値を読み込むのはoperator *の仕事だ。これは単にvalueを返す。

    const reference operator *() const
    @@ -13342,7 +13344,7 @@ 

    operator new/operator delete

    -

    使い方はmallocとほぼ同じだ。"operator new"までが名前なので少し混乱するが、通常の関数呼び出しと同じだ。

    +

    使い方はmallocとほぼ同じだ。"operator new"までが名前なので少し混乱するが、通常の関数呼び出しと同じだ。

    グローバル名前空間であることを明示するために::を使っている。

    operator newで確保したメモリーは、operator deleteで解放するまで有効だ。

    @@ -13484,7 +13486,7 @@

    クラス型の値の構築

    void * ptr = ::operator new ( sizeof( Logger ) ) ; Logger * logger_ptr = new (ptr) Logger{"Alice"s} ; }
    -

    このプログラムを実行すると、"Alice is constructed."と出力される。

    +

    このプログラムを実行すると、"Alice is constructed."と出力される。

    クラスのオブジェクトを適切に破棄するためには、デストラクターを呼ばなければならない。通常の変数ならば、変数が寿命を迎えたときに自動的にデストラクターが呼ばれてくれる。

    int main()
     {
    @@ -14633,7 +14635,7 @@ 

    resize

    std::vector<X> v ; v.resize(5) ; }
    -

    このプログラムを実行すると、"default constructed.\n"は5回標準出力される。

    +

    このプログラムを実行すると、"default constructed.\n"は5回標準出力される。

    resize(sz, value)resizeを呼び出した結果要素が増える場合、その要素をvalueで初期化する。

    int main()
     {
    @@ -17104,11 +17106,11 @@ 

    エンディアンの問題

    // 下位バイト print( rep[1] ) ; }
    -

    筆者の環境では"21"と表示される。これはつまり、2つのバイトのうち、下位バイトの方が先に配置されているということだ。

    +

    筆者の環境では"21"と表示される。これはつまり、2つのバイトのうち、下位バイトの方が先に配置されているということだ。

    世の中にはリトルエンディアン(Little Endian)とビッグエンディアン(Big Endian)がある。これは複数バイトの順序の違いだ。

    リトルエンディアンは下位バイトから配置する。

    ビッグエンディアンは上位バイトから配置する。

    -

    リトルエンディアン環境では、上のプログラムは"21"と表示する。ビッグエンディアン環境では、"12"と表示する。

    +

    リトルエンディアン環境では、上のプログラムは"21"と表示する。ビッグエンディアン環境では、"12"と表示する。

    エンディアンの存在により、UTF-16とUTF-32は2つのバイト列表現が存在することになる。

    UTF-8

    UTF-8は最も後発のUnicodeのコードポイントの文字エンコードだ。

    @@ -17193,7 +17195,7 @@

    通常の文字リテラル

    - +
    二重引用符\"\"
    @@ -17226,9 +17228,9 @@

    通常の文字列リテラル

    という通常の文字列リテラルは、バックスラッシュとラテンアルファベットnではなく、改行文字1文字になる。

    通常の文字列リテラルは末尾にnull文字(\0)が付与される。このために、配列のサイズは文字数+1になる。

    -

    具体的な例では、"abc"という通常の文字列リテラルの型はconst char [4]になる。これは以下のような配列に等しい。

    +

    具体的な例では、"abc"という通常の文字列リテラルの型はconst char [4]になる。これは以下のような配列に等しい。

    -

    "hello"の型はconst char [6]になる。

    +

    "hello"の型はconst char [6]になる。

    char型の配列の初期化に通常の文字列リテラルを使うことができる。

    @@ -17270,7 +17272,7 @@

    UTF-8/UTF-16/UTF-32

    char16_t s2[] = u"hello" ; // char32_t [6] char32_t s3[] = U"hello" ;
    -

    "いろは"をそれぞれの文字列リテラルで表現すると以下のようになる。

    +

    "いろは"をそれぞれの文字列リテラルで表現すると以下のようになる。

    以下のように解釈される。

    文字'が'はUnicodeコードポイントでは結合済みコードポイントのU+304Cで表現できるが、コードポイントU+304B(HIRAGANA LETTER KA)のあとに直ちに続いて、コードポイントU+3099(COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)を使って表現してもよい。

    // u8"\u304C"
    @@ -17300,7 +17302,7 @@ 

    UTF-8/UTF-16/UTF-32

    変数ga1, ga2はどちらもUnicodeとして正しい「が」という1文字の表現だ。Unicodeでは複数のコードポイントで1文字を表現することもあるし、意味的に表示的に同じ文字に対して複数の表現方法がある。

    -

    Apple macOSはUnicodeの正規化として一般的なNFC(Canonical Composition)ではなくNormalization Form D(NFD)を使っているので、濁点や半濁点は必ず分解される。Apple macOSではu8"\u304B\u3099"が一般的な表現で、それ以外の環境ではu8"\u304C"が一般的な表現だ。しかし、どちらも意味上は同じ表現だ。

    +

    Apple macOSはUnicodeの正規化として一般的なNFC(Canonical Composition)ではなくNormalization Form D(NFD)を使っているので、濁点や半濁点は必ず分解される。Apple macOSではu8"\u304B\u3099"が一般的な表現で、それ以外の環境ではu8"\u304C"が一般的な表現だ。しかし、どちらも意味上は同じ表現だ。

    Unicodeの奇妙で面白い例は枚挙に暇がない。ここでは日本語を扱う際によくある注意点を説明したが、ほかにも絵文字、デーヴァナーガリー(ヒンディー語、マラーティー語、ネパール語)、モンゴル文字、アラビア文字、ヘブライ文字など扱いの難しい文字がたくさんある。

    重要な点をまとめると、

      @@ -17312,7 +17314,7 @@

      生文字列リテラル

      しかしエスケープシーケンスがあるために、バックスラッシュを普通に使うには、\\と書かなければならない。例えば上の文字列リテラルを改行文字に続いて「は改行文字」ではなく、本当に「\nは改行文字」という文字列にしたい場合、以下のように書かなければならない。

      -

      また、単一引用符'や二重引用符"もエスケープシーケンスが必要だ。

      +

      また、単一引用符'や二重引用符"もエスケープシーケンスが必要だ。

      また、以下のような内容の文字列をリテラルとして書きたい場合、

      @@ -17557,7 +17559,7 @@

      null終端文字列の操作

      s[5] = 'f' ; s[6] = '\0' ; }
    -

    このコードで、変数sは最終的に"abcdef"という文字列になる。最後のnull文字による終端を忘れてはならない。

    +

    このコードで、変数sは最終的に"abcdef"という文字列になる。最後のnull文字による終端を忘れてはならない。

    ここで、配列sの要素数は7以上でなければならない。最終的なnull終端文字列を表現するには最低でもchar [7]が必要だからだ。

    例えば2つのnull終端文字列を結合する場合で、どちらもconstであったり、十分なサイズがなかった場合、2つの文字列を保持できるサイズのメモリーを確保して、コピーしなければならない。

    // s1, s2を結合して使う関数
    @@ -17685,7 +17687,7 @@ 

    部分文字列の検索

    // 32 auto dog = text.find("dog"sv) ; }
    -

    文字列"fox"に一致する部分文字列の先頭'f'の文字型の値へのインデックスは12で、"dog"'d'36だ。この結果は、上のソースコードに使っている文字が1文字につき1文字型の値を使うためだ。通常は文字数と連続した文字型の要素へのインデックスは等しくならない。

    +

    文字列"fox"に一致する部分文字列の先頭'f'の文字型の値へのインデックスは12で、"dog"'d'36だ。この結果は、上のソースコードに使っている文字が1文字につき1文字型の値を使うためだ。通常は文字数と連続した文字型の要素へのインデックスは等しくならない。

    例えば以下のコードを実行すると、

    int main()
     {
    @@ -17716,7 +17718,7 @@ 

    部分文字列の検索

    // 見つからなかった std::cout << "not found." ; }
    -

    この場合、変数textに文字列"abc"はないので、nposが返る。nposが返ったかどうかはnposと比較すればわかる。npos-1と等しいので、以下のようにも書ける。

    +

    この場合、変数textに文字列"abc"はないので、nposが返る。nposが返ったかどうかはnposと比較すればわかる。npos-1と等しいので、以下のようにも書ける。

    findの亜種として、rfindがある。

    @@ -17789,7 +17791,7 @@

    文字列の挿入

    text.insert( text.find("cat"sv), "fat "sv ) ; // textは"big fat cat" }
    -

    これはtext.find("cat"sv)でまず部分文字列"cat"の先頭へのインデックスを探し、そこに文字列"fat "を挿入している。結果として変数text"big fat cat"となる。

    +

    これはtext.find("cat"sv)でまず部分文字列"cat"の先頭へのインデックスを探し、そこに文字列"fat "を挿入している。結果として変数text"big fat cat"となる。

    部分文字列の削除

    文字列から部分文字列を削除するにはerase( pos, n )を使う。posは削除すべき先頭のインデックスで、nは削除すべきインデックス数だ。

    int main()
    @@ -17799,7 +17801,7 @@ 

    部分文字列の削除

    text.erase( 0, dirty.size() ) ; // textは"cat" }
    -

    このプログラムは文字列"dirty cat"から"dirty "を削除し、"cat"にする。

    +

    このプログラムは文字列"dirty cat"から"dirty "を削除し、"cat"にする。

    -

    このプログラムは文字列"big fat cat"から部分文字列"fat"を検索し、その先頭から変数fatのサイズ文の部分文字列を削除する。結果として変数text"big cat"になる。

    +

    このプログラムは文字列"big fat cat"から部分文字列"fat"を検索し、その先頭から変数fatのサイズ文の部分文字列を削除する。結果として変数text"big cat"になる。

    先頭から末尾までを削除すると、clear()と同じ意味になる。

    -

    このコードは、文字列textから部分文字列"ugly"を探し、その先頭へのインデックスと文字列"ugly"のサイズを指定することで、部分文字列"ugly"を、文字列prettyの値である"pretty"に置換する。結果としてtext"pretty cat"になる。

    +

    このコードは、文字列textから部分文字列"ugly"を探し、その先頭へのインデックスと文字列"ugly"のサイズを指定することで、部分文字列"ugly"を、文字列prettyの値である"pretty"に置換する。結果としてtext"pretty cat"になる。

    その他の推奨できない操作

    basic_stringにはこのほかにさまざまな、現代では推奨できない操作がある。

    例えばoperator []で文字列をインデックスでアクセスできる。これは基本実行文字セットに対しては動く。

    @@ -17848,7 +17850,7 @@

    その他の推奨できない操 // 0xe3 auto c = text[0] ; } -

    textのインデックス0にあたる文字型の値はu8'い'ではない。UTF-8は文字「い」を文字型1つで表現できないからだ。u8"いろは"というUTF-8文字列リテラルはすでに学んだように、以下のように表現される。

    +

    textのインデックス0にあたる文字型の値はu8'い'ではない。UTF-8は文字「い」を文字型1つで表現できないからだ。u8"いろは"というUTF-8文字列リテラルはすでに学んだように、以下のように表現される。

    文字「い」をUTF-8で表現するためには、char8_t型の値が3つ必要で、0xe3, 0x81, 0x84というシーケンスでなければならない。そのため、個々の文字型の値をインデックスでアクセスしても意味がない。また、size()は文字数を返すのではなく、インデックス数を返す。

    @@ -17860,7 +17862,7 @@

    その他の推奨できない操 // 3 auto i = text.find_first_of("abc"sv) ; } -

    i3になる。なぜならば、find_first_of("abc"sv)a, b, cのうちいずれかの文字である最初のインデックスを返すからだ。

    +

    i3になる。なぜならば、find_first_of("abc"sv)a, b, cのうちいずれかの文字である最初のインデックスを返すからだ。

    この機能はUnicodeでは使えない。というのも1文字型で1文字を表現できないからだ。

    basic_string_viewの操作

    basic_string_viewbasic_stringとほぼ同じ操作が行える。ただし、basic_string_viewは書き換えることができないので、一部の操作が使えない。append, insert, erase, replaceは使えない。basic_string_view同士のoperator +もない。

    @@ -19254,7 +19256,7 @@

    #includeディレクティブ

    #includeは以下のいずれかの文法を持つ。

    -

    #includeは指定したファイルパスのファイルの内容をその場所に挿入する。このファイルをヘッダーファイルという。<>によるファイルパスは、標準ライブラリやシステムのヘッダーファイルを格納したディレクトリーからヘッダーファイルを探す。""によるファイルパスは、システム以外のディレクトリーからもヘッダーファイルを探す。例えばカレントディレクトリーなどだ。

    +

    #includeは指定したファイルパスのファイルの内容をその場所に挿入する。このファイルをヘッダーファイルという。<>によるファイルパスは、標準ライブラリやシステムのヘッダーファイルを格納したディレクトリーからヘッダーファイルを探す。""によるファイルパスは、システム以外のディレクトリーからもヘッダーファイルを探す。例えばカレントディレクトリーなどだ。

    例えば、以下のようなヘッダーファイルfoo.hがあり、

    @@ -19754,7 +19756,7 @@

    定義済みマクロ名

    __DATE__ -"Mmm dd yyyy" +"Mmm dd yyyy" ソースファイルがプリプロセスされた日付 Mmmは月、ddは日、yyyyは年
    月の文字列はasctimeが生成するものと同じ
    日が1桁の場合、ddの最初の文字は空白文字