要約
このレッスンは、式とその演算子にあてられた、コースの第三単元の始まりである。まず単元の目的を説明する。すなわち、どの演算が先に行われるか、結果がどの型になるか、どんな副作用があるか、そしてどの式が意味をもたないかを知ったうえで、値を確実に予測できる式を組み立てることを教えることである。さらに、単元の四つのレッスンと、整数と実数の算術からビットと数学ライブラリまで、条件と未定義動作の一覧を経てそれらを貫く筋道を示し、単元が第二単元から前提とすること、すなわち各データの型、その限界、その変換を思い出させる。そして単元が解決する問題、つまり警告を一つも出さずにコンパイルされるのに、学校で習う六つの問いへの答えがすべて誤っている、あるいは答えが出ないプログラムの問題を提示し、この先の単元と「C++ プログラミング II」コースへどうつながるかを示す。
学習目標
- 単元の内容の順序を述べ、各レッスンがそのあとのレッスンに何をもたらすかを説明する。
- 第 2 単元から必要なこと(整数型と実数型、それらの値の範囲、オーバーフロー、型変換)を思い出し、必要なら復習する。
- 例を用いて、式の値が計算の規則(優先順位、オペランドの型、作用の順序)に左右され、それらが学校の数学の規則と必ずしも一致しないことを予告する。
- 単元の終わりにできるようになるはずのことを確認し、それをこの先の単元で学ぶことと区別する。
導入テスト
単元に取りかかる前に、出発点を測っておくとよい。このテストは自己チェックのためのもので、採点はしない。目的はただ学習の方向づけにある。最初の四問は、単元が第二単元から前提とすることを確かめる。整数どうしの割り算、二進法での書き方、符号なしの引き算、そして符号付きのオーバーフローである。続く六問は、レッスンの順に、単元の中心となる考え方を扱い、そのいくつかは学校の数学や初歩の論理から出発する。まだ正解できなくてもかまわないし、そのうちの二問では「まだわからない」が誠実な答えである。正解であれ不正解であれ、どの答えもその要点を説明し、それを扱うレッスンを示すからである。同じ技能は、単元の最後のまとめのレッスンで再び測る。
単元を始める前に、自分の出発点を測りましょう。短い自己評価です。採点はされず、結果はあなたのブラウザーにだけ保存されます。
問 1前の単元からあるプログラムが
const double r{7 / 2};と宣言し、rを書き出す。何が表示されるか。変数の型が割り算のしかたを決めると考えている。決めるのはオペランドの型である。整数の割り算が丸めると考えている。実際には小数部分を捨てる。値 3 は定数で、doubleで正確に表せる。拒否すべき縮小はない。割り算7 / 2は整数どうしで行われ、整数の商 3 を与える。その 3 がdoubleに変換されるのは、そのあとである。演算を決めるのはオペランドの型であって、結果を受け取る変数の型ではない。レッスン 3.1 はこの観察を一般的な規則にする。すなわち、型は式の各ノードで決まる。復習: 文字・真偽値・型変換・定数
問 2前の単元から数 13 を 8 ビットの二進法で書くとどうなるか。
重みの足し算を誤っている。。繰り返しの割り算の余りを、出てきた順に書いている。余りは最後のものから最初のものへと読む。このビットの並びは 26、つまり 13 の 2 倍である。ビットが一桁ずれている。各桁の重みは右隣の桁の 2 倍である。1 は重み 8、4、1 の桁を占める。レッスン 3.4 はこれらのビットを直接操作するので、すらすら読めるようにしておくとよい。復習: 整数とその表現
問 3前の単元から32 ビットの
unsigned intのもとで、std::println("{}", 0u - 1);は何を書き出すか。接尾辞uを考慮していない。符号なしの型には負の値がない。を法として定義されている符号なしの算術を、符号付きのオーバーフローと混同している。引き算が型の最小値で止まると考えている。実際には一周して戻る。符号なしの算術は を法とする算術である。。結果は定義されており、だからどのサニタイザも報告しない。単元では、混合した式の型変換と、符号なしの型で書くビット演算子のなかで、これに再び出会う。復習: 整数とその表現
問 4前の単元からあるプログラムが
int count{std::numeric_limits<int>::max()};を実行し、続いてcount = count + 1;を実行する。countの値について規格は何を保証するか。ときどき観察されることと、規格が保証することを混同している。最小値へ戻るのは符号なしの型のふるまいであって、符号付きの型のふるまいではない。型の限界を無視している。この数は 32 ビットのintに収まらない。コンパイラは、プログラムが実行時にとる値を知らない。ほとんどの場合、何も言わずにコンパイルする。足し算には、結果が型に収まるという事前条件があり、それを外れると規格は何も約束しない。このコースのサニタイザはプログラムを止める。それがなければ、何が書き出されてもおかしくない。レッスン 3.3 は、この場合を同じ種類のほかの場合とともに一覧にまとめる。復習: 整数とその表現
問 5 と はそれぞれいくつか。
積の優先順位を考えずに、左から右へ計算している。二つ目の式のかっこを無視している。それぞれの式にもう一方の値を割り当てている。かっこの効果を逆にしている。積は和よりも強く結びつく。 では掛け算が先に行われ、かっこはその順序を変える。C++ も同じ約束に従い、レッスン 3.1 はそれを割り算、剰余、結合規則へと広げ、式の木を道具として用いる。復習: 算術演算
問 6数学の除法の定理に従い、余り に という条件を課すとき、商 と余り ( を で割ったときの)はそれぞれいくつか。
C++ が計算する値ではあるが、 を満たさない。数学の余りは負にならない。等式を確かめていない。 であって、 ではない。これも等式を満たさない。。定理は を、 という条件のもとで要求し、この二つの数だけがそれを満たす。一方 C++ は商を 0 方向に切り捨てる。-7 / 2は に、-7 % 2は になる。レッスン 3.1 はこの違いを説明し、C++ の余りから数学の余りを得る方法を教える。復習: 算術演算
問 7「雨が降っていて、かつ寒い」の否定はどれか。
結合子を変えずに各部分を否定している。これは否定よりはるかに多くを主張する。雨が降らずに寒い日まで除外してしまうからである。一方の部分しか否定していない。二番目の選択肢と同じことを言っており、誤りも同じである。連言の否定は否定の選言である。これはド・モルガンの法則の一つである。二つのうち一つが欠ければ、主張は偽になる。レッスン 3.2 は同じ法則をプログラムの条件に適用する。復習: 関係と論理
問 8
std::println("{}", 3 < 5);は何を書き出すか。論理値が数として書き出されると考えている。プレースホルダ{}では、std::printlnは単語を書き出す。式が文字列として書き出されると考えている。書き出されるのはその値である。値である。比較はbool型の式である。比較はほかのどの式とも同じく式である。bool型の値をもち、std::printlnはそれをtrueまたはfalseと書き出す。レッスン 3.2 はこの観察から出発する。条件は計算されるものであり、だから優先順位と木と代数をもつ。復習: 関係と論理
問 9
int a{5};とint b{a++ * 2};のあと、aとbはそれぞれいくつか。誠実な答えである。レッスン 3.3 で式の値とその作用の違いを学び、まとめテストでもう一度問われる。作用を忘れている。a++はaを増やす。aの新しい値を使っている。後置インクリメントが与えるのは前の値である。式a++には値、すなわちaがもっていた値(5)と、aを 6 にする作用とがある。積が使うのは値であって、作用ではない。レッスン 3.3 は、この二つを切り分けることと、一つの式のなかの二つの作用がその式から意味を奪うのはどんなときかを見分けることを教える。復習: 代入・副作用・未定義動作
問 10
std::println("{}", 2 ^ 3);は何を書き出すか。誠実な答えである。レッスン 3.4 で説明し、まとめテストでもう一度問われる。多くの電卓の記法のように、キャレットを累乗と読んでいる。C++ ではそうではない。積と読んでいる。C++ ではキャレットは累乗ではなく、ビットごとの XOR である。 と は重み 1 のビットだけが異なるので、結果は 1 になる。C++ には累乗の演算子がない。レッスン 3.4 で、std::powを使うべきときとシフトを使うべきときを学ぶ。復習: ビットと数学ライブラリ
コンテンツを読み込んでいます…