要約
ここまで、このコースのプログラムはどれも一つのブロック main であり、すべてが使われる場所に書かれていた。このレッスンでは、プログラムの一部に名前を付け、その内部を見ずに使う方法、すなわち関数を学ぶ。扱うのは、名前、戻り値の型、引数をコンパイラに告げる宣言、本体を加える定義、そしてその本体に制御を移し、返された値を受け取る呼び出しである。コンパイラがコンパイル時に確かめること(各呼び出しがある宣言と合致すること)と、実行時に起こること(定義への移動と復帰)とを区別する。値を返すべき関数の終わりに return なしで達することが、なぜ未定義動作なのかを説明する。進行中の呼び出しごとにフレームを一つ保持するコールスタックによって実行を追う。そして各関数を契約、すなわち事前条件と事後条件によって仕様化する。契約は、関数が呼び出し側に何を求め、その代わりに何を保証するかを述べる。
学習目標
- 引数と戻り値をもつ関数を宣言し、定義し、呼び出し、プログラムのどの部分が宣言でどの部分が定義かを示す。
- 互いに呼び出し合う関数をもつプログラムの実行を、コールスタックの図で追う。
- 関数をその事前条件と事後条件で仕様化し、事前条件に違反する呼び出しを書く。
- ある経路で値を返さない関数がなぜ未定義動作なのかを説明し、それに対応するコンパイラの警告を見分ける。
なぜ関数か
前の単元のプログラムは、どの問題も main のなかで解いていた。問題が小さいうちはそれで足りた。しかし、プログラムが大きくなると、すぐに三つの困難が現れる。第一は繰り返しである。同じ計算が三か所で必要なら三回書かれ、その計算の誤りは三回直される(あるいは、一か所で直し忘れる)。第二は読むことである。五十行のブロックは、何をするかではなく、どうするかを語り、読者は仕組みから意図を再構成しなければならない。第三はテストである。すべてが混ざっていると、残りを実行せずに一部分だけを確かめる方法がない。
関数とは名前の付いたプログラムの断片であり、引数と呼ばれるデータを受け取り、戻り値と呼ばれる結果を渡す。数学はこの考えを何世紀も前から知っている。平面上の二点間の距離はその四つの座標の関数 であり、三角形の周の長さを計算する人は公式を三回書き直すのではなく、それに名前を付けて三回適用する。C++ でも同じように書く。
このプログラムは三角形の三つの頂点の座標を読み、その周の長さ、すなわち三辺の長さの和を書く。
#include <cmath>#include <iostream>#include <print>// 点 (x1, y1) と点 (x2, y2) の距離。double distance(double x1, double y1, double x2, double y2){ const double dx{x2 - x1}; const double dy{y2 - y1}; return std::sqrt(dx * dx + dy * dy);}int main(){ double ax{}; double ay{}; double bx{}; double by{}; double cx{}; double cy{}; std::cin >> ax >> ay >> bx >> by >> cx >> cy; const double perimeter{distance(ax, ay, bx, by) + distance(bx, by, cx, cy) + distance(cx, cy, ax, ay)}; std::println("周の長さ:{}", perimeter);}0 0 3 0 0 4周の長さ:12頂点が 、、 である三角形の辺の長さは 3、5、4 であり、その和は 12 である。
関数 distance は一度書かれ、三度使われる。main は周の長さの定義、「三つの距離の和」として読める。そして distance は、距離のわかっている二点の組を使って単独でテストできる。こうして欠けていた三つのものが得られる。どの言語でも通用する一般的な教訓は、関数が一つの考えに名前を付けるということである。使う人は、関数がどうするかではなく、何をするかを考える。この使用と実現の分離を手続き抽象と呼び、それが単元全体の主題である。
コンテンツを読み込んでいます…