コンテンツへスキップ
Topos Uranos

要約

このレッスンは、C++ によるプログラミングのはじめの一歩にあてられた、コースの第一単元の始まりである。まず単元の目的を説明する。すなわち、プログラムを書いたことのない人を、ふだんの言葉で述べられた問題から、コンパイルされ、実行され、データを読み、結果を書くプログラムまで導き、その途中でそれぞれの道具が何をしたのかを分かるようにすることである。さらに、単元の六つのレッスンと、日本語で書いたアルゴリズムからエラーの分類までそれらを貫く筋道を示し、前提とするわずかな知識、すなわち学校の算数、書かれた指示に従う習慣、ファイルとフォルダの扱いを明らかにする。また、単元が解決する問題、つまり手でできる計算を、機械が間違えず疲れもせずに実行する手続きに変えるという問題を定式化し、最後に、単元がどこへ通じるか、すなわちこの先の単元と「C++ プログラミング II」コースへの道を示す。

学習目標

  1. 単元の内容の順序を述べ、各レッスンがそのあとのレッスンに何をもたらすかを説明する。
  2. コースが前提とする予備知識(整数と小数の計算、商と余りの計算、書かれた指示に従うこと、決まったフォルダにファイルを保存すること)を確認し、必要なら復習する。
  3. 単元がたどり方を教える道筋を、例を用いて説明する。すなわち、問題文から仕様へ、仕様からアルゴリズムへ、アルゴリズムからコンパイルされ実行されるプログラムへという道筋である。
  4. 単元の終わりにできるようになるはずのことを確認し、それをこの先の単元で学ぶことと区別する。

導入テスト

単元に取りかかる前に、出発点を測っておくとよい。このテストは自己チェックのためのもので、採点はしない。目的はただ学習の方向づけにある。これはコースの第一単元なので、前の単元に関する問いはない。その代わりに、最初の三問は、コースが前提とする知識を確かめる。整数の割り算の商と余り、手続きの手順の順序、そしてファイルの名前・拡張子・フォルダである。続く七問は、単元の六つのレッスンの中心となる考え方を順に扱う。プログラムを書いたことのない人が正解できなくても当然であり、それで構わない。正解であれ不正解であれ、どの答えもその要点を説明し、それを扱うレッスンを示すからである。同じ技能は、単元の最後のまとめのレッスンで再び測る。

導入テスト

単元を始める前に、自分の出発点を測りましょう。短い自己評価です。採点はされず、結果はあなたのブラウザーにだけ保存されます。

  1. 問 1前の単元から47 枚の紙を 6 人に、同じ数の整数枚ずつ配り、一巡分に足りない紙は取っておく。一人あたり何枚もらい、何枚取っておくか。

  2. 問 2前の単元からファイルを電子メールに添付して送るには四つの手順がある。A、宛先のアドレスを書く。B、「送信」を押す。C、新しいメッセージを開く。D、ファイルを添付する。適切な順序はどれか。

  3. 問 3前の単元からあるファイルが C:\Users\ana\programas\suma.cpp に保存されている。その名前は何か、拡張子は何か、どのフォルダにあるか。

  4. 問 4あるレシピにこう書いてある。「生地が乾いていたら、水を大さじ一杯加えてこねる。乾いていなくなるまで繰り返す」。生地が乾いていなくなるには、ちょうど大さじ三杯が必要である。生地が乾いているかどうかを何回尋ねるか。

  5. 問 5以下の指示のうち、誰でも、あるいは機械でも、まったく同じやり方で従えるものはどれか。

  6. 問 6あるプログラムはエラーも警告もなくコンパイルされ、多くのデータで正しく実行されるが、ある特定のデータで「division by zero」というメッセージを出して停止する。エラーはどの時点で生じたか。

  7. 問 7Visual Studio Code とその C++ 拡張機能をインストールしたが、ターミナルで g++ --version を求めると、g++ を認識できないという答えが返ってくる。最もありそうなことは何か。

  8. 問 8コンパイラが warning: で始まるメッセージを出し、それでも実行ファイルを作る。これは何を意味するか。

  9. 問 9C++ のプログラムの実行はどこから始まるか。

  10. 問 10あるプログラムが int age{}; を宣言し、年齢を読み取るために std::cin >> age; を実行し、ユーザーが abc と入力する。何が起こるか。

