今回は「変数」を超えたデータ、そして「オブジェクト指向」について見ていきましょう。
|
変数の限界
値を入れるもの、それが変数です。でも、プログラムを組んでいくと、変数だと分かりづらくなっていきます。 例えば、「下駄箱」というデータを扱いたい場合、どうすればいいでしょう。左上角を起点にしてGeta01_01, Geta01_02...みたいな感じで変数を作っていったら、とんでもなく大変なことになります。 同じように「自分自身」というデータを扱いたい場合も大変です。身長、体重といった値、名前やニックネームといった値、様々なデータが複合的に存在します。これらを別々に扱うのも大変でしょう。
データは無味簡素な数字です。コンピューターはそれを操作します。 |
配列
最初の下駄箱の例を解決するのが配列です。配列とは、変数を10個とか20個とかまとめて作るもので、例えば |
/* C言語系の場合。 */
int iTest[256]; /*256個の変数を作製します。*/
iTest[0] = 128;
iTest[1] = 256;
iTest[2] = 512;
といった感じに使うことができます。「どの変数に入れるのか」というのは[](大かっこ)の中の数字で選択します。ここにはもちろん変数も使えるので、forループなどと組み合わせることでデータの扱いを簡略化できます。
C言語系とJAVA系は、上の例のような形で配列を作製することができます。Visual Basic系とPascal系は次のようになります。 |
' VB系の場合。
Dim iTest(256) As Integer '256個の変数を作製します。
iTest(0) = 128
iTest(1) = 256
iTest(2) = 512
{ Pascal系の場合。 }
iTest: array [1..256] of Integer;
begin
iTest[0] := 128;
iTest[1] := 256;
iTest[2] := 512;
このように、まったく違います。大変ですねぇ。実際、ここから先は言語によって大きく違います。そして言語によってはサポートしていないものもあるので注意してください。
でも、文法は違ってもどの言語も意味は同じです。その辺は安心してください。 |
構造体
さて、今度はもうひとつの方、「自分自身」を表すデータです。こういった「変数がいっぱい詰まったデータ」を構造体と呼びます。英語で書くとstructure。会社なんかの再構築はRestructuring、いわゆる「リストラ」です。こういう風に憶えると分かりやすいですね。 構造体は、変数の中に変数を持っているような形です。「自分自身」のデータの中に、「身長」というデータ、「体重」というデータ、「名前」というデータが入っている、そんな感じです。それらの変数ひとつひとつで、値を変更したり取得したりすることができます。 ここからは、ほんっとーに言語ごとにバラバラになっちゃってかなり違うので、それぞれの例は紹介せず、簡単な説明だけ行います。
C言語系はstructという予約語を使用します。JAVA系は構造体を持たず、代わりに後で紹介する「クラス」と呼ばれるものを使用します。 |
Wate.Weight = 67;
というふうに、ピリオドを挟むことで構造体の中の変数に値を入れたり取得したりすることができます。ピリオドを「の」に置き換えて読むと分かりやすいかもしれません(つまり「WateのWeight」と読む)。この形式は基本的にどの言語でも同じなので(C言語系のポインタは別)憶えるのは簡単でしょう。もっとも、そのおかげで他の部分で混乱を招くのですが……。
|
オブジェクト指向
「人間構造体」ができた、じゃぁそれを扱おうということで、「体重適性度関数」なるものを作るとします。この関数は、身長、体重、年齢からその人が肥満かやせ過ぎかをチェックするものだとしましょう。 この関数の設計、つまり「どんな引数を取ってどんな戻り値にするか」というイメージ、どのようなものが浮かびますか? そのまま構造体を渡すという人もいれば、みっつのデータだけ渡すという人もいるでしょう。 こういった場合には、基本的に構造体をそのまま渡す方がいいことになっています。構造体の各データを渡す場合、そのデータを間違えて入れてしまったり、さらにそれ以外のデータを入れてしまうかもしれません。また言い換えれば、「それができる」ということでもあります。データの管理がまた難しくなります。
構造体を渡す場合、その関数は「その構造体専用の関数」ということになり、基本的に他の構造体とは関係がなくなります。この考え方を突き詰めると、「専用の関数も構造体の属性のひとつ」という風に捉えることができます。
例えば、ビデオテープ。ビデオテープはビデオデッキを使うことで、中身を見ることができます。つまり「ビデオテープ」という構造体の中のデータを「ビデオデッキ」という専用の関数でのみ取り出せる、ということです。もちろん、ビデオデッキ以外の何らかの方法でも、テープの中のデータを取り出すことができます。が、それはイレギュラーな方法です。
これまでは(特にC言語では)プログラマー重視の風潮がありました。データとはプログラマーが管理するもの、という観点からです。コンピューターは人間が作りだしたもので、人間が管理するものなので、それも当然です。 |
「オブジェクト指向」の実際
長々と書いてきましたが、実際「オブジェクト指向プログラミング」とはどのようなものか説明してませんでしたね。 簡単に言えば「オブジェクト中心」ということです。先ほどの例で言えば「ビデオテープはビデオデッキでなければ見られなくする」ことです。ビデオテープはビデオデッキ以外の方法を用いてアクセスすることができます。が、それはビデオテープにしてみれば「不正なアクセス方法」です。ビデオテープは、ビデオデッキを使って、データを取得して欲しいのです。 こういった「データ側でデータの取得方法を設定することができる」ことが、プログラミング言語としての「オブジェクト指向」です。関数以外にも、様々な「取得方法」を設定することで、オブジェクト自身に「オブジェクトの振る舞い方」を決めさせることができるようになります。
では、現在プログラミングにおいてどのように「オブジェクト指向」は使われているのでしょうか。実際には「色々なところで様々な形で使われている」と言えますが、そのため、ここではひとつひとつ紹介することができません。 |
変数+関数
オブジェクト指向のもっとも基本的な部分です。構造体のような形で変数を持ち、それらを操作するための専用の関数を持ちます。これらを「メンバ変数」「メンバ関数」と呼びます。 オブジェクトの中には、「構造体」的な部分があまりなく、実際には関数のみが後悔されている物もあります(後述の「アクセス指定」を参照)。これは見かけ的には「オブジェクト」そのものといった感じでしょう。 |
初期化(コンストラクタ)と後処理(デストラクタ)
変数と違って、大きく複雑なデータは作製するのも大変です。 例えば「画像イメージ」を保存するデータを作製する場合、まず作るときに「イメージのサイズはどれにするか」「色数はどうするか」「白で埋めるか黒で埋めるか透明で埋めるか」といった各パラメータを最初に設定する必要があります。 また、大きなイメージは普通に変数を作製せず、メモリから直接必要な分だけ取得するのが普通です。その取得、そして、要らなくなったときの解放が必要になってくるわけです。
これらをこなしてくれるのが「コンストラクタ」と「デストラクタ」です。 |
演算子のオーバーロード
オブジェクトからオブジェクトへと代入する場合、すべてのメンバがそのままコピーされます。が、そうではなく、渡されるデータをチェックしたり加工したりしてから格納したい場合もあるでしょう。また、オブジェクトから他の種類のオブジェクトへと代入を行いたい場合、各メンバを渡すのではなくそのまま=を使って代入した方が分かりやすい場合もあるでしょう。
これらをクリアするのが演算子のオーバーロードです。特定の演算子とデータの組み合わせに対して新しい定義を作製し、新たに作製した関数を呼び出すことができます。
これは=だけでなく、+にも<にも−−にも、たいがいの演算子で行うことができます。これらをうまく使うことで、さらに「オブジェクトらしく」することができます。ただし、便利だからといってむやみに使うと、直感的に分かりにくくなってしまう可能性があります。そうなると「オブジェクトらしく」ないので注意しましょう。 |
アクセス指定
オブジェクト内のデータをどのようにするのかまだ決まっていないうちにプログラムに組み込む必要がある場合もあるでしょう。たとえ決定していてもあとで変更する可能性が高い場合もあります。 このような場合には、メンバ関数のみで内部データの設定と取得を行うのが良策です。関数の使用による「パッケージ化」を用いれば、データが変わってもアクセスするためのメンバ関数の中身を変更するだけで、関数の呼び出し側を変更する必要はなくなります。 ところが、オブジェクトが「構造体+関数」だった場合、めんどくさくなってその関数を用いずに直接データへとアクセスする可能性が出てきます。自分以外のプログラマーがそのオブジェクトを使用する場合にはその可能性が特に高くなるでしょう。それを防ぐのが「アクセス指定」です。
アクセス指定には主に「パブリック」と「プライベート」があります。
さらに、このアクセス指定はメンバ関数にも行うことができます。「パブリック」に指定すれば、構造体のようにオブジェクトを宣言した関数から呼び出すことができます。「プライベート」に指定すればオブジェクトのメンバ関数からしか呼べなくなります。 |
継承(インヘリタンス)
「猫」というオブジェクトがあった場合、「三毛猫」というオブジェクトを新たに作製したい場合にはどうすればいいのでしょう。「三毛猫」は「猫」オブジェクトが持つ属性をすべて持っていて、そこに「毛色タイプ」というデータが加えられているだけの違いということにしましょう。そうなると、「三毛猫」オブジェクトをさらに作製するのはめんどくさく、大変です。「猫」のコピーを作製して「三毛猫」を作製したとしたら、「猫」に修正を加える度に「三毛猫」にも修正を加えなければならなくなります。
ここで出てくるのが「継承」という概念です。「継承」とは「元からあるオブジェクトに新たに属性を追加する」ことです。ここでは「猫」オブジェクトから継承して「三毛猫」オブジェクトを作ることになります。 |
多態性(ポリモーフィズム)
継承を使用することで、同属性のオブジェクトをどんどん作ることができます。これらのオブジェクトでは、同名のメンバ関数を作製することができます。例えば「猫」から派生された「三毛猫」「シャム猫」「チンチラ」の各オブジェクトに「毛色タイプ」メンバ関数を作製することができます。そしてこの場合、各関数は同じ引数を取り同じ戻り値を返すけれども、関数の中で行っていることは別ということにします。 このとき、「猫」オブジェクトにも「毛色タイプ」メンバ関数があったら、どうなるでしょう。どちらの関数が呼ばれるか明確にはなっていません。それを防ぐために常に派生先の関数が存在するか調べ、適切な関数を呼び出してくれる機能があります。これを「仮想関数」といいます。
「仮想関数」は継承元のメンバ関数、つまりここでは「猫」オブジェクトの「毛色タイプ」メンバ関数に指定します。指定されたメンバ関数が呼ばれると、そのオブジェクトがその継承元かそれとも派生先なのか、派生先ならどの派生先なのか調べ、そして適切な派生先のメンバ関数を呼び出してくれるというわけです。
こういった、オブジェクトを派生することでその属性がどんどん変わっていくことを「多態性」と呼び、さらに「オブジェクト的」な扱いを行うことができるわけです。 |
オブジェクト指向の利点と欠点
オブジェクト指向の良さは、一度作ってしまえばデータの管理が楽になるということです。これはオブジェクト指向に限らず、プログラミングにおける多くの部分で言えることです。かっちりとしたシステムを構築することで、安定性が約束されるというわけです。 しかし、そこにたどり着くためには様々な難関が立ち塞がっています。オブジェクト指向の場合、「概念」「実装方法」「管理」のみっつをしっかりと学ぶ必要があります。
「概念」とは、それぞれの機能の本来の意味です。継承やアクセス指定はどのような必要性から生まれて、どういうことのために使うべきなのか。今回の講座ではこの部分を中心に解説したつもりです。
とにかく、「データ管理」。これが一番大事です。そのための便利な道具が「オブジェクト指向」であり、もしなければ、ないなりに巧妙に管理する必要があります。実際、「コンパイラに引っかからないミス」のほとんどは「データ管理」の不手際に起因します。見やすく分かりやすい管理、これが大事だということを忘れないでください。
さて、次回はついに最終回。プログラミングのまとめをしようと思います。 |
(C)KAB-studio 1998 ALL RIGHTS RESERVED. |