プログラム・プロブレム

第七回 オブジェクト・クラス(C++)


I はじめに

初(以下H):第七回はオブジェクトとクラスだ。
涼(以下S):いよいよオブジェクト指向の核心に迫るわけですね。
H:そうなるといいんだが… あの作者にあんまり期待するのもなぁ。
S:うぅ、反論が思い付きません。すみません作者さん。
H:じゃ、とりあえずオブジェクトについてから行ってみよう。

II オブジェクト

S:「オブジェクト」… 直訳すると「モノ」ですが。
H:そのままと言えばそのままなんだよな… コンピュータ上で動いてるプログラムの中のモノのことだ。
S:抽象的なんですが、そんな感じです。プログラムの中にも色々なモノがあるんだ、程度に思ってもらえればいいですかね。
H:広い分類だと、変数の中身自体もオブジェクトと思えなくもない。たとえば数値の「10」とか、文字列の「Test」とか。
S:int とか double みたいな基本的な型の変数は「プリミティブ」とも呼ばれて、オブジェクトと区別する場合も多いです。
H:とにかく、コンピュータ上に存在して、色々な種類があるのが「オブジェクト」というわけだ。

III クラス

S:オブジェクトがモノで、こっちはその原型です。クッキーの「抜き型」みたいなものですか。
H:ちなみに抜いたクッキーそれぞれは「(クラスの)インスタンス」とも呼ばれるぞ。
S:中にはクラス自体もオブジェクトになってる言語もあるんですよ。
H:そこまでいくと、もうワケが分からんな。
S:とりあえず C++ のクラスはオブジェクトではないです。あくまで「型」ですね。
H:そしてインスタンスの方がオブジェクトになるわけか。

S:それで、クラスは「抜き型」なので形があるわけです。
H:その形に相当するのが「クラス定義」だ。どんな風になってるかを定義したわけだな。
S:動物なのか植物なのか、動物ならば足はあるのか、あるなら何本か… といった事が書かれています。
H:まあ、あくまで比喩だから、実際にそんな事が書いてあるわけじゃない。…ホントに動物クラスを書くなら別だが。

S:クラス定義は「メンバ変数(属性、プロパティ)」と「メンバ関数(メソッド)」の 2種類に分けられます。
H:例えば「人間クラス」を考えてみる。メンバ変数ってのは「名前」とか「性別」とか、オブジェクトそれ自身を他と区別する情報を入れる変数だ。
S:メンバ関数の方は「歩く」とか「食べる」とかといった「取れる行動」を表します。もちろん主語はオブジェクト自身です。
H:これらをクラスの特徴として「クラス定義」に記述していく。
S:メンバ変数の場合は「型」と「変数名」だけなんですが、メンバ関数については「関数名」、「関数シグネチャ」およびその関数の「内容」が書かれます。

IV 関数

H:関数… 前にちょっとだけ出てきたが、これは一体何なのか?
S:数学で言う関数とはちょっと違います。正しくは「手続き」と呼ぶべきかもしれませんが、C 系列では「関数」です。
H:一言だと「ある作業のまとまり」だろうか。
S:「作業の手順書」とも言えますね。
H:とにかく、処理のまとまりを単位にして名前を付けたものが「関数」だ。
S:実は今までのソースでの「main」も関数だったんですよ。
H:C 系列では、プログラムの実行時に開始される処理に「main」という名前を付けるルールになっている。
   返り値の型 関数名(引数のリスト)
   {
     実際に行う処理内容
   }
S:こういう感じで定義されますね。
H:次の例を参考に考えてみよう。
   int main(int argc,char* argv[])
   {
     いろいろな処理
   }