単元の内容

この単元には六つのレッスンがあり、問題から動くプログラムまでの道を順にたどる。どのレッスンもその前のレッスンを前提とし、最初のレッスンは言語について何も前提としない。

  • 問題からアルゴリズムへ。アルゴリズムはプログラムに先立つので、コースはコードなしで始まる。このレッスンでは、問題をその入力と出力と両者の関係(事前条件と事後条件)によって仕様として定めること、アルゴリズムを番号付きの手順で曖昧さなく記述すること、トレース表を使ってそれを手で追うこと、その中に順次・分岐・反復を見分けること、そして周回ごとに減少する量によって繰り返しが停止することを示すことを学ぶ。
  • プログラム・コンパイラ・C++。機械のために書かれたアルゴリズムはプログラムであり、C++ のプログラムは書かれたままの形では実行されない。プリプロセッサ、コンパイラ、リンカがそれを実行ファイルに変換する。このレッスンでは、この三つの段階をたどり、コンパイル時に見つかることと実行時にしか現れないことを区別し、コンパイル方式とインタプリタ方式を比較し、言語の規格を時代順に位置づける。コースが使う C++23 まで、そして各コンパイラで実装が進行中の C++26 までである。
  • Windows・macOS・Linux での環境構築。システムごとに一つずつ、三つの完全なガイドで、C++23 に対応したコンパイラと、エディタ Visual Studio Code とその C++ 拡張機能をインストールし、動作確認用のプログラムですべてが動くことを確かめ、よくある問題を解決する。ターミナルがコンパイラを見つけられない、バージョンが古い、規格を指定していない、といった問題である。
  • コンパイル・実行・設定。編集・保存・コンパイル・実行・読むという作業の流れを、ターミナルとエディタの両方から、このコースのコマンドを使って行う。このレッスンでは、実行ファイルの生成を妨げるエラーと、疑わしいコードを指摘する警告を区別し、プログラムの実行中にそれを見張るサニタイザ(メモリエラーを検出する AddressSanitizer と未定義動作を検出する UndefinedBehaviorSanitizer)を有効にし、出力に日本語の文字の代わりに奇妙な記号が表示される理由を説明する。
  • プログラムの解剖。最小のプログラムを部品ごとに見る。インクルード指令とヘッダ、関数 main と終了コード、名前空間 std、文、ブロック、コメントである。このレッスンでは、コンパイラのメッセージを最初のものから読んで構造のエラーを直す方法を学び、コースのスタイルガイドを定める。
  • 入力・出力・エラー。プログラムは外部とやり取りする。std::print と std::println で、幅と小数の桁数のための書式指定を使って出力し、std::cin で読み取る。このレッスンでは、入力が期待した形をしていないときに何が起こるか、読み取りをどう確かめるかを示し、エラーをそれが現れる時点で分類して単元を締めくくる。コンパイルエラー、リンクエラー、実行時エラー、論理エラーである。

これらをつなぐ糸は、プログラムそのものである。一つ目のレッスンはそれを構想し、二つ目はそれが何に変換されるかを説明し、三つ目はそれを変換する道具をインストールし、四つ目はその道具の使い方と、道具が告げることに耳を傾ける方法を教え、五つ目はそれを解剖し、六つ目はそれを使う人と対話させる。そうであれば、単元の終わりに学生は C++ についてまだごくわずかしか知らないが、C++ でどう仕事をするかは知っている。そして、それこそがコースの残りの部分が前提とすることである。

前提とする知識

このコースは、プログラミングの経験を何も前提としない。その代わりに三つの技能を前提とする。導入テストで何か不確かな点が見つかったなら、今のうちに復習しておくとよい。

第一は学校の算数である。整数と小数の計算をし、演算の順序(かっこがなければ、掛け算と割り算を足し算と引き算より先に行う)を守り、そして何よりも整数の割り算の商と余りを求めることである。この最後のものは見かけ以上に重要である。47 枚の紙を 6 人で分けると、一人 7 枚で 5 枚余る。これを 47=6⋅7+547 = 6 \cdot 7 + 5 と書き、余り 55 は除数 66 より小さい。C++ には商のための演算子と余りのための演算子があり、整数を使う計算の大部分(秒を時間に換算する、数を各桁に分ける、数が偶数かどうかを判定する)はこの二つに帰着する。整数の割り算をじっくり学びたい人は、「代数と幾何 I:数・関数・平面幾何」コースで、その証明とともに見つけられる。ここでは計算できれば十分である。

第二は、書かれた指示に文字どおり従い、ある指示だけでは何をすればよいか分からないときにそれに気づく習慣である。これは知識ではなく、態度の問題である。「塩を好みで加える」と読んで、この指示は実行する人によって変わると気づくことである。単元の最初のレッスンは、この態度を方法に変える。

第三は、コンピュータの基本的な操作である。フォルダを作り、そこに選んだ名前でファイルを保存し、名前の拡張子(.cpp、.txt、.exe)を見分け、各ファイルがどのフォルダにあるかを知っていることである。拡張子を表示するように、今からシステムを設定しておくとよい。Windows のように、既定で拡張子を隠すシステムもあるからである。ソースコードの suma.cpp と実行ファイルの suma.exe はまったく別のファイルであり、両者を取り違えると、ばかげたエラーのもとになる。単元ではターミナルの使い方も教えるので、あらかじめ知っている必要はない。ボタンを押す代わりにコマンドを打ち込む気があれば十分である。最後に、プログラムをインストールするには、自分のコンピュータでそれができなければならない。それができない人のために、インストールのレッスンはオンラインの代替手段を示している。

英語は前提としない。コンパイラのメッセージは英語だが、コースではそれをフィールドごとに読む方法を教えるので、そこで大事なこと(ファイル、行、欠陥の種別)は、英語に通じていなくても分かる。また、学校で学ぶ範囲を超える数学も前提としない。例に使うのは割り算、平均、九九の表、日付、時刻である。

単元が解決する問題

手でできる計算は、まだプログラムではない。両者のあいだの距離こそ、この単元が埋め方を教えるものであり、簡単な問題がその距離を測らせてくれる。

例時・分・秒で表す所要時間

ストップウォッチが各作業の所要時間を秒数で記録する。たとえば 7384 である。これを、分と秒をつねに二桁で表す、時・分・秒のふつうの形で書きたい。2:03:04 である。手で計算すると、割り算の連鎖になる。7384=3600⋅2+1847384 = 3600 \cdot 2 + 184 から 2 時間と 184 秒が得られ、184=60⋅3+4184 = 60 \cdot 3 + 4 から 3 分と 4 秒が得られる。所要時間が一つなら、一分で済む。しかし記録にある千件の所要時間となると、手計算は長く、誤りやすい。この仕事は機械に実行させるのに向いている。

しかし、機械は上の説明を受け取ることができない。機械が受け取るのはプログラミング言語で書かれたテキストであり、そのテキストを書くには、手計算では暗黙のままだった三つの問題を先に解決しておかなければならない。第一は仕様である。何が入り(負でない整数の秒数)、何が出て(三つの整数、つまり時、分、秒)、どんな関係がそれらを結ぶか。tt を総秒数とすると、t=3600h+60m+st = 3600 h + 60 m + s であり、0≤m<600 \leq m < 60 かつ 0≤s<600 \leq s < 60 である。第二はアルゴリズムである。すなわち、許される任意の入力に対してその出力を生む、正確な手順である。

  1. hh の値を、tt を 36003600 で割った商にする。
  2. mm の値を、6060 で割った商(割られる数は tt を 36003600 で割った余り)にする。
  3. ss の値を、tt を 6060 で割った余りにする。
  4. hh、コロン、二桁の mm、コロン、二桁の ss を書く。

第三は、言語が求める正確な形でアルゴリズムを C++ で書き、適切な道具でそれを実行ファイルに翻訳することである。以下のプログラムがそれを行う。そこには、まだ説明していないことが多く含まれている。変数(レッスン 2.1)、整数の割り算の商と余りを与える演算子 / と %(レッスン 3.1)、std::cin による読み取りと書式指定(レッスン 1.6)である。このプログラムの書式指定 {:02} は、数を二桁の位置に左をゼロで埋めて書くもので、そのレッスンが教える幅の一種であり、レッスン 3.1 でも使う。今のところは、その中にアルゴリズムの四つの手順を見分けられれば十分である。

C++ のプログラム
#include <iostream>#include <print>​int main(){    int total{};  // 変数はレッスン 2.1 で説明する    std::print("所要時間(秒):");    std::cin >> total;    const int hours{total / 3600};    const int minutes{total % 3600 / 60};    const int seconds{total % 60};    std::println("{} s = {}:{:02}:{:02}", total, hours, minutes, seconds);}
プログラムへの入力
7384
出力
所要時間(秒):7384 s = 2:03:04

9 行目から 11 行目が手順 1 から 3 にあたり、12 行目が手順 4 にあたる。結果が手計算と一致するのは偶然ではなく、どのプログラムも受けるべき確認である。まず、答えの分かっているデータで試してみることである。

プログラムは問題を解決するが、単元はそれを疑うことも教える。ユーザーが数の代わりに abc と入力すると、読み取りは失敗し、total はゼロのままで、プログラムは何の警報もなく、所要時間は 0:00:00 だと書く。本当らしく見える誤った結論である。負の所要時間を入力した場合は、仕様が何が起こるべきかを定めておらず、結果は意味をなさない。total / 360 を total / 3600 の代わりに書くと、プログラムはコンパイルされ、実行され、誤った結果を出すが、どの道具もそれを警告しない。これらの欠陥はそれぞれ別の種類のエラーに属し、別の時点で見つかり、別のやり方で探される。単元の最後のレッスンがそれらを分類する。ここから、単元が解決する問題は、このプログラムを書くことだけではなく、それがなぜ動くのか、どんなデータで動かなくなるのか、そしてどの道具がそれぞれの欠陥を警告するのか、あるいはしないのかを知ることでもある、ということが導かれる。

したがって、単元を終えた学生は、この種のプログラムを書き、コンパイルし、実行できるようになる。少しの入力データ、順に並んだいくつかの計算、書式をつけて書かれたいくつかの結果からなるプログラムである。C++ で分岐したり文を繰り返したりすること(日本語で書いたアルゴリズムではできるが)や、各データの型を根拠をもって選ぶことは、まだできない。それはこの先の単元がもたらすものである。

この先へ

この単元はコースの入り口である。ここで説明なしに使うものは、のちに以下の順序で説明される。第 2 単元「データと型」は、変数とそれがメモリにどう格納されるか、整数の限界(そして正しい計算がなぜオーバーフローしうるのか)、小数をもつ数の丸め、型のあいだの変換を説明する。第 3 単元「式と演算子」は、負の数の商と余りを含む C++ の算術、条件の論理、そして式において規格が未定義のまま残すものを説明する。第 4 単元「制御フロー」は、最初のレッスンが日本語で書く分岐と反復を C++ に移し、ループが正しく、かつ停止することを示す方法を教える。第 5 単元はプログラムを関数に分解し、第 6 単元は複合データ(配列、ベクター、表、レコード)を、探索とソートのアルゴリズムおよびそのコストとともに導入し、第 7 単元はテキスト、頑健な入力の読み取り、ファイルを扱い、第 8 単元は複数ファイルのプログラム、プログラムの体系的なテスト、デバッガを扱う。

このコースのあとは、「C++ プログラミング II」が、このコースが意図して外に残したものを引き継ぐ。ポインタと動的メモリ、メソッドをもつクラス、自作のテンプレート、無名関数、例外である。これらの主題はどれも、このコースの問題を解くのには必要なく、それらを後回しにすることで、どの言語でも通用することに集中できる。仕様を定め、アルゴリズムを設計し、それを明快に書き、正しいことを確かめることである。この単元のまとめのレッスンは、まさにこの区別、すなわち C++ に固有のものと、ほかのどの言語にも持っていけるものとの区別を取り上げる。