メンバー関数
この章では構造体のみに焦点を当てているが、この章のほとんどの情報はクラスにも適用できる。
この章では、構造体のメンバー関数について説明し、string形式でオブジェクトを表現するために使用される特別なメンバー関数toString()を定義する。
構造体やクラスが定義されると、通常、それとともにいくつかの関数も定義される。このような関数の例は、前の章で見たことがある。addDuration()とinfo()のオーバーロードは、TimeOfDay型で使用するために特別に記述されている。ある意味で、この2つの関数はTimeOfDayのインターフェースを定義している。
addDuration()とinfo()の両方の最初のパラメータは、各関数が操作するTimeOfDayオブジェクトだ。さらに、これまで見てきた他のすべての関数と同様に、この2つの関数は、他のスコープの外側のモジュールレベルで定義されている。
構造体のインターフェースを決定する一連の関数の概念は、非常に一般的だ。そのため、型と密接に関連する関数は、その型の本体内で定義することができる。
メンバー関数の定義
structの波括弧で囲まれた部分で定義された関数は、メンバー関数と呼ばれる。
メンバー関数は、メンバー変数と同じ方法で、オブジェクトの名前からドットで区切ってアクセスする。
入力および出力操作でstdinおよびstdoutを明示的に指定する際に、メンバー関数を以前使用したことがある。
上記のreadf()とwriteln()は、それぞれオブジェクトstdinとstdoutに対して動作するメンバー関数の呼び出しだ。
info()をメンバー関数として定義しよう。その以前の定義は次の通りだった。
info()をメンバー関数にするには、その定義を構造体内に移動するだけで済むわけではない。この関数は2つの点で変更する必要がある。
- メンバー関数は、オブジェクトを明示的にパラメータとして受け取らない。
- そのため、メンバー変数は単に
hourとminuteとして参照される。
これは、メンバー関数は常に既存のオブジェクトに対して呼び出されるためだ。オブジェクトは、メンバー関数から暗黙的に利用可能だ。
上記のinfo()メンバー関数は、timeオブジェクトに対して呼び出されている。関数定義内で参照されているメンバーhourおよびminuteは、timeオブジェクトのメンバー、具体的にはtime.hourおよびtime.minuteに対応している。
上記のメンバー関数の呼び出しは、以下の通常の関数呼び出しとほぼ同じである。
オブジェクトでメンバー関数が呼び出されると、そのオブジェクトのメンバーは関数から暗黙的にアクセス可能になる。
morningで呼び出されると、メンバー関数内で使用されるhourおよびminuteは、morning.hourおよびmorning.minuteを参照する。同様に、eveningで呼び出されると、これらはevening.hourおよびevening.minuteを参照する。
10:00-22:00
toString() stringの表現の場合
前章で、info()関数の制限について説明した。この関数には、少なくとももう1つ不便な点がある。それは、人間が読める形式で時刻を出力するものの、'-'文字の出力と行の終了は、依然としてプログラマが明示的に行う必要があることだ。
しかし、TimeOfDayオブジェクトを、次のコードのように基本的な型と同じように簡単に使用できれば、より便利だろう。
コードを4行から1行に削減できるだけでなく、オブジェクトを任意のストリームに出力することも可能になる:
ユーザー定義型のtoString()メンバー関数は特別に扱われる。これは、オブジェクトのstring表現を生成するために自動的に呼び出される。toString()は、オブジェクトのstring表現を返さなければならない。
詳細には立ち入らず、まずtoString()関数の定義を見てみよう。
toString()まだ意味のある出力を生成していないが、出力から、writefln()によって2つのオブジェクトに対して2回呼び出されたことがわかる:
todo-todo
また、info()はもう必要ないことにも注意。toString()がその機能を置き換えているからだ。
toString()の最も単純な実装は、std.stringモジュールにあるformat()を呼び出すことだ。format()は、writef()のようなフォーマット出力関数と同じように動作する。唯一の違いは、変数を表示する代わりに、string形式でフォーマットされた結果を返すことだ。
toString() format()の結果を直接返すこともできる:
toString()は、このオブジェクトのみの表現を返すことに注意。残りの出力は、writefln()によって処理される。これは、2つのオブジェクトに対して個別にメンバー関数toString()を呼び出し、その間に'-'文字を出力し、最後にその行を終了する。
10:00-22:00
上記で説明したtoString()の定義は、引数を受け取らず、単にstringを作成してそれを返すだけだ。toString()の別の定義では、delegate引数を受け取る。この定義については、関数ポインタ、デリゲート、およびラムダ式の章で後で説明する。
例:increment()メンバー関数
TimeOfDayオブジェクトに期間を追加するメンバー関数を定義しよう。
その前に、これまで見過ごしていた設計上の欠陥を修正しておこう。構造体の章で、addDuration()で2つのTimeOfDayオブジェクトを足すことは意味のない操作であることを学んだ。
ある時点に追加するのが当然なのは、その時点からの経過時間だ。例えば、出発時間に移動時間を追加すると、到着時間が算出される。
一方、2つの時刻を引く操作は自然な操作であり、その場合は結果が期間になる。
次のプログラムは、分単位の精度を持つ構造体Durationと、それを使用する関数addDuration()を定義している。
今回は、同様の関数をメンバー関数として再定義しよう。addDuration()は、その結果として新しいオブジェクトを生成していた。代わりに、このオブジェクトを直接変更するメンバー関数increment()を定義しよう。
increment()オブジェクトの値を指定した期間だけ増加させる。後の章では、Dの演算子オーバーロード機能によって、+=演算子構文で期間を追加する方法について説明する。
また、unittestブロックは、主にメンバー関数のテストのために、struct定義内にも記述できることに注意。このようなunittestブロックを構造体の本体外に移動することも可能だ。
演習
TimeOfDayに、指定した時間だけ時間を短縮するメンバー関数decrement()を追加しよう。increment()と同様に、その日に十分な時間がない場合は、前の日にオーバーフローするようにして。例えば、00:05から10 分を差し引くと、結果は 23:55になるはずだ。つまり、
decrement()を実装して、以下のユニットテストに合格するようにしよう。info()のオーバーロードであるMeeting、Meal、DailyPlanも、toString()のメンバー関数に変換しよう。info()のオーバーロードについては、関数オーバーロードの章の演習の解答を参照しよう。それぞれの構造体がより便利になったことに加えて、
toString()メンバー関数の実装がすべて1行で構成されるようになったことに注目しよう。