コンストラクタ - みる会図書館


検索対象: 月刊 C MAGAZINE 1993年11月号
17件見つかりました。

1. 月刊 C MAGAZINE 1993年11月号

C 十十入門 p. move t0 ( 1 , 2 ) ; 回は , モジュールの実装とインタフェイス int hl=p. x 十 p. y , と同様の軽い処理て、済みます。 というメンバ関数呼び出しが可能となるの を明確に区別することが重要だ , といいま て、す。通常 , コンストラクタも public にし 通常 , アクセス関数は , インラインにし 、 0 て , どこて、もオプジェクトを生成て、きるよ ます。実行性能を下げることなく , データ クラスはモシュールだ という意味のこ のアクセス制御がて、きるからて、す。データ とをはじめに書きましたが , private, prot うにしておきます。 一方 , x, y は , private の後に宣言されて ected なメンバはモジュールの実装て、あり , メンバを public にするくらいなら , インライ います。 private なメンバは , クラスの中だ public なメンバはモジュールのインタフェイ ンのアクセス関数を用いるようにしましよ け , すなわち , メンバ関数の中だけて、しか スに相当します。データメンバは実装中の フ。 使えません。よって , クラスの外部から 実装みたいなものて、すから , ぜひとも priva コントクタの多重定義 te か protected にしましよう。【例題 9 】て、再 P ・ X び扱います。 P ・ Y lllllllllllllllllllllllllllllllllll 【例」題 7 】ⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢⅢⅢ とアクセスすることはて、きません。コンパ インイメンバ関数 座標値を受け取って初期化するコンスト イル時に , 次のようなエラーになります。 ラクタをインライン形式で追加しなさい。 Error : cannot access private' member. ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅱⅢⅢ【例」題 6 】ⅢⅢⅢⅢⅢⅢⅢⅢⅱⅢⅢⅢ x, y の値を知るには , public メンバ関数 class POint { Point クラスのメンバ関数を , クラスの定 の get x ( ) , get y ( ) を用います。このよう なデータメンバの値を返すだけの関数をア 義の中で定義しなさい。 private : クセス関数と呼びます。逆に , x, y へ値を ⅢⅢⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢ・く解答 > ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅱ int x, y , 書き込むには , move to ( ) ないし r move List 3 のとおり public : to ( ) を使わなければなりません。つまり , Point ( ) メンバ関数は , クラスの中て、定義するこ Point(int ax, int (y) / / 追加 x, y への直接的なアクセスを禁じているの とがて、きます。クラスの中て、定義されたメ / / 追加 て、す。このようにデータへのアクセスを制 ンバ関数は , インライン展開を指示されま 限すれば , Point クラスの知らないところて、 ・・・以下冂様 す。つまり , class F00 { 内容が変化することは , ますありえません。 void abc( ) { ・・ コンストラクタをもうひとつ定義します。 x , y は Point クラスが完全に掌握しているの 前に定義した引数なしのコンストラクタ Po て、す。 前回の ABC 十十の内容を理解した読者な int ( ) は , そのままて、す。 C 十十て、は , 引数 が異なれば同じ関数名を使うことがて、きま らば , 思いあたることがあるて、しよう。則 す。これを関数の多重定義 ( オーバロード ) 【例題 6 】解答インラインメンバ関数の定義 といいます。ここて、は , コンストラクタを 多重定義しています。 inline VOid F00 . と見なされます。 その結果 , Point p(), 2 ) ; 前回お話したように , inline の指示はヒン と , オプジェクトを定義することがて、きる トにすぎず , 複雑な関数て、は , 実際にイン ライン展開されないことがあります。 ようになり , p. x, p. y はそれぞれ 1 , 2 て、初期 しかしながら , Point クラスの get x ( ) , 化されます。つまり , 無引数のほうのコン get y ( ) のような , データメンバの値を返す ストラクタて、はなく , いま追加したコンス だけの単純なアクセス関数は , まず間違い トラクタが使われ , ( 1 , 2 ) が引数になるの なくインライン展開されるて、しよう。この て、す。 ため , ところて、 , 【例題 3 】て、説明した , コンス int =p ・ get_x( ) 十 p ・ get_y ( ) ; トラクタの初期化リストの部分の初期値は , と書いても , 関数呼び出しが行われず , 実は , そのデータのコンストラクタへの引 ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ < 解答 > ・ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ日 と書くと , class F00 { void abc( ) ; List 1 : ass PO i nt { 2 : private: 3 : i n t X, y : 4 : pu b ⅱ c : P 0 i n t ( ) 6 : VOid move-to(int ax, int (y) { 7 : ax : Y 8 : void r-move_to(int dx, int dy) 9 : move_to(x + dx, y + dy); vo i d pr i n t ( ) { ' (' くく x くく COtlt くく くく y くく ' ) ' くく end l; int get-x() { return x; } int get-y() { return y; } X Case Study A 級 B 型 C 十十入門 121

2. 月刊 C MAGAZINE 1993年11月号

ならば , C 十十て、は , 直ちにコンストラクタ が呼び出されるのて、ある。コンストラクタ (constructor) とは , class 名と同じ名前を持 っ特別なメソッドて、ある。 class 定義の冒頭 に出てくる次のメンバ関数がそうて、ある。 / / constructor Point(int x0 0 , int yO x0 ・ X こて、 , C から見ると奇妙なのは , 引数リ ストて、あろう。「 int x0 = 0 」などと , 初期 化がなされている。これはデフォルト値つ きの引数て、ある。 C 十十て、は , デフォルト引 数を指定しておけば , 関数を呼び出す場合 に , 必ずしも仮引数に対して実引数値を指 定しなくてよくなる。デフォルト値が指定 されているかぎり , 実引数は右から順次省 略していくことがて、きる。この機能は , と くにコンストラクタて、は便利て、ある。 「 Pointa ; 」という宣言て、は , 実際にはコ ンストラクタが呼び出されているのだが , こて、は何も引数を渡していない この場 合 , デフォルト値が使われる。その結果 , 「 Pointa(), 0 ) ; 」のように書いたのと同じ になる。この , 関数呼び出しなのか , 変数 宣言なのかよくわからない記法は , C 十十て、 はよくある , 曖昧に見える構文のひとって、 , こて、は変数 a を宣言するとともに , コンス トラクタを ( 0 , 0 ) という引数て、呼び出すと いう意味て、ある。 一方 , 変数 b のほう (Point b(10, 20 ) : ) は , 明示的に引数を与えてコンストラクタ を呼び出している。 なお , 仮に「 Pointc ( 5 ) ; 」のような宣言が あったとすると , これは変数 c を宣言 ンストラクタに実引数をひとつだけ与えて 呼び出したということて、ある。この場合 , コンストラクタの最初の仮引数にだけ実引 数の値が設定され , 2 番目の仮引数はデフォ ルト値が使われることになる。 一方 , 「 Point d [ 5 ] ; 」は配列の宣言て、 , 各要素はコンストラクタに引数を与えない 40 C MAGAZINE 1993 11 1 : 2 : 28 : } : 32 : { 44 : } クトの生成と初期化が終わったと考えられ に代入するだけて、ある。これて、 , オプジェ 渡された座標の初期値をメンバ変数の x と y きる。 Point のコンストラクタて、は , 引数て、 変数て、あるかのように , 直接記すことがて、 変数の名前を , あたかも通常の非メンバの セスすることがて、きる。その場合 , メンバ ールがどうなっていようとも , 自由にアク メンノヾ変数については , アクセスコントロ の定義が含まれるクラス ) て、宣言されている て、は , 自クラス ( すなわち , そのメンバ関数 コンストラクタを始めとするメンバ関数 なるため , 要注意て、ある。 のか角カッコなのかて、 , 意味がまったく異 て呼び出していることになる。丸カッコな さて main の中て、は , 「 a. show( ) ; 」に示す 式文が実行される。これは , オプジェクト a ( 便宜上 , 誤解のないかぎり「変数 a に格納 されているオプジェクト」のことを , たんに 「オプジェクト a 」と呼び慣わすことにする ) に関する , show というメンバ関数の呼び出 して、ある。いい換えれば , オプジェクト a に show という ( 引数なしの ) メッセージを送っ ているとも解釈て、きる。これは関数呼び出 して、あるから , 引数はなくても , 必ず引数 を囲むためのカッコだけは記さなければな らない。これによって , オプジェクト a は自 己の位置を表示するのて、ある。引数なしの メッセージ show が送られると , 次のメソッ ドが起動される。 〃表示用メソッド virtual void show( ) { cout くく " ( " くく x くく くく y くく " ) " くく endl ・ List 3 : 5 : 6 : 7 : 8 : 9 : 13 : 20 : 22 : 23 : 24 : 25 : 27 : 29 : 30 : 31 : 33 : 35 : 36 : 38 : 39 : 42 : 43 : C 十十によるこく簡単なオプジェクトの例 ( ⅱ stl . cpp ) $include く iostream. h> #include く string. h> 4 : / * 「点」を表すオプジェクト * / class P0int { public: / / constructor Point(int x0 = 0 , int y0 ニ x0; X protected : / / 表示用メソッド virtual VOid shov() { cout くく” ( " くく X くく / / 座標移動用メソッド virtual void move(int dx, X 十 = dx ; = の { int (y) { ”くく y くく " ) " くく end l; / / 座標位置 int x; int main() 〃オプジェクトを生成する return 0 : b. shov(); b. 加 ve ← 2 , b. show(); a. shov() : a. move(15, 27 ) : a. shov() ; Point b ( 10 , 2 の : Point a;

3. 月刊 C MAGAZINE 1993年11月号

特集℃言語とオプジェクト指向 Point (x0, (0) { strncpy (myname, name, sizeof(myname) [sizeof (myname) int x0 = 0 , int YO = 0 ) { NarrÜant(char * nane を書き換えても問題は生じない 初期化て、も大差ない。実際次のように定義 しかしながら , こての例て、は代入て、も てある。 化と代入が本質的に異なる操作になるから と , C にはない参照型という変数て、は , 初期 t の変数に対しては初期化しか許さない しているからだ。その理由のひとつは cons いうのは C 十十は初期化と代入を完全に区別 ことになっているのがポイントて、ある。と し , それはあくまて、初期化として行われる スのコンストラクタ呼び出して、ある。ただ という指定て、 , これは実質的には基本クラ それを果たすのがここて、の Point ( x0 , y0) 初期化しなければならない Point を生成する場合には , Point の部分も れているものて、ある。したがって , Named しいメンバ関数が追加 ( ないしは再定義 ) さ って , Point に対して新しいメンバ変数と新 のは先にも述べたとおり , Point の一種て、あ 行うための指定て、ある 0NamedPoint という t(x0, (0) というのが基本クラスの初期化を その次のコロンの後に記されている Poin を定義しなければならない 要に応じて導出クラスて、もコンストラクタ トラクタは継承しないのて、 , このように必 がわかる。なお , C 十十においては , コンス すると , 、、 unnamed" という名前になること えないて、 NamedPoint のオプジェクトを生成 が与えてある。すなわち , とくに名前を与 て、ある。 , こて、は , これにもデフォルト値 て , これが点の名前を指定するための引数 い。 name という char * の引数が増えてい 数リストは Point に比べてひとつ仮引数が多 るのて、少し説明を要するだろう。まず , 引 これには , C 十十の特有の機能を用いてい X x0 ・ strncpy (myname, sizeof (myname) Csizeof (myname) name, だが , これは差分プログラミングの精神 に反する。基本クラスて、行っている動作と 同じコードを , 導出クラスに複写して書き 下すのは避けるべきて、ある。ちなみに , Sm alltalk て、はこのような場合 , オプジェクト の初期化を行うためのメッセージとしては new というものを使うことになっており , 導 出クラス ( Smalltalk て、はサプクラス ) の new て、は次のように記すのが一般的て、ある。 new ↑ super new initialize ”オプジェクトを生成し初期化する " すなわち , super new というメッセージ式 て、スーパクラス (C 十十て、呼ぶ基本クラス ) へと new メッセージを送り , 自分自身のオプ ジェクトのうち , スーパクラスから継承し ている部分に関する初期化を行ってもらう のて、ある。この記法のほうが直観的て、わか りやすいと思うのだが , 残念ながら C 十十て、 は , この Smalltalk のような記述は許されな い。コンストラクタ呼び出しは , オプジェ クトの生成を伴うからて、ある。このため , 先に示したように , コロンの後に初期化の 指定として記すという構文が生まれたもの と考えられる。 C 十十て、は , 言語の性格上こ ういった構文をあえて導入せざるを得なか ったということて、ある。 さて , コンストラクタが理解て、きたなら ば , ほかのメソッドを見てみよう。 Named Point て、はコンストラクタのほかに , ふたっ のメンバ関数定義している。このうち show というメンバ関数は Point て定義しているメ ソッドと同名て、あり , 引数 ( がないというこ と ) も同じて、ある。そのようなケースて、は , 基本クラスの同名のメソッドを上書きした ( override ) という。すなわち , あえて継承す ることを捨て , 異なる機能を実現したのて、 ある。て、は , show の定義を見てみよう。 virtual void show( ) { showName( ) ・ ・ show ( ) ・ Point ・ NamedPoint クラスの show は , 点の位置 を表示するということに関しては , Point の show と同じて、ある。違いは , NamedPoint クラスて、は点に名前がついているのて、 , 座 標を表示する前に , その名前をラベルとし て付加するというところて、ある。最初に「 s howName() ; 」の呼び出しがある。 これは , 自クラス (NamedPoint) て、定義し た showName というメンバ関数を , 自分自 身について呼び出している。自分の名前の 表示はここて、行われる。それから , 「 P0int ・ ・ show() : 」のようにして , Point の show を呼び出す。 これは「自分自身について , クラス Point の show メソッドを呼び出すこと」を意味す る。これによって座標位置表示という処理 は , NamedPoint て、は直接は処理せす , 基本 クラスの Point の show メンバ関数に処理を依 頼しているのて、ある。このタライ回しに近 い記述は , 実は OOP て、は頻出する処理方法 て、ある。コンストラクタにおける初期化の 場合と同じて、 , 基本クラスて、実現されてい る処理を再びコヒ。ーすることは極力避ける のが正しい OOP のスタイルて、ある。 さて , こて、用いた Point ・・というの が , 明示的に呼び出すべきメンバ関数の属 するクラス名を指定する記法て、ある。より 正確にいえば , クは C + + 特有の有効 範囲演算子と呼ばれるものて、あり , クラス Point の有効範囲における show を呼ぶ出すこ とを指示している。これまた , Smalltalk て、 あれば , たんに「 supershow 」のように書く ところて、ある。 SmaIItaIk はスーパクラスをひとっしか許 さない , 単一継承の言語なのて、 , super とい えばひとつに定まる。一方 , 後て、みるよう に C 十十は , 基本クラスを複数指定て、きる多 重継承機能を有するのて , 単純に「基本クラ 特集 c 言語とオプジェクト指向 45

4. 月刊 C MAGAZINE 1993年11月号

クラス モジューノレ 非公開メンバ 実装 インタフェイス . 公開メンバ というように対応します。 ところて、 , C 十十は C 言語の性質を引き継 いて、いますから , ファイルによるモジュー ル化も依然として可能て、す。つまり , C 十十 には , クラスとファイルのふたつのモジュ ールの切り方があるのて、す。 クラスは論理的なモジュール , ファイル は物理的なモジュールということがて、きま す。 流通しているソースプログラムを読むと , だいたい次の 3 種類て、 , 使い分けされている ようて、す。 ①ひとつのファイルに いくつかのクラス やメンバ関数の定義を書く おもに「 1 ファイル = 1 プログラム」の小 さなプログラムのときにあてはまりま モジ、 , 、ル化の方法 す。また , ひとつのファイルの中て、し としクラス か使わないクラスは , ファイルにロー ⅢⅢⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢ【例題 9 】日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ カルに定義されます。 ②ひとつのヘッダファイルにひとつのクラ TabIe 1 の空欄 ( a ) ~ ( c ) を選択肢 ( い ) ~ スを定義し , メンノヾ関数をひとつのファ ⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢⅢⅢ【リ題 8 】ⅢⅢⅱⅢⅢⅢⅢⅢⅢⅢⅢⅢ ( は ) で埋めなさい。 イルに定義する List 4 のプログラムを実行すると , どのよ 選択肢 通常用いられる方法て、す。「クラス = フ ( い ) pub ⅱ c ( 公開 ) なメンバ うに表示されますか ? ァイル = モジュール」となり , クラス名 ( ろ ) クラス ⅧⅢⅢⅢⅢⅢⅢⅡⅱⅢⅢⅢ < 解答 > ⅧⅢⅢⅢⅢⅱⅢⅢⅢⅡⅢⅢ ( は ) private, protected ( 非公開 ) なメン からファイル名の連想がっきやすいと コンストラクタ : 1 いう利点があります。クラス名と inclu ハロー ! : 1 日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢく解答 > ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡⅢⅢ de すべきへッダファイル名が一致して # 0 (c)-( は ) いると便利て、す。 コンストラクタ : 2 ③ひとつのヘッダファイルにひとつのクラ 前回のモジュール化についての話の続き ハロー ! : 2 て、す。く解答 > にあるように , モジュール スを定義し , メンバ関数をいくつかのフ の実装とインタフェイスについて , C 十十て、 ァイルに分けて定義する コンストラクタ : 2 大規模なクラスにしばしば用いられま ハロー ! : 2 は , 正解て、きたて、しようか ? Table 1 【例題 9 】空欄 ( a ) ~ ( c ) を埋めよ コンストラクタは定義のときに呼び出さ れるのて、 , 16 行目て、オプジェクト 01 につい て , 20 行目て、 02 について呼び出されます。 02 はループの中て、 2 回呼び出されていること に注意してください 以上て、 , クラスの説明に一段落つけるこ 数になります。正しくは , データメンバ ( 引数 , ・・・・・・ ) , となり , 初期値の引数は , ひとっとはかぎ りません。たとえば , class Rect { POint top left ; int width, height ; 【例題 8 】どのように実行されますか ? List タ ク ン 0 ー 1 ワ朝 00 -4 ・′ 0 り「ー -8- 0 リ 0 1 ワ 0 ワ 0 4 ・ LO CD っー 8 0 リ 0 11 ワ編ワ 0 ーー・ 1 よ 1 ー 1 よーー 4 1 ー 1 ー 1 ー 1 ー 1 ー 0 乙 0 乙ワ 0 りし x ( ax ) { VO i d he Ⅱ 0 ( ) { CO u t くく”ノ、ロー ! ・ endl; くく X くく という , Point クラスのオプジェクト top le ft をデータメンバに持っ矩形 ( Rect ) クラスが あるとします。コンストラクタは , . Rect(int x, int y, int w, int h) Rect . : top left (), y) , width (w) , height (h) { } と書けるて、しよう。初期化リストのはじめ が , t 叩 left (), y) となっていますが , これ は , あたかもメンバが , Point top left(), y) ・ というように定義・初期化されることを表 しています。 Nonsense 01 ( 1 ) : 01. he Ⅱ 0 ( ) ; for ( i n t i : 0 : i く 2 : i 十十 ) cout くく ' # ' くく i くく endl; Nonsense 02 ( 2 ) : 02. he Ⅱ 0 ( ) : 確 概念 モジュール インタフェイス 実装 C 言語 C 十十 (a) (b) ファイル 外部リンケージを持つ関数や変数 内部リンケージを持つ関数や変数 (c) 122 C MAGAZINE 1993 11

5. 月刊 C MAGAZINE 1993年11月号

C 十十入門 ります。→またを誌面節約のために , 前者 と初期化することができます 9 でも , コ を使わざるを得ないこともあります。 ンストラクタを定義して使う方法に慣れ ・教えて 2 てください。 初期化につい新 0 言語の構造体では第 : こ」っーさーぎの反省 struct POINT { 今回は意外とわガりやすなったよっに 思います前難しすきて泣いたなら , 、敗その 1 手加減してくれたみたい。あ願い次回 ~ 【例題 2 レで , struct POINT p = { 第。 2 } ; もこの調子 0 ” void PO ⅲい . move to ぐ・・ とするように , →確ガ学校の授業で習っに ところで「うさぎのノート」のところ 。のですオプジェワトの初期化もいこ のイラスあやしけですよね。「エジプ ト調」ど丿ク主ストしたら , ーこんなふう のようにできないの℃しようガ ? 。。 ' 、 } セミコロンガ余分 に。 →全メンバが public なら可能です 9 つラスの定義には最後にセミコ朝ンガ class P0int { 。 にのヒ工ログリフ ( 古代工三プト神聖文 動要でしたメンヾ関数の定義の最後 客 ) は , 解読の結果どうやら「 P 丁〇 LM IS pu blic; ~ にはいらないですね 9 えてみれま、メ ( プトレマイオス ) 」と読むらしいです。ロ ンミ関数といっても , →やはり関数ですガ ゼッタストンに書いてあります。絵の 0 いル、つものとあり考えればいいのでレ = = 、 = 第うは , エジプトの女神ハトホルでしよ た。ここにセミコロンガあっても別にガ ーとなっていれば一 うガ現在調査中です。 P ゆ号ヨレ 2 敗その 2 ・ : うさぎのノート : 一【例題 / 】の日 ect クラスのコンストラ クタを書いていて ; → Rect : 第 Point() ◇オプジェクト指向 メッセージを受けると行動ヴるオプ 初期イ VI 丿ストは , データメンパのコ いう意味不明のものを書いてしまった。 ンストラワタを呼び出ヴ。 ジェワトを作り , オプジェワト間の協 = これではコンストラクタではなくて : = : ◇オプジェクトの定義 調をデザインヴる考え方。 クラス名オプジェワト名 ; ◇クラスの定義 ただのメンヾ関数になってしまいすね。 ( 例 ) Point 0 ; class ワラス名 { 「コンストラクタはワラス名と同じ関数名 ◇メンバ関数の呼び出し データメンパと になるっていったでしよ」と , 叱られてし オプジェつト . メンパ関数 メンパ関数の宣言 まいました。。 ( 例 ) b.orint(); ・教えて 1 ◇ public, private ※文末のセミコロンを忘れるな ー関数定義の ' { ' の位置な ◇メンバ関数の定義 メンパへのアワセス制御 Point : : : print( ) { 型ワラス名 : : 関数名 { oubli c:O ラスの外ガらアワセス可能 だったり orivate: アワセス不可能 ◇クラスとモジュールの関係 、 Point : 謇 print( ) クラズ ◇コンストラクタの定義 モジュール コンストラワタは , 初期化専用メン 非公開メンパ : 実装 だったりするけ巴どっちガいいの ? パ関数。 公開メンパ : インタフェイス →どっちでもいいです。私は , おもに後 0 ワラス名 : : ワラス名 : 初期化リスト { ー。者を用いていますが , インラインメンバ 関数のときには , 前者を用いることもあ うさぎの 失敗 ( あしまい ) Case Study A 級 B 型 C 十 + 入門 125

6. 月刊 C MAGAZINE 1993年11月号

特集℃言語とオプジェクト指向 先頭に記された virtual というキーワード の意味は , 継承の話をするまて、説明を延期 する。さしあたって無視しておいてほしい cout は , C 十十の iostream. h というへッダが 提供しているオプジェクト指向入出力に含 まれる標準出力ストリームて、あり , stdio. h て、の stdout に対応する。そこに「挿入演算子 (insertion operator) 」て、ある、、く < 〃を使 って文字列や数値を送ると , その型に応じ て出力してくれる。これは C 十十特有の演算 子関数 (operator function) と , オーバロー ド ( 多義 , overload) という機能 , さらには フレンド機能の組み合わせによって実現さ れている ( 本稿は C 十十の解説て、はないの て、 , いずれも今回は詳しく説明しないし , 深入りもしない ) 。 なお , C 十十の関数宣言て、は , 空のパラメ ータタイプリスト、、 ( ) 〃は , 、、 (void) クと同 じ意味を表す点が C と違っている。ただし , 、、 ( v 。 id ) 〃と書いてもよい ( 古い C 十十て、は許 されなかったが , 現在の C 十十て、は , この記 法が ANSIC から逆輸入されている ) 。 ちなみに , 動的データ型を有するオプジ ェクト指向言語て、あれば , オーバロードに 頼ることなく同様の機能を実現て、きる。た とえば , SmalItalk にはオーバロードはな い。しかし , C 十十と類似の構文て、 , たとえ ば Transcript という情報表示用のウインド ウに出力を行うことがて、きる。 同様に , 「 a. move ( 15 , 27 ) : 」は a に move というメッセージを送る。これは , 相対移 動用のメッセージて、ある。今度のメッセー ジには引数がある。移動すべき x と y の差分 値て、ある。これは次のメソッドを起動する ことになる。意味は明白だろう。 / / 座標移動用メソッド virtual void move(int dx, int dy){ x 十 = dx ・ その後 , 再び show を送って移動後の位置 を表示させ , さらにもう一度移動して , 一 たび show を送って最終位置を表示させて m によるメンバ関数の ミュレーション ain は終了する。 義されている Point クラスを , 素直に C て、表 もの ) を実現することがて、きる。 List 1 て、定 ば , 一応メンバ関数 ( あるいは , それらしき こうすれ を格納しようというわけて、ある。 のを格納する代わりに , 関数へのポインタ 法が考えられる。メンバ変数に関数そのも ラスて、ある sturct のメンノヾに加えるという手 えるならば , 「関数へのポインタ」を疑似ク なるべく C 十十のスタイルに似せることを考 オプジェクトへのメッセージの渡し方を , メンバ関数の起動の仕方 , いい換えれば , メンバ変数」なるものは使えない だけて、ある。したがって , 「関数を格納する ンタの変数を宣言したりすることがて、きる ( アドレス ) を取り出したり , 関数へのポイ の操作て、ある。すなわち関数へのポインタ い。唯一許されるのは , 関数へのポインタ としたりといった操作をすることもて、きな それを引数として渡したり , 関数の戻り値 は宣言て、きないし , 関数そのものに関して , や , そのような変数の配列などというもの い。すなわち関数そのものを値とする変数 C て、は , 関数は first class object て、はな いいのだろうか。 メンバ関数て、ある。これはどう表現すれば おいてもそのまま宣言すればよい。問題は スに含まれるメンバ変数は , 疑似クラスに 択肢は struct しかないだろう。 C 十十のクラ ても , C て、の疑似クラスを実現するための選 が , C 十十の class と struct の類似関係からみ を「疑似クラス」と呼ぶことにしよう ) 。だ ければならない ( 以下 , C て、実現したクラス class 定義をどのように実現するかを考えな するとどうなるかを考えてみよう。まず , 以上に説明した List 1 の働きが , C て、実現 現すると次のようになるだろう。 typedef struct POint POint ・ struct Point { void (*show) (P0int * self) ・ void ( * move) (P0int * self, int dx, int (y) ・ この考え方に基づいて , て、きるかぎり Li st 1 を忠実に C に翻訳したのが List 2 て、あ る。なお , 上の struct 定義て、 , 一度 typedef しているのは , いちいち structPoint と書か ずに Point だけて、済ませたいからて、あって , 本質的な問題て、はない ( C 十十て、はこのよう な操作は不要て、あったことを思い出された い ) 。なお , C て、は typdef 名とタグ名は名前 空間が異なるのて、 , まったく同一の識別子 を両方に用いることは許される ( ただし , st ruct/union/enum のタグ名の名前空間は全 部て、ひとつだけ ) 。 メンバ関数はここて、は show と move のふた つだけて、ある。コンストラクタ ( に相当する 関数 ) はメンバ関数として実現するわけには いかない。メンバ関数は , 「関数へのポイン タ」という型を有するメンバ変数を介して呼 び出すわけだが , コンストラクタもメンバ 関数として実現するならば , コンストラク タ自身もまた , 「関数へのポインタ」型のメ ンバ変数によってポイントされていなけれ ばならない。 ところが , そもそもコンストラクタ ( に相 当する関数 ) は , オプジェクトの初期化を行 うために呼び出すわけて、 , その時点て、のオ プジェクトの値は不定て、ある。したがって , 「関数へのポインタ」型のメンバを介して呼 び出すことなどて、きない。そこて、 , List 2 て、 は Point init という名の ( 普通の ) 関数を定義 これにコンストラクタに相当する機能 し , を分担させることにした。 また , C 十十て、はコンストラクタは変数宣 言時など , 一般にオプジェクトが生成され た時点て、自動的に呼び出された。だが , C に はそのような機能は備えられていないのて、 , 明示的にプログラマが呼び出す必要がある。 そこて , List 2 て、は main の冒頭て次のよう にして変数 a および b の宣言と , その初期化 特集 C 言語とオプジェクト指向 41 int X ・

7. 月刊 C MAGAZINE 1993年11月号

( 11 , 22 ) いったい何について move to ( ) をするのて、 ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡ・く解答 > ・ⅢⅡⅢⅡⅢⅢⅢⅢⅢⅢⅢ クラスを定義したのて、 , オプジェクトを public は , 以後に宣言されたメンバを公開 すか ? 生成して使ってみることにします。オプジ し , クラスの外からでもアクセスできる move to( ) の定義て、 , ェクトの生成は , クラス名を用いて , ようにする X ax , POint p , private は , その逆で , クラスの外からの とします。組み込みのデータ型 int て、 , アクセスを禁止する クラスの定義の中て、は , 次のような 3 種の とするのと書き方は同じて、す。 Point 型が用 p. move tO ( 1 , 2 ) ; ラベルづけを行い , 公開の度合いを限定す 意されたと考えてもよいて、しよう。 と呼び出して , はじめて , move to ( ) は p の ることがて、きます。 これて、 , オプジェクト p が生成されまし 中にある x , y を操作て、きるのて、すね。 public : た。いい方を換えると , オプジェクト p が「定 クラスの外からアクセスできる 義」されました。本連載の 1 回目の内容を思 ( 注 1)Point : : move to ( 1 , 2 ) のような書き protected . い出していただくと , 定義は「名前と型と内 方は存在します。しかし , オプジェクトを そのクラスと派生クラスからアクセ 容」を示すことて、したね。 p の内容とは , デ 特定せずに呼び出すのて、はなく , 派生クラ スできる ータメンバ x , y のことて、す。クラス定義の スのメンノヾ関数の中て、 , べースクラスのメ 中て、は宣言されるにとどまったデータメン ンバ関数を陽に呼び出すために用いられま そのクラスの中だけでしかアクセス バは , オプジェクトの定義と同時に , やっ す。クラスの継承のときに説明します。 できない と定義されるのて、す。 ( 注 2 ) 特定のオプジェクトを必要としないメ これらの性質は , ラベルが現れた場所て、 オプジェクトの生成時には , 暗黙のうち ンバ関数を定義することも可能て、す。これ 切り換わり , 次のラベルが現れるまて、有効 にコンストラクタが呼び出されます。つま を static メンバ関数といいます。ここて、説明 て、す。最初のラベルが現れるまて、の部分は り , Point : : Point ( ) が実行され , 先に定 するとややこしくなるのて、 , 【例題 10 】て、簡 ( デフォルトは ) , private て、す。ラベルは繰 単に触れるにとどめます。 義したとおり p の中の x, y が 0 に初期化され り返し現れることがて、きます (Fig. 1 ) 。 ます。 private と protected の違いは , クラスの継 次のヘ メンバ関数の呼び出しは , メンバアクセ 承のときに説明することにして , こて、は ス演算子の ' . ' や ' ー > ' によって行います。 もっと基本的なことを理解しましよう。 構造体と同様て、 , ' . ' は実体 , ' ー > ' はポイン クラスを定義し , コンストラクタをはじ public は , クラスの外に無条件て、アクセス めとするメンバ関数を定義し , オプジェク タのときに用います。 p は実体だから , Poi 権を与えます。 List 1 て、 , print( ) や move : print ( ) を呼び出すには , トを生成して使いました。これが C 十十のプ to ( ) などのメンバ関数は , public の後に宣 nt : ログラミング方法て、す。次にもう一歩進ん p. print( ) ; 言されていることを確認してください だ書き方を覚えましよう。 とします。同様に , move to ( ) や r これにより , move ・メンバのアクセス制御 to ( ) も , 実装とインタフェイスを分け , 安全性 p. move t0 ( 1 , 2 ) ; Fig. 1 メンノヾアクセス制御 を向上する p. r move t0 ( 10 , 2 の ; というように呼び出します。座標値の計算 ・インラインメンバ関数 実行効率をあげる はメンバ関数に任せてしまいます。 Point ク ラスは自律しているのて、すから。 ・コンストラクタの多重定義 こて、とくに注意してほしいのは , クラ 複数の初期化法を提供する スのメンバ関数は , オプジェクトがなけれ メン ' のクセス制御 ば呼び出せない , ということて、す。 Point : : move to ( 1 , 2 ) ; などと , オプジェクトを特定せずに呼び出 日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ【リ題 5 】ⅢⅢⅢⅢⅢⅢ日ⅢⅢⅢⅢⅢ すことはて、きません。だってそうて、しよう ? public と private の意味は何ですか ? と現れた x , y は何を示すというのて、しよう private cla ss xxxx { private ← -- public : ← public private . protected private . private ←ー protected private 120 C MAGAZINE 1993 11

8. 月刊 C MAGAZINE 1993年11月号

C 十十 門 このように , メンバ関数の定義は " クラ を返しながら関数を抜け出ることはて、きま スに対して " 行うものなのて、す。当たり前 せん。 のように聞こえるかもしれません。しかし , 初期化リストは , 次の形式て、書きます。 オプジェクト指向の考え方に慣れていない データメンバ ( 初期値 ) , と , つい、、プログラム全体に対して " 関数 こて、は , x , y をそれぞれ 0 て、初期化してい ます。なお , 【例題 7 】て、述べますが , 初期 を定義しようとしてしまいます omain( ) 関 数からのトップダウンの処理の流れの中に 値は , ひとっとはかぎりません。 関数を位置づけてしまいがちて、す。もちろ コンストラクタの関数本体には , データ メンバ以外の初期化作業を書きます。たと ん , 最終目標て、あるプログラム全体へ , 常 に意識を払っておく必要はありますが , そ えば , Window クラスなら , 必要な API コー れよりも , クラスという小さな枠を意識し ルをしてウインドウを表示するとか , 画像 て , クラスの中に関数を位置づける心構え クラスなら , 画像保管用メモリをシステム が大事て、す。 から取得するとかが考えられます。 Point ク 小さい枠のほうが考えやすいし , 処理が ラスて、は , データメンバの初期化が済んだ クラスの中て、完結していれば , クラスの自 ら , もうやることがなくなってしまったの て、 , { } となります 律性が高くなり , 異なるプログラムにおい ても動作する可能性 , すなわち , 再利用性 が高まるからて、す。 コン ークタ の助詞「の」のように捉えておけばいいて、し ⅢⅡⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ【リ題 3 】ⅢⅢⅢⅢ日ⅢⅢⅢⅢⅢⅢⅢ メンバの名前はクラスにローカルなのて、 , 座標を原点 ( 0 , 0 ) に初期化するようにコ ほかのクラスのメンバ名とは無関係て、す。 ンストラクタを定義しなさい。 : get x ( ) と Window : たとえば , Point : lllllllllllllllllllllllllllllllllll < 解答 > ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡ : get x ( ) というように , 異なるクラスて、メ : Point( ) ンバ名が重複していてもかまいません。つ Point : まり , メンバ関数名やデータメンバ名は , コンストラクタは , 生成関数とも呼ばれ , クラスにローカルなスコープを持っている クラスの初期化を行う特別なメンバ関数て、 わけて、す。これをクラススコープといいま す。次のような書き方をします。 す。第 1 回のノートに次のように補充してく ・関数名はクラス名と同じ ださい ・関数の返り値は存在しない クラススコープ クラス固有の名前の有効範囲 ・関数の返り値の型はない ( void 型ですらない ) メンバ関数の中て、は , データメンバを扱 ・データメンバの初期化リストを持っこ うことがて、きます 0List 2 て、は , 関数の中に とができる 現れる変数 x , y は , データメンバの x , y を Point クラスのコンストラクタは , Point 示しています。 という名前になります。型名を書いてはい また , メンバ関数の中から , ほかのメン けません。クラスの定義のところて、も , コ バ関数を呼び出すことがて、きます。 r move to( ) は , 内部て、 move t 。 ( ) を呼び出してい ンストラクタの宣言には型名がないことを ます。この move to ( ) は , とりもなおさず 確認してください (List 1 の 5 行目 ) 。返り値 がないのて、 , returnxxx: というように値 Point: :move to ( ) を示しています。 【例題 2 】の解答 . メンバ関数の定義 LiSt 1 : void Point::move_t0(int ax, int ay) 3 : 4 : 6 : int dy) void Point::r_move-to(int dx, 7 : dy); move_to(x 9 : 十 dx, y 十 10 : } :print() 1 2 : v 0 i d P 0 i n t . 17 . 18 : i n t Po i n t : : ge t_ 20 : 22 : i n t PO i n t : : ge t _y ( ) 23 : 24 : 25 : 26 : } ' (' くく X くく くく end に C ou t くく くく y くく return X : return Y ; オプな トの定義 ( 生成 ) 、 ⅢⅢⅢⅢⅢⅢⅡⅢⅢⅢⅢⅢ【リ題 4 】ⅢⅢⅢⅢ日ⅢⅢⅢⅢⅢⅢⅢ Point クラスを使って , 点 p を次のように 操作するプログラムを作りなさい。 ( 1) 最初 ( 0 , 0 ) になっているはすだから , 確認のために座標値を表示する ( 2 ) ( 0 , 0 ) から ( 1 , 2 ) に移動して , 座標 値を表示する ( 3 ) ( 1 , 2 ) から ( 十 10 , 十 20 ) すっ相対移動 して ( 11 , 22 ) にし , 座標値を表示す る ⅢⅢⅢⅢⅱⅢⅢⅢⅢⅢⅢⅢ < 解答 > lllllllllllllllllllllllllllllllllll main( ) POint p , p. print ( ) : p. move to ( 1 , 2 ) : p. print ( ) : p. r_move to ( 1 0 , 20 ) : p. print( ) : 【参考】実行結果 ( 0 , 0 ) Case Study A 級 B 型 C + + 入門 119

9. 月刊 C MAGAZINE 1993年11月号

プロクラミング道場 初期化 柴田望洋 第⑩回 Dr. 望洋の ー初期化に関する質間が私のもとに舞い込ん一と混同して , 『外部変数』や『内部変数』なー : できた。初期化に関して , きちんと理解す一 どといっていては , C 言語の本質を理解する るには , まず記憶寿命などの概念をマスター ことは永遠にできないだろう。 , せねばならない。 BASIC などのほかの言語 : 普通のオプジェクトを初期化 List , ー、、めに 最近 , 匿名希望の読者の方から以下のよ うな質問を受けた。 友人の作ったプログラムを参考にしなが ら , プログラムを自作しています。友人 のプログラムの中には , 初期化しないに も関わらず , きちんとゼロになっている 変数もありますし , 明示的に初期化しな いといけない変数もあるようです。これ らの違いがわかりません。よろしくご教 授お願いします。 「変数の初期化忘れ」に起因するバグは , 比較的起こりやすいし ( というよりもプログ ラマが起こしやすいし ) , しかもその結果は サンタンたる場合が多い。今回は初期化に ついて考えていこう。 ネ期化子と初期値 変数の「初期化」と聞いてまず思い出すの が , 次のような宣言てあろう。 int X 細かくすれば , { 1 , 3 , 7 } 中の 1 と 3 と 7 も , この宣言によって , x は , 5 という値て、初 期化される。初期化を伴った配列の宣言例 それぞれが初期化子て、あるといえる。 て、 , x, a [ 0 ] , a [ 1 ] , a [ 2 ] の初期値 ( in も示そう。 int a [ ] = { 1 , 3 , 7 } ; itial value) は , それぞれ 5 , 1 , 3 , 7 とな これらの例において , 5 や { 1 , 3 , 7 } を初 る。 期化子 (initializer) と呼ぶ。もちろん視点を 1 : # i nc lude 2 : 3 : int main(void) 5 : 6 : 7 : 8 : く stdio. h> ニ { 4. 5 } : i nt X printf()x ニ %dYn ” return ( 0 ) : 0 0 ー Co ⅲ m れ 1 C 十十の初期化 wC 十十では , Sim 田 a を参考に初期化の構文 が拡張されている。 C 言語での int j は , 次のようにも記述できる 0 int i ⑤ ! これは , クラスに対する変換コンストラ クタ ( ひとつの引数を渡すことによって呼び 出すことのできるコンストラクタ ) と同じ構 文である。 たとえば : 次のような com が ex クラスが与 えられたとしよう。 このときい次のふたつの宣言は等価であ complex x complex x ( 5.0 ) , 「なるほど , 初期化子というのは難しい 言葉だが , これは初期値のことなんだ と短絡的に理解されると困るのだ。 次の例を考えよう。 int X ところが , D 「 . 望洋のプログラミング道場 145

10. 月刊 C MAGAZINE 1993年11月号

ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅱⅢⅢ < 解答 > Ⅲ日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ あります。 れます。 以下て、は , 題材として「移動と表示がて、き 1 - ( い ) 2- ( へ ) 3- ( は ) 4- ( ほ ) クラスの定義法をごく簡潔にまとめてお る 2 次元の点」という Point クラスを定義しま 5- ( と ) 5 ~ 10- ( に ) 1 ト ( ろ ) きます。 す。点に関する機能は Point クラスに任せて class クラス名 { これがクラスの定義て、す。はじめに外枠 しまいます。たとえば移動については , 「外 から見ていきましよう。 1 行目のキーワード データメンバや 部から点を移動してやる」のて、はなくて「点 class て、始まる宣言行と , 11 行目の終了行 メンバ関数の宣 に移動してもらう」のて、す。もちろん , 「移 は , とりあえず問題ないて、しよう。あると 動しろ」というきっかけは , 外部から与えら すれば , 「最後のセミコロンを忘れるな」と すて、にお気づきかと思いますが , クラス れなければなりませんが ( 受動性 ) , 移動に いう注意くらいて、す。 2 , 4 行目の private, の中身は , データメンバとメンバ関数の集 関する処理は , すべて Point クラスが行いま public というのは , C 十十のキーワードて、 , まりて、す。 C 言語の発想て、いえば , 構造体と 【例題 5 】て、説明します。 す ( 自律性 ) 。 専用操作関数の集まりて、す。 はじめに , クラスの入門第 1 段階として , 以上の計 4 行はクラス定義のための道具立 変数と関数がまとまって , ある特定の機 ・クラスの定義 てて、す。残りの部分がクラスの中身に相当 能をします。したがって , クラスはプログ ・メンバ関数の定義 ラムのモジュールを規定したものだ , とも します。クラスの中身はメンバと呼ばれま ・オプジェクトの定義と使用 す。 いえます。 について説明します。これて、 , 曲がりなり 3 行目は , データメンバの宣言て、す。オプ 最初に述べた「特定の機能を持った小さな にもクラスを使うことがて、きるて、しよう。 ジェクトの属性値を格納するものて、す。 コンヒ。ュータ」は , こうしてデザインされる こて、は , 座標値を入れる変数 x , y が宣言さ のて、す。 からできている ? クラ れています。クラスの中て、は , 変数は宣言 メン関数の定義 されるだけて、定義を伴いません。データと ⅢⅢⅢⅱⅢⅢⅢⅢⅢⅢⅢⅢ【例」題 1 】 lllllllllllllllllllllllllllllllllll なるオプジェクトの一覧を示すだけて、す。 List 1 は , 2 次元の点を表す Point クラスの 構造体 (struct) のメンバを示すのと同じて、 Ⅲ日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ【例」題 2 】ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ 定義です。各行にあてはまる説明を , 次 す。て、すから , こて、初期値を与えること Point クラスのメンバ関数を , それそれ次 の ( い ) ~ ( と ) から選びなさい。 はて、きません。初期値の与え方は , 【例題 3 】 の働きをするように定義しなさい。 ( い ) Point クラス定義開始 て、説明します。 move to 座標の移動 ( ろ ) 定義終了 5 ~ 10 行目は , メンバ関数の宣言て、す。メ r move to : 座標の相対移動 ( は ) テータメンバの宣言 ンバ関数は , このクラスにローカルに定義 . 座標値の表示 print される関数て、 , オプジェクトに対する操作 ( に ) メンバ関数の宣 = : x 座標を返す get x を示します。オプジェクト指向の用語て、は ( ほ ) public メンバ開始 : y 座標を返す get_y こて、は , 座標値の ( へ ) private メンバ開始 メソッドといいます。 ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢⅡく解答 > ⅢⅢⅢⅢ日ⅢⅢⅢⅢⅢⅢⅢ 表示 (print) や移動 (move (o) などの操作を ( と ) Point クラスのコンストラクタ List 2 のとおり 宣言しています。 メンバ関数の定義は , クラスの定義の外 【例題 1 】 2 次元の点クラスの定義 もちろん , 宣言だけて、は動作しません。 て、は , クラスは , メンバ関数の定義を伴ってはじ 型クラス名 . : メンバ関数名 ( 引数 , めて完成します。メンバ関数の定義の方法 は【例題 2 】て、説明します。また , 【例題 6 】 て、触れますが , インライン展開するメンバ という形式て、書きます。普通の関数定義と 関数は , クラスの中に定義を書くこともて、 比べると , 「クラス名 : : 」という部分が付加 きます。 5 行目の Point というメンバ関数は , とく されて , メンバ関数がクラス固有に定義さ れることを示しています。 : : はスコープ演 にコンストラクタと呼び , オプジェクトの 算子といいますが , 難しく考えず , 日本語 初期化専用て、す。【例題 3 】と【例題 7 】て、触 List 1 : cl ass PO i n t { 2 : private: 3 : i n t X, y : 4 : public: Po i n t ( ) : 5 : VOid move-to(int ax, int ay); 6 : void r-move-to(int dx, int dy); 7 : VO i d pr i n t ( ) : 8 : int get-x(); 9 : int get-y(); 118 C MAGAZINE 1993 11