メンバー関数
この章では構造体のみに焦点を当てているが、この章のほとんどの情報はクラスにも適用できる。
この章では、構造体のメンバー関数について説明し、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行で構成されるようになったことに注目しよう。