Comments
Description
Transcript
第 9 章 クラスの基本
第9章 クラスの基本 オブジェクト指向プログラミングにおいて最も基礎的で重要な概念が、クラス です。本章では、銀行口座クラスと自動車クラスを例に、クラスに関する基本を 学習します。 296 9-1 クラスの考え方 本節では、オブジェクト指向の基礎であるクラスの考え方について学習します。 ■ データの扱い List 9-1 示 。変数 クラスの基本 9 、足立君 値 設定 仲田君 、表示 銀行口座 単純 扱 。 Chap09/list0901.cpp List 9-1 // 銀行口座 実行結果 #include <string> #include <iostream> using namespace std; ■足立君の口座:"足立幸一" (12345678) 800円 ■仲田君の口座:"仲田真二" (87654321) 300円 int main() { string adachi_account_name = "足立幸一"; string adachi_account_no = "12345678"; long adachi_account_balance = 1000; // 足立君の口座名義 // 〃 の口座番号 // 〃 の預金額 string nakata_account_name = "仲田真二"; string nakata_account_no = "87654321"; long nakata_account_balance = 200; // 仲田君の口座名義 // 〃 の口座番号 // 〃 の預金額 adachi_account_balance -= 200; nakata_account_balance += 100; // 足立君が200円おろす // 仲田君が100円預ける cout << "■足立君の口座:\"" << adachi_account_name << "\" (" << adachi_account_no << ") " << adachi_account_balance << "円\n"; cout << "■仲田君の口座:\"" << nakata_account_name << "\" (" << nakata_account_no << ") " << nakata_account_balance << "円\n"; return 0; } 2 人分 口座 関 表 6個 変数 利用 。 、変数 adachi_account_name 口座名義 、adachi_account_no adachi_account_balance 預金額 。 名前 関係 変数 足立君 始 adachi_ 、変数名 、nakata_account_no 4 4 仲田君 4 4 4 4 口座番号 4 測 推測 4 4 関 4 4 。 4 口座名義 可能 。 表 、 。 4 足立 君 、adachi_account_name 、変数間 不可能 、100%確定 口座名義・口座番号・預金額 銀行口座 銀行口座 関 口座番号 関係 4 推 変数名 。 変数 宣言・定義 関係 中 、 現 。 一 297 、 命名法 続 、 莫大 変数名 溢 。 ■ オブジェクトとクラス 私 作 界 (変数) 投影 ▼ 世 。 、Fig.9-1 座番号・預金額 (物) 、 、現実世界 示 個別 変数 、一 口座 関 投影 口座名義・口 。 この図は一般化して表したものです。足立君の口座・仲田君の口座に対して、三つのデータが別々 の変数として投影されます。 口座 1側面 、複数 側面 着目 扱 投影 、図 示 、口座名義・口座番号・ 9-1 、 クラスの考え方 預金額 。 。 投影 行 クラス(class) 考 方 基本 。 口座に関するデータを個別に投影 口座名義 name 口座番号 no 預金額 balance 口座に関するデータをひとまとめにして投影(クラス) 口座名義 name no balance 口座番号 預金額 Fig.9-1 オブジェクトの投影とクラス 扱 世界 投影 問題 種類 範囲 異 、 『 。』 、 方針 、 、現実 世界 。 』『本来 自然 素直 、 。 * ) 用 示 List 9-1 。 書 直 。 List 9-2( 次 298 Chap09/list0902.cpp List 9-2 // 銀行口座クラス(第1版) 実行結果 #include <string> #include <iostream> using namespace std; クラス定義 class Account { public: string name; string no; long balance; }; // 口座名義 // 口座番号 // 預金額 int main() { Account adachi; Account nakata; クラスの基本 9 ■足立君の口座:"足立幸一" (12345678) 800円 ■仲田君の口座:"仲田真二" (87654321) 300円 // 足立君の口座 // 仲田君の口座 adachi.name = "足立幸一"; adachi.no = "12345678"; adachi.balance = 1000; // 足立君の口座名義 // 〃 の口座番号 // 〃 の預金額 nakata.name = "仲田真二"; nakata.no = "87654321"; nakata.balance = 200; // 仲田君の口座名義 // 〃 の口座番号 // 〃 の預金額 adachi.balance -= 200; nakata.balance += 100; // 足立君が200円おろす // 仲田君が100円預ける cout << "■足立君の口座:\"" << adachi.name << "\" (" << adachi.no << ") " << adachi.balance << "円\n"; cout << "■仲田君の口座:\"" << nakata.name << "\" (" << nakata.no << ") " << nakata.balance << "円\n"; return 0; } ■ クラス定義 口座名義・口座番号・預金額 宣言 先頭 クラス定義(class definition) 以下 定義 class Account { 。関数 { } 間 宣言 違 、末尾 置 部 }; 続 =要素 。 。 開始 、 必要 定義 。 データメンバ(data member) 宣言 三 ・口座名義 呼 網 構成 。 Account (Fig.9-2) 。 表 string 型 name ・口座番号 表 string 型 no ・預金額 表 long 型 balance 配列 同一型 要素 組 作 型 公開 合 作 型 。任意 型 要素 組 合 。 先立 指示 public: 。 、 以降 宣言 、 外部 対 、 299 クラスAccountはname, no, balanceがセットになった型ですよ! class Account { public: string name; string no; long balance; }; name no balance Account // 口座名義 // 口座番号 // 預金額 ▼ Fig.9-2 クラス定義 もし公開しなければ、クラスの外(たとえば main 関数)から、そのデータメンバの名前や型はお ろか、その存在を知ることすらできなくなります(p.302) 。 ■ クラス型のオブジェクト 定義 《型》 宣言 。 、具体的 Account 型 int 型 nakata 型名 、adachi 定義 焼 定義 。 分 。Account 。 // Account型のadachiの宣言・定義 // Account型のnakataの宣言・定義 // int 型のx の宣言・定義 焼 、 並 名前 Account adachi; Account nakata; int x; 型名 変数名; 、 =変数 宣言・定義 以下 実体 「 作 」 本物 。上 宣言・定義 焼 (Fig.9-3)。 タコ焼きのカタ カタから作られたタコ焼き Account adachi; Account nakata; クラス(枠組み) name no balance 型名 オブジェクト(実体) 変数名 name no balance name no balance adachi nakata Account Fig.9-3 クラスとオブジェクト int 型 double 型 、整数 実数 型 、C++ 型(built-in type) 対 呼 型 表現 最初 都合 用意 型 。 、組込み 。 、銀行口座 自分 作成 )。 数値 言語 型 口座番号 預金額 ( 、誰 Account 作 、ユーザ定義型(user-defined type) 利用 呼 。 、 9-1 クラスの考え方 300 ■ メンバのアクセス 足立君 仲田君 各 型 呼 値 代入 表示 内 。 利用 . 演算子(.operator) 、ドット演算子 (Table 9-1) 。 ■Table 9-1 メンバアクセス演算子(ドット演算子) x のメンバ y をアクセスする。 ▼ x.y ドットは点という意味です。“ドット演算子” は俗称です。 クラスの基本 9 、足立君 口座 式 次 。 // 足立君の口座名義 // 〃 の口座番号 // 〃 の預金額 adachi.name adachi.no adachi.balance 仲田君 各 口座 表 nakata 足立君の口座名義 同様 adachi.name adachi.no adachi.balance adachi 足立君の口座番号 足立君の預金額 (Fig.9-4)。 nakata.name nakata.no nakata.balance nakata 仲田君の口座名義 仲田君の口座番号 仲田君の預金額 Fig.9-4 データメンバのアクセス ■ 問題点 導入 確実 初期化 、 対 無保証 4 4 問題 残 。 4 プログラムでは、口座オブジェクトのメンバが初期化されていません。オブジェクト 4 4 を作った後に値を代入しているだけです。 値を設定するかどうかがプログラマに委ねられている状態となっていますので、初期 化を忘れた場合は、思いもよらぬ結果が生じる危険性があります。 すべてのオブジェクトを初期化すべきかどうか という一般論はさておき、初期化 すべきオブジェクトは、初期化を強制したほうがよいでしょう。 保護 対 無保証 足立君の預金額である adachi.balance は、誰もが自由に扱うことができます。この ことを現実の世界に置きかえると、足立君でなくても(通帳や印鑑がなくても) 、自由 に足立君の口座からお金をおろしたりすることができるということです。 口座番号を公開することはあっても、預金額を操作できるような状態で公開すると いったことは、現実の世界ではあり得ません。 301 掲 問題点 解決 改良 定義・利用 List 9-3 。 。 Chap09/list0903.cpp List 9-3 // 銀行口座クラス(第2版) #include <string> #include <iostream> using namespace std; class Account { private: string name; string no; long balance; 実行結果 ■足立君の口座:"足立幸一" (12345678) 800円 ■仲田君の口座:"仲田真二" (87654321) 300円 // 口座名義 // 口座番号 // 預金額 // コンストラクタ string GetName() { return name; } // 口座名義を調べる string GetNo() { return no; } // 口座番号を調べる long CheckBalance() { return balance; } // 預金額を調べる void Deposit(long k) { balance += k; } // 預ける void Withdraw(long k) { balance -= k; } // おろす }; int main() { Account adachi("足立幸一", "12345678", 1000); Account nakata("仲田真二", "87654321", 200); adachi.Withdraw(200); nakata.Deposit(100); // 足立君の口座 // 仲田君の口座 // 足立君が200円おろす // 仲田君が100円預ける cout << "■足立君の口座:\"" << adachi.GetName() << "\" (" << adachi.GetNo() << ") " << adachi.CheckBalance() << "円\n"; cout << "■仲田君の口座:\"" << nakata.GetName() << "\" (" << nakata.GetNo() << ") " << nakata.CheckBalance() << "円\n"; } return 0; 9-1 クラスの考え方 public: Account(string n, string num, long z) { name = n; // 口座名義 no = num; // 口座番号 balance = z; // 預金額 } 302 ■ 非公開メンバと公開メンバ 公 第1版 開 class Account { private: string name; // 口座名義 string no; // 口座番号 long balance; // 預金額 public: Account(string n, string num, { /* … */ } string GetName() { /* … string GetNo() { /* … long CheckBalance() { /* … void Deposit(long k) { /* … void Withdraw(long k) { /* … }; 。今回 第2版 非公開 。非公開 指示 。 private: 非公開 存在 、 隠 外部 対 。 表 Account 。 Fig.9-5 9 Account クラスの基本 非公開部 Account name GetName no GetNo balance CheckBalance 非公開 long z) 公 開 */ */ */ */ */ } } } } } 初期化用の関数 コンストラクタ Account(string n, string num, long z) { name = n; // 口座名義 no = num; // 口座番号 balance = z; // 預金額 } メンバ関数 Deposit データの流れ Withdraw クラスの外部から呼び出される関数 Fig.9-5 クラスAccount 部 枠内 関数 入 name, no, balance 、非公開 以下 、 adachi.name = "柴田望洋"; adachi.no = "99999999"; cout << adachi.balance; 非公開 向上 外部 private: 不正 守 。 。 、本 。 、public: 何度 、 private: 削除 private: 構 。 、 main main 関数 。 // 足立君の口座名義を書きかえる // 足立君の口座番号を書きかえる // 足立君の預金額を表示 データ隠蔽(data hiding) 非公開 外部 、 保守性 。 隠 、public: 。 時 保護性・隠蔽性 、 期待 非公開 、 定義中 (Fig.9-6) 。 class X { int a; public: int b; int c; private: int d; }; 非公開 公開 非公開 Fig.9-6 アクセス指定 303 ■ コンストラクタ 名 同 名前 型 切 関数(Fig.9-5 生成時 初期化 ) コンストラクタ(constructor) 呼 出 宣言 、main 関数 Account adachi("足立幸一", "12345678", 1000); Account nakata("仲田真二", "87654321", 200); 流 呼 出 値 網 各 部 着目 nakata 生成 、仮引数 n, num, z 受 adachi 。 。 name, no, balance 対 呼 知 出 。 、 、 adachi.name nakata.name 何 、自分自身 呼 呼 出 中 出 中 name name 。 ▼ コンストラクタを呼び出すクラスオブジェクトの宣言は、int 型の整数 x を 5 で初期化する宣言 (p.233)と同じ形であることに注意しましょう。 int x(5); 、 宣言 // int x = 5;と同じ 以下 書 。 Account adachi; Account adachi("足立幸一"); 不完全 、 。必 初期化 行 比 重要 // エラー:引数がない // エラー:引数が不足 、 問題 不正 初期化 手間 発生 過 防止 示 、確実 初期化 行 。 クラス型を宣言するときは、必ずコンストラクタを用意し、オブジェクトを確 実に初期化せよ。 値 ▼ 、 返却 。 すなわち、コンストラクタに返却値型を与えることはできません(void とすることもできません) 。 ■ メンバ関数 ▼ 権 含 、 内部 存在 関数 メンバ関数(member function) 非公開 呼 特 (Fig.9-5 ) 。 コンストラクタは、オブジェクト生成時に呼び出される特殊なメンバ関数です。 9-1 クラスの考え方 。 // 足立君の口座 // 仲田君の口座 宣言文 通過 、 取 適 役目 、 。 Account 型 際 。 304 、 Account ・ GetName 以外 五 関数 。 string 型 返 。 ・ GetNo :口座番号 string 型 返 。 ・ CheckBalance :預金額 long 型 返 。 ・ Deposit : 金 預 (預金額 増 )。 ・ Withdraw : 金 (預金額 減 )。 者 関数 内部 ▼ 数 、公開 内輪 存在 。 、 非公開 関 。 たとえばメンバ関数 GetName は、非公開のデータメンバである name の値を調べて返却します。 関数 用 。以下 例 クラスの基本 9 :口座名義 呼出 、Table 9-1(p.300) 示 外部 利 // 足立君の預金額を調べる // 足立君の口座から200円おろす // 仲田君の口座に100円預ける 直接 口座番号 預金額 間接的 関数 、 。 、次 疑問 値 演算子 . 。 adachi.CheckBalance() adachi.Withdraw(200) nakata.Deposit(100) 通 示 設定 上 。 調 、 、実行効率 低下 疑問 関数(p.206) 心配 関数 呼 出 ? 無用 。 定義 。 中 埋 込 関数 、 x = adachi.CheckBalance(); // 預金額を返却するメンバ関数を呼び出す 、次 示 同等 x = adachi.balance; ▼ 、必 置 上 。 // 実質的なコード 展開 限 (p.207) 。 以下のように、生成済みのオブジェクトに対してコンストラクタを呼び出すことは (こ の点で、コンストラクタは普通のメンバ関数とは異なります) 。 adachi.adachi("足立幸一", "12345678", 5000); // エラー ■ メソッドとメッセージ 指向 。 、 世界 関数 呼 出 送 、 、次 。 関数 表現 メソッド(method) 。 呼 305 、adachi.CheckBalance() 。 』 結果、 意志決定 、 、 『○○円 adachi 『預金額 教 (Fig.9-7)。 「預金額 返却 adachi 行 対 adachi 送 。 』 。 」 返答処理 行 能動的 。 Account name GetName no GetNo balance CheckBalance adachi.CheckBalance() 預金額を教えてください。 Deposit 非公開である預金額の値を調べて返却する 9-1 クラスの考え方 Withdraw Fig.9-7 メンバ関数の呼出しとメッセージ ■ クラスとオブジェクト 関数 更新 、 。 値 関数 基 実体 C言語 、 多用 、必要 応 値 4 。 考 。 集積回路 、本書 集積回路 4 集積回路」 設計図 作 行 、緻密 結合 4 「 図 処理 第8章 、(実質的 、 (理想的 C++ 設計図= 、 設計 )関数 集合 。 優 、C++ 集合 ) 強大 。 発揮 。 ■ Column 9-1 インラインメンバ関数と前方参照 4 通常の非メンバ関数は、関数原型宣言が与えられない限り、前方参照(自分より後ろ側で定義され た関数を呼び出すこと)ができません(p.170) 。 しかし、メンバ関数では、その制限から解放されます。以下のクラスを考えましょう。 class X int public: int int }; { x; func1() { return func2(); } // 後方で定義されている関数の呼出し func2() { return x; } メンバ関数 func1, func2 ともにインライン関数です。関数 func1 から、それより後方で定義され ている func2 を呼び出していますが、エラーとなることなく正しくコンパイルできます。 コンパイラは、クラス定義を最後まで読んだ後で、インラインメンバ関数の解析を始めるのです。 306 9-2 クラスの実装 本節では、クラスの実装法について学習します。 ■ クラス定義の外部でのメンバ関数の定義 口座 Account 込 。 大規模 、 実装 現実的 一 管理 困難 単一 、 4 定義 外部 実装 関数 定義 定義中 埋 。 。 関数 定義 List 9-4 。 。 メンバは原則非公開ですから、クラス定義の冒頭にあった private: は削除しました。 ▼ クラスの基本 9 4 、 Chap09/list0904.cpp List 9-4 // 銀行口座クラス(第3版:メンバ関数の定義を分離) 実行結果 #include <string> #include <iostream> using namespace std; ■足立君の口座:"足立幸一" (12345678) 800円 ■仲田君の口座:"仲田真二" (87654321) 300円 class Account { string name; string no; long balance; // 口座名義 // 口座番号 // 預金額 public: Account(string n, string num, long z); string GetName(); string GetNo(); long CheckBalance(); void Deposit(long k); void Withdraw(long k); }; //--- コンストラクタ ---// Account::Account(string n, { name = n; // no = num; // balance = z; // } string num, long z) 口座名義 口座番号 預金額 //--- 口座名義を調べる ---// string Account::GetName() { return name; } //--- 口座番号を調べる ---// string Account::GetNo() { return no; } //--- 預金額を調べる ---// long Account::CheckBalance() { return balance; } // // // // // // コンストラクタ 口座名義を調べる 口座番号を調べる 預金額を調べる 預ける おろす 307 //--- 預ける ---// void Account::Deposit(long k) { balance += k; } //--- おろす ---// void Account::Withdraw(long k) { balance -= k; } int main() { Account adachi("足立幸一", "12345678", 1000); Account nakata("仲田真二", "87654321", 200); // 足立君の口座 // 仲田君の口座 // 足立君が200円おろす // 仲田君が100円預ける adachi.Withdraw(200); nakata.Deposit(100); 9-2 クラスの実装 cout << "■足立君の口座:\"" << adachi.GetName() << "\" (" << adachi.GetNo() << ") " << adachi.CheckBalance() << "円\n"; cout << "■仲田君の口座:\"" << nakata.GetName() << "\" (" << nakata.GetNo() << ") " << nakata.CheckBalance() << "円\n"; return 0; } 定義 必要 関数 定義 外部 。 、以下 宣言・定義 定義 中 関数 定義 外 関数 含 、 名 :: :: 演算子 関数原型宣言 定義 関数 、関数 宣言 4 4 定義中 。 行 。 行 。 、名前 以下 。 関数名 有効範囲解決演算子 囲(class scope)中 数 CheckBalance 行 場合 Account 示 示 。 名 :: 。 付 預金額 名前 調 クラス有効範 関 、 属 CheckBalance 、Account:: 前置 必要 。 ▼ 単項の :: 演算子は p.197 で、2項の :: 演算子は p.283 で学習しました。 ▼ 重要 クラス X のメンバ関数 func は、クラス定義の外部では以下のように定義する。 返却値型 X::func( 仮引数宣言節 ) { /* … */ } 本章以降では、“プログラムを一目で見渡せるように” という観点から、プログラムやコメントの 表記をぎっしり詰めています。実際にプログラムを作る際は、もっとスペース・タブ・改行を入れて、 ゆとりある表記をするようにしてください。 308 ■ インタフェース部と実装部の分離 前 、 場合 main 関数) 一 、 一人 宣言・定義 、 Account 実現 小規模 行 限 、 必要 点 利用 。 定義 必 提供 必要 。 、具体的 関数定義 、 中身 。 考 装 設計・開発 、独立 限 保守 、 収 利用 、 部分( 。 1個 利用者 利用 。 、 定義 一般的 構成 関数 定義 別々 実 。 Fig.9-8 クラスの基本 9 ヘッダ"ABC.h" class ABC { インタフェース部 }; インクルード インクルード インクルード #include "ABC.h" #include "ABC.h" #include "ABC.h" ABC::ABC() { // クラスを利用する // プログラム // クラスを利用する // プログラム 実装部 } Fig.9-8 クラスの実装 開発者 、 部 定義 実装部 義 作成 作成 、関数定 。 ▼ 図に示すのは、実装部を一つのソースファイルとして実装した例です。大規模なクラスであれば、 実装部を分割して複数のソースファイルで実現することになります。 実現 ・ List 9-5 ▼ ・ List 9-6 銀行口座 Account Account 以下 示 実装部( 。 部( ) 関数 定義) 本書では、クラス定義を含むヘッダをインタフェース部と呼び、メンバ関数の定義を含むソースフ ァイルを実装部と呼ぶことにします(ヘッダ中に関数定義が含まれることもありますので、これらの 語句が 100%適切というわけではありません) 。 309 Chap09/Account/Account.h List 9-5 // 銀行口座クラス(第4版:インタフェース部) #include <string> using namespace std; class Account { string name; string no; long balance; // 口座名義 // 口座番号 // 預金額 public: Account(string n, string num, long z); string GetName() { return name; } string GetNo() { return no; } long CheckBalance() { return balance; } // 口座名義を調べる // 口座番号を調べる // 預金額を調べる void Deposit(long k); void Withdraw(long k); // 預ける // おろす 9-2 クラスの実装 }; // コンストラクタ Chap09/Account/Account.cpp List 9-6 // 銀行口座クラス(第4版:実装部) #include <string> #include <iostream> #include "Account.h" using namespace std; //--- コンストラクタ ---// Account::Account(string n, { name = n; // no = num; // balance = z; // } string num, long z) 口座名義 口座番号 預金額 //--- 預ける ---// void Account::Deposit(long k) { balance += k; } //--- おろす ---// void Account::Withdraw(long k) { balance -= k; } 定義 。窓口 含 、 利用 供給 "Account.h" 利用 《窓口》 、 実装部 、 #include "Account.h" 、 Account 宣言 取 込 。 、 310 利用者 部 大事 入手 、 、 形 適切 記入 重要 用意 。 標準 定義 、 済 利用 事実、C++ 、 。実装部 関 提供 。 、立派 。 クラスはブラックボックスである。クラスの宣言であるインタフェース部は原 則としてヘッダに記述する。それはクラスの 仕様書 となる。 、 Account 中 定義 。 関数 GetName, GetNo, CheckBalance 、以下 関数 、効率 利用者 対 好 中身 4 状態 関数 4 放棄 4 、非公開部 処理 期待 利用者 提供 、 、 点 品質 効率 定義 向上 4 努力 4 完全 示 4 。 実行 。C++ 使命 、非公開部 。 公開部 結果作成 』 。C++ 、C++ 実行効率 低下 率 。 仕様 利用者 対 、 。 暴露 。実 関数化 4 。 詳細 、 見 示 定義 、実行速度 、『C言語 与 同程度 中途半端 実行効 仕様 本音 、 、他人 見 見 ! 。 ■ -> 演算子によるメンバのアクセス Account 第4版 利用 nakata Account 型 adachi 例 指 違 *nakata 。 。 生成 *adachi 示 Account* 型 点 、 new 演算子 List 9-7 、 銀行口座 adachi nakata (p.231)。 int 型のオブジェクトを動的に生成して *x を 5 で初期化する(p.232)のと同じ形式です。 ▼ クラスの基本 9 、 int* x = new int(5); 一般 p *p (*p).m 指 *p m 、以下 式 表 (p.217) 、p 指 (Fig.9-9) 。 // pが指すオブジェクト*pのメンバm 311 Chap09/Account/AccountTester.cpp List 9-7 // 銀行口座(第4版)の利用例 実行結果 #include <string> #include <iostream> #include "Account.h" using namespace std; ■足立君の口座:"足立幸一" (12345678) 800円 ■仲田君の口座:"仲田真二" (87654321) 300円 int main() { Account* adachi = new Account("足立幸一", "12345678", 1000); Account* nakata = new Account("仲田真二", "87654321", 200); // 足立君が200円おろす // 仲田君が100円預ける adachi->Withdraw(200); nakata->Deposit(100); cout << "■足立君の口座:\"" << adachi->GetName() << "\" (" << adachi->GetNo() << ") " << adachi->CheckBalance() << "円\n"; cout << "■仲田君の口座:\"" << nakata->GetName() << "\" (" << nakata->GetNo() << ") " << nakata->CheckBalance() << "円\n"; 9-2 クラスの実装 return 0; ▼ } *p を囲む ( ) を省略することはできません。 アドレス演算子 * よりもメンバアクセス演算子 k m x . の優先度のほうが高いからです。 、 式 煩雑 、以下 簡略表記 (*p).k (*p).m (*p).x p->k p->m p->x 。 p p->m 一般 呼 アロー演算子(arrow operator) -> 、 指 Fig.9-9 クラスオブジェクトとポインタ 演算子 (Table 9-2)。 本 、 関数 呼出 、 演算子 利用 。 ■Table 9-2 メンバアクセス演算子(アロー演算子) x->y x が指すオブジェクトのメンバ y をアクセスする(すなわち (*x).y と同じ)。 ▼ アロー演算子という俗称は -> の形が矢印(arrow)に似ていることに由来します。 ■ 自動車クラス 定義中 関数 定義 。 自動車 由 移動 現在位置 表 定義 自動車 座標 燃料 。 提供 、 List 9-8(次 。燃料 残 ) 示 限 、自 。 312 Chap09/Car.h List 9-8 // 自動車クラス #include <cmath> #include <string> #include <iostream> using namespace std; class Car { string name; int width, length, height; double x, y; double fuel; 名前 車幅・車長・車高 現在位置座標 残り燃料 public: //--- コンストラクタ ---// Car(string n, int w, int l, int h, double f) { name = n; width = w; length = l; height = h; fuel = f; x = y = 0.0; } // 現在位置X座標を返す // 現在位置Y座標を返す // 残り燃料を返す double X() { return x; } double Y() { return y; } double Fuel() { return fuel; } クラスの基本 9 // // // // void PutSpec() { cout << "名前:" cout << "車幅:" cout << "車長:" cout << "車高:" } << << << << // スペック表示 name << "\n"; width << "mm\n"; length << "mm\n"; height << "mm\n"; bool move(double dx, double dy) { // X方向にdx・Y方向にdy移動 double dist = sqrt(dx * dx + dy * dy); // 移動距離 }; if (dist > fuel) return false; else { fuel -= dist; x += dx; y += dy; return true; } } // 燃料不足 // 移動距離の分だけ燃料が減る Car 7個 。 name 自動車 名前 、width, length, height 在位置 X 座標 Y 座標 、fuel 残 燃料 車幅・車長・車高 。x y 現 。 ・ 座標 除 五 受 、自動車 ・ 取 各 位置 原点 (0.0, 0.0) 、X 座標 値 x、Y 座標 。 車 y 値 0.0 。 関数 X, Y, Fuel 関数 ・ 。x 関数 PutSpec 名前 車幅・車長・車高 表示 。 値 y、残 燃料 値 fuel 返 313 ・ 関数 move 自動車 X 方向 。移動 計算 dx、Y 方向 dy 移動 距離 dist Fig.9-10 示 求 dy √dx2+dy2 。 ▼ double 型実数値の平方根を求めるライブラリが <cmath> で提供される sqrt です。関数の形式は以下 dx の通りです。 Fig.9-10 自動車の移動と距離 double sqrt(double); 残 燃料 fuel 必要 ▼ 移動 移動距離 dist 燃料 場合 満 移動不能 判断 現在位置 残 燃料 更新 true 返 false 返 。 。 燃費を 1 と考えています。すなわち距離 1 を移動すると燃料も 1 減ります。 自動車 利用 例 List 9-9 示 。 9-2 実行例 #include <iostream> #include "Car.h" using namespace std; int main() { string name; int width, length, height; double gas; cout << "車のデータを入力せよ。\n"; cout << "名前は:"; cin >> name; cout << "車幅は:"; cin >> width; cout << "車長は:"; cin >> length; cout << "車高は:"; cin >> height; cout << "ガソリン量は:"; cin >> gas; Car myCar(name, width, length, height, gas); myCar.PutSpec(); // スペック表示 車のデータを入力せよ。 名前は:僕の愛車 Ÿ 車幅は:1885 Ÿ 車長は:5022 Ÿ 車高は:1490 Ÿ ガソリン量は:90 Ÿ 名前:僕の愛車 車幅:1885mm 車長:5022mm 車高:1490mm 現在地(0, 0) 残り燃料:90 移動[0…No/1…Yes]:1 Ÿ X方向の移動距離:5.5 Ÿ Y方向の移動距離:12.3 Ÿ 現在地(5.5, 12.3) 残り燃料:76.5263 移動[0…No/1…Yes]:0 Ÿ while (true) { cout << "現在地(" << myCar.X() << ", " << myCar.Y() << ")\n"; cout << "残り燃料:" << myCar.Fuel() << '\n'; cout << "移動[0…No/1…Yes]:"; int move; cin >> move; if (move == 0) break; } double dx, dy; cout << "X方向の移動距離:"; cin >> dx; cout << "Y方向の移動距離:"; cin >> dy; if (!myCar.move(dx, dy)) cout << "\a燃料が足りません!\n"; } 最初 名前 myCar 車幅 構築 後、現在位置 読 込 。 、 値 関数 PutSpec 移動 対話的 繰 返 。 Car 型 表示 。 クラスの実装 Chap09/CarTester.cpp List 9-9 // 自動車クラスの利用例 314 ■ Column 9-2 C言語の構造体と共用体 C言語の構造体(structure)と共用体(union)について簡単に紹介します。 ■ 構造体 C言語の構造体を用いると、日付(第 10 章)は以下のように宣言できます。 struct date { int year; int month; int day; }; /* 西暦年 */ /* 月 */ /* 日 */ C++ のクラスと比べると、C言語の構造体には以下のような制限があります。 クラスの基本 9 独立 型 上のように date が宣言されても、“date 型” ができるのではなく、“構造体 date 型” が作られるだ けです。構造体名 “date” 単独では型とはならず、“struct date” が型となります。したがって、“構 造体 date 型” のオブジェクト day の定義は以下のようになります。 struct date day; struct は省略できません。 公開 構造体のメンバはすべて公開されます。すなわち、全メンバが “公開部” で宣言されているものと して扱われます。したがって、クラス外部からメンバを保護することはできません。 関数 構造体のメンバとしては、データメンバのみが許されます。メンバ関数をもつことはできません。 もちろん、メンバ関数の一種である “コンストラクタ” をもつこともできませんので、確実な初期化 の手段を提供することはできません。初期化を行う際は、 struct date day = {2010, 11, 18}; と配列風の { } を用いた初期化子を与えなければなりません(各メンバに対する初期化子をコンマで 区切ったものを { } で囲みます) 。 いくつかの制限をあげましたが、次章以降で解説する “クラス” 特有の機能は、ほとんどすべて使 えません。 ※実際には、“C++ のクラスを制限したものがC言語の構造体” ではなく、“C言語の構造体をもとに して大幅に拡張して作られたものが C++ のクラス” です。 ■ 共用体 構造体が直列的なデータ構造を実現するのに対し、共用体は並列的かつ選択的なデータ構造を実現 します。構造体と共用体のイメージを対比したのが Fig.9C-1 です。 共用体であることを宣言するためのキーワードが union です。メンバの宣言法や、. 演算子と -> 演算子によってメンバをアクセスできる点は、クラスや構造体と同じです。 なお、構造体と同様に、メンバ関数をもったり、非公開とすることはできません。 315 共用体 構造体 struct xyz int long double }; union xyz { int x; long y; double z; }; { x; y; z; struct xyz a; union xyz b; a.x b a.y b.x b.y b.z a.z 共用体ではすべてのメンバが同一アドレス上に並びます。そのため、“全メンバが同時に存在する” というよりも、“同時には一つのメンバだけが意味をもつ” といった性格となります。 たとえば、 b.x = 5; /* int型のメンバxに5を代入 */ という代入を行って、その直後に、 c = b.z; /* double型のメンバzとして値を取り出す */ としても意味のない値が得られることになります。共用体は、 『メンバ x, y, z を同時に使うことはないから、一緒の領域に閉じこめてしまえ。 』 といった目的で利用されるのです。 なお、C++ では共用体も大幅に機能が拡張されています。 * class, struct, union というキーワードは、《クラス》、《構造体》、《共用体》という概念と一対一 で対応するものでは 。 C++ でのキーワード struct は、キーワード class とほとんど同じ意味が与えられており、クラス の宣言に使うことができます。唯一の相違点は、struct によってクラスを宣言すると、アクセス権 を指定しないメンバが(非公開でなく)公開となるこということだけです。 ■ 演習 9-1 自動車クラス Car にデータメンバやメンバ関数を自由に追加せよ(ナンバーを表すデータメン バを追加する、燃費を表すデータメンバを追加する、移動による燃料残量の計算に燃費を反映さ せる、タンク容量を表すデータメンバを追加する、給油のためのメンバ関数を追加する etc…)。 ■ 演習 9-2 名前・身長・体重などをメンバとしてもつ《人間クラス》を自由に定義せよ。 9-2 クラスの実装 Fig.9C-1 構造体と共用体 316 まとめ ■ プログラムを作る際は、現実世界のオブジェクトをプログラムの世界のオブジェクト に投影する。 ■ その投影に際しては「まとめるべきものは、まとめる。」「本来まとまっているものは、 ■ クラスはプログラムの集積回路の設計図である。任意の型の要素をデータメンバとし そのままにする。」といった方針をとったほうが自然で素直なプログラムとなる。 クラスの基本 9 てもつことができる。メンバ関数をもつこともできる。 ■ クラス A のクラス定義は class A { /* … */ }; とする。末尾にはセミコロンが必要 である。{ } の中はデータメンバとメンバ関数の宣言である。 ■ オブジェクト x のメンバ m は、x.m としてアクセスできる。 ■ ポインタ p が指すオブジェクトのメンバ m は、(*p).m あるいは p->m としてアクセス できる。 ■ クラスのメンバは、そのクラスの有効範囲の中に入る。したがって、クラス C のメン バ m の名前は C::m となる。 ■ データメンバとメンバ関数は、公開することもできるし非公開にすることもできる。 ■ データ隠蔽を実現するために、原則としてデータメンバは非公開にするとよい。 ■ オブジェクトの生成時に呼び出されるメンバ関数がコンストラクタである。コンスト 公開を指示するのが public: であり非公開を指示するのが private: である。 ラクタの目的は、オブジェクトを適切に初期化することである。 ■ コンストラクタの名前はクラス名と同一である。返却値型はなく void とすら定義す ることはできない。 ■ メンバ関数の中では、公開メンバにも非公開メンバにも自由にアクセスできる。 ■ メンバ関数を呼び出すことは、オブジェクトに対してメッセージを送ることである。 メッセージを受け取ったオブジェクトは能動的に処理を行う。 317 ■ クラス定義中で定義されたメンバ関数はインライン関数となる。メンバ関数は前方参 ■ クラス定義は独立したヘッダとして実装する。そのヘッダをインタフェース部と呼ぶ。 ■ クラス定義中で定義しないメンバ関数の定義をソースファイルとして実装する。その 照ができる。 ソースファイルを実装部と呼ぶ。 まとめ 9