S:まず「返り値の型」は「int」です。
H:そして「関数名」が「main」だ。
S:「引数リスト」にあたるのは「int argc,char* argv[]」ですね。
H:「引数(ひきすう)」ってのは… 「関数の中だけで使える変数」かねぇ?
S:えーと、例で説明しましょうか。次のは引数で渡された数に 1 を足した数を返す関数の使い方です。
01:// plusone.cpp
02:#include <iostream>
03:
04:int plusone(int x)
05:{
06:  return x + 1;
07:}
08:
09:int main(int argc,char* argv[])
10:{
11:  std::cout << "3 + 1 = " << plusone(3) << std::endl;
12:
13:  return 0;
14:}
H:04~07行目が関数 plusone の定義だ。引数は一つで「int x」だな。
S:実際に使われているのは11行目です。「plusone(3)」として呼ばれていますね。
H:この呼び出しがあると、渡された 3x に設定して、plusone の内容が実行される。
S:つまり「return x + 1;」は「return 3 + 1;」と見えるわけです。
H:引数に渡す値を変えてやることで、別の条件で同じ処理ができるよな。「plusone(99)」とか。
S:こんな風に「ある処理のまとまり」が「関数」として括り出されていると、後から使う時に便利なわけです。
H:この例だと大した事をしてないから、あんまりメリットを感じないかもしれないな…
S:いずれにしても「処理に関係するパラメータを引数として渡せる」のが重要です。
H:そして実際の処理内容は「引数にある名前」を使って記述すると。

V ソースコード

01:// class.cpp
02:#include <iostream>
03:
04:class PlusN
05:{
06:    int m_value;
07:  public:
08:    PlusN(int v);
09:    int getResult(int n);
10:};
11:
12:PlusN::PlusN(int v)
13:{
14:  m_value = v;
15:}
16:
17:int PlusN::getResult(int n)
18:{
19:  return n + m_value;
20:}
21:
22:int main(int argc,char* argv[])
23:{
24:  PlusN plusOne(1);
25:  PlusN plusTwo(2);
26:
27:  std::cout << "3 + 1 = " << plusOne.getResult(3) << std::endl;
28:  std::cout << "3 + 2 = " << plusTwo.getResult(3) << std::endl;
29:
30:  return 0;
31:}
S:いつもより大分長くなってますね。実行すると次のような結果が出力されます。
01:3 + 1 = 4
02:3 + 2 = 5
H:これだけ見て理解できたら、以下は読まなくても大丈夫だぞ。

VI 解説

クラス定義

S:01~03行目まではいつも通りですね。一方、main は22行目からになっています。
H:関数とかクラスは使う前に内容が分かってないといけないから、main よりも前に書いてあるぞ。
04:class PlusN
05:{
06:    int m_value;
07:  public:
08:    PlusN(int v);
09:    int getResult(int n);
10:};
S:04~10行目までがクラス定義になります。08と09行目のメンバ関数は宣言されているだけで内容はまだ書いてありません。
H:10行目の最後に「;」(セミコロン)がある事に注目だ。クラス定義は閉じ括弧の後ろにこれが必要になる。
S:忘れると、例によってコンパイラに分かりにくいエラーで文句を言われます。
H:内容としては… とりあえず07行目の「public:」からか。
S:これは「アクセス指定子」というもので、以降のメンバが誰からでも使えることを宣言しています。
H:他には「protected → 自分と関係者だけ」と「private → 自分だけ」があるぞ。
06:    int m_value;
S:これがメンバ変数の宣言です。「int」型の変数「m_value」を準備していますね。
H:アクセス指定子は「class」のデフォルトである「private」になるぞ。
S:自分だけしかアクセスできなくなるわけですね。
08:    PlusN(int v);
09:    int getResult(int n);
H:続いてメンバ関数の宣言だ。二つあるのが分かるな。
S:一つ目が「PlusN(int v)」で、二つ目が「int getResult(int n)」です。
H:二つ目の方は前に説明した関数の通りなんだが、一つ目はちょっと例外的だ。
S:これはクラス特有のメンバ関数で、「コンストラクタ」と呼ばれています。
H:返り値の型が書いてないのと、関数名がクラス名と同じなのが特徴だぞ。
S:返り値の型が無いのは、クラスのインスタンス自身が返ると決まっているからなんですが… 詳しくは後で使い方を見る時にしましょう。
H:ちなみに、どっちのメンバ関数も引数を一つ取って、その型は「int」だな。
S:さらに「getResult」の方は返り値も「int」ですね。

main

22:int main(int argc,char* argv[])
23:{
24:  PlusN plusOne(1);
25:  PlusN plusTwo(2);
26:
27:  std::cout << "3 + 1 = " << plusOne.getResult(3) << std::endl;
28:  std::cout << "3 + 2 = " << plusTwo.getResult(3) << std::endl;
29:
30:  return 0;
31:}
H:途中を飛ばして先に main からやってしまおう。
S:ここは前までの回を理解していれば、特に問題無いはずですよ。
24:  PlusN plusOne(1);
25:  PlusN plusTwo(2);
H:24、25行目だ。見ての通りの変数宣言だな。
S:PlusN 型の「plusOne」と「plusTwo」を作っています。クッキーを二つ抜いているわけですね。
H:それぞれに「1」と「2」が渡っているのを覚えておいてくれ。
S:「plusOne」のクッキーには 1、「plusTwo」のクッキーには 2 ですよ。覚えておいて下さいね。
27:  std::cout << "3 + 1 = " << plusOne.getResult(3) << std::endl;
28:  std::cout << "3 + 2 = " << plusTwo.getResult(3) << std::endl;
S:そして、今まで通りの出力ですね。
H:気になるのは「plusOne.getResult(3)」とかの部分だな。
S:これがメンバ関数の呼び出し方なんです。「インスタンス名.メンバ関数名(引数)」で呼びます。
H:つまり「plusOne」と「plusTwo」の「getResult」メンバ関数を呼んでるわけだ。
S:はい。その中身については以降で説明しますね。
H:返り値は int だから、数値が出力されるのは分かるよな。

メンバ定義

12:PlusN::PlusN(int v)
13:{
14:  m_value = v;
15:}
H:で、12~15行目がコンストラクタの定義だ。
S:名前の所が「クラス名::関数名」になってるのに注意してくださいね。
H:中身は「m_value = v;」だけだな。やってる事に不思議は無いが、m_value がこのスコープ中で宣言されてないのに気付いたか?
S:ここがオブジェクトの特徴です。main の中で「plusOne」と「plusTwo」が作られていたのを覚えていますね?
H:そいつらがクラスと言う型で抜かれたクッキー一つ一つなわけだが…
S:このそれぞれが「m_value」という名前の変数を持っています。
H:で、そこに値が設定されるんだな、これが。
S:24行目で「PlusN plusOne(1)」だったのを覚えてますか?この「1」が v に渡されます。
H:つまり plusOne と言うインスタンスの変数 m_value には 1 が格納されるということだ。
S:同じように25行目では plusTwom_value2 に設定しています。
H:…クッキーの例だと「plusOne」の方には「m_value = 1」と書いてあって、「plusTwo」には「m_value = 2」とあるわけだな。
S:…いい加減クッキーから離れませんか?
17:int PlusN::getResult(int n)
18:{
19:  return n + m_value;
20:}
H:そして getResult メンバ関数の定義の方になる。
S:ここでも m_value は宣言されていません。というわけで動作も前と同じようになります。
H:インスタンス自身の m_valuen と足して返すわけだ。

V まとめ

S:以上で要素の説明をし終わったわけですが…
H:最後にちょっとだけまとめ直しておくことにしよう。
S:ポイントその1:クラスと関数は使う前に書かれていなくてはならない。
H:ポイントその2:クラス名と同じ名前のコンストラクタはちょっと特殊なメンバ関数である。
S:ポイントその3:メンバ関数の呼び出しにはインスタンスを指定する必要がある。(コンストラクタは別)
H:ポイントその4:メンバ定義の中では引数の他に、インスタンス固有のメンバ変数も利用できる。
S:あとはもう一度ソースコードを追いかけてみてください。きっと理解できると思います。
H:分からなかったら検索して他のサイトを見るのはお約束だ。

VI 終わりに

S:ふぅ、結構大変な説明でした。
H:しかし、作者のヤツ「普通と違うのが書きたい」とか言ってた割には「クッキーと抜き型」なんて独創性の無い例を使ったな。
S:仕方無いんです。それが一番分かりやすい表現なんですから。
H:それで読者の方々の理解の助けになってればいいんだがな。
S:オブジェクトの概念は、コンピュータの動作原理とも関わってますからね。理解が難しいのは確かです。
H:「メモリ上の塊がオブジェクトだ」って言って通じれば簡単なんだがな。
S:そういう人には、こんな記事必要ありませんってば。
H:なにはともあれ、今回はこれで終わりだ。
S:ご意見、ツッコミは歓迎です。トップページ最下部のメールアドレスまでお寄せ下さいね。

第七回 終了


一覧に戻る