C言語講座15回目です。
ちょっと変わった志向で、C言語でオブジェクト指向のプログラムをしてみます。
オブジェクト指向言語としては、C言語ならC++が普通に使えるし、たいていのプログラミング言語では、クラスが使えるのに、わざわざC言語でオブジェクト指向をなぜするのか?
オブジェクト指向を理解して、クラスの必要性をよりわかってもらうため!
というのが、今回の試みになります。
C言語でオブジェクト指向
オブジェクト指向でプログラミングするメリット
そもそもの発端が、「クラスがなぜ必要なの、関数が書ければ十分じゃないの?」という話題を、ネット上では結構みたからです。
「大規模プロジェクトでは、他に影響を与えないようにするためにクラスにまとめます」というのが多くの答えでした。
もちろんその通りなのですが、では小規模でちょっとしたプログラムを書くときにはクラスは必要ないのでしょうか?
クラス化とフレームワーク化をすることで再利用がずっと進むし、コーディング量は相当減らすことができます。
そこで、関数しか使えないC言語で、オブジェクト指向的に書いてみることで、だからクラスという概念がでてきたのだ、ということを改めてお伝えしたいと思います。
オブジェクト指向の考え方は、オブジェクト指向言語でなければ実装できないかというと、実際にそんなことはありません。
オブジェクト指向とは、考え方です。
もちろん、C++の方が、C言語よりは、クラスがある分実装は楽にはなります。
しかし、C++も最初の頃は、C言語からのトランスレータ(※)で実現されていたことを考えれば、C言語でオブジェクト指向の考え方の一部を実現することも、やろうと思えばできるのです。
※C++のソースコードを入力して、C言語のソースコードに変換することが、最初の頃のC++でした。
C言語でオブジェクト指向プログラミングをしてみる
生徒名簿を作ることを考えてみます。
登場するオブジェクトは、たとえば、【生徒】、【名簿】。
名簿は、一覧と考えれば、生徒というオブジェクトを表すものと、それを一覧にできるものを作ればよいことになります。
/* 生徒 */ typedef struct _tagStudent { char szName[NAME_LEN+1]; char szAdder[ADDER_LEN+1]; int iClass; } TSTUDENT; /* 生徒一覧 */ typedef struct _tagStudentArray { TSTUDENT* pStudents[MAX_STUDENTS]; int nStudents; } TSTUDENT_ARRAY;
生徒型をTSTUDENT、生徒一覧をTSTUDENT_ARRAY として作ります。
簡単にするために、生徒は、名前と住所と、クラスだけを持つものとします。
生徒一覧は、その生徒を配列につないだものと、生徒数だけを持ちます。
さて、ファイルから生徒の一覧を読み込んで、それをプリントすることを考えます。
簡単に順番に書くと、よくあるロジックで考えるとでは次のようになります。
・ファイルから1行読み込む(名前と住所)
・名前と住所をプリントする
・ファイルの最後まで読み込みとプリントを繰り返す
オブジェクト指向で考えると、考え方が変わります。
・生徒一覧にファイルから生徒の情報(名前と住所)を読み込む
・生徒一覧をプリントする。
つまり、
生徒一覧←ファイルを読み込む
生徒一覧←ファイルをプリント
ということです。
生徒一覧にお願いすれば、あとはやってくれる!
こちらの方が、考え方は自然だと思いませんか?
実際にC言語で書くならば、C言語なので、メモリのアロケーション等も必要になるので、生徒では、こんな関数が必要になります。
/* 生徒関係 */ TSTUDENT* Student_Alloc(const char* szName, const char* szAdder, int iClass); void Student_Free(TSTUDENT* pStudent); int Student_Print(TSTUDENT* pStudent); /* 一覧関係 */ TSTUDENT_ARRAY* StudentArray_Alloc(); void StudentArray_Free(TSTUDENT_ARRAY* pArray); int StudentArray_Print(TSTUDENT_ARRAY* pArray); int StudentArray_LoadFromFile(TSTUDENT_ARRAY* pArray, const char* szFileName); 構造体のアロケーションとフリー、配列への追加、削除、プリントなど、必要な機能をつくりこみます。 プログラムは、これらを組み合わせて作ります。エラー処理は割愛しますが、先ほどの考え方で実装できます。 int main() { StudentArray* pStudentAry; pStudentAry = StudentArray_Alloc(); /* ファイル読み込み */ StudentArray_LoadFromFile(pStudentAry, "生徒一覧.txt"); /* 一覧表示 */ StudentArray_Print(pStudentAry); StudentArray_Free(pStudentAry); } これは、ファイルから一覧を読みこみ表示するだけの機能を擬似的に実現しただけである。 勿論、実際のアプリケーションにするには、一覧表示機能以外に、生徒入力機能、一覧保存機能、生徒削除機能などが追加されていくことになると思われます。 それも以下のように簡単にできます。
/* 追加対応 */ int StudentArray_AddStudent(TSTUDENT_ARRAY* pArray, TSTUDENT* pStudent); int StudentArray_DeleteStudent(TSTUDENT_ARRAY* pArray, const char* szName); int StudentArray_SaveToFile(TSTUDENT_ARRAY* pArray, const char* szFileName);
プログラムが複雑にならずに追加ができるし、考え方はオブジェクト指向の方が、シンプルだと思います。
オブジェクト指向で共通部品化
上記で書いたようなオブジェクト指向でオブジェクトという対象(生徒とか生徒一覧とか)を考えていくと、オブジェクト単位で部品として独立して使えるものになります。
ここで文字列の配列を考えてみます。
C言語で普通に実装すると、配列にしてしまうケースも多いと思います。
char strlist[128][128];
char *strlist[128];
もちろん、リストをポインタでつなぐこともよくやりますが、ポインタのつなぎ変えとかの処理を都度書いているのをよく見かけます。
オブジェクト指向で配列を考えたら例えば次のような関数群を作り上げれば、あとはそれを呼び出すだけで配列操作ができるようになります。
typedef struct _tagSTRARY { int iSize; char** pData; int iMaxSize; } TSTRARY; STRARY* StrArray_Alloc(int nArray); void StrArray_Free(TSTRARY* pAry); void StrArray_DeleteAllItems(TSTRARY* pAry); char* StrArray_GetItem(TSTRARY* pAry, int id, char* pData); int StrArray_AddItem(TSTRARY* pAry, char* pData); int StrArray_GetSize(TSTRARY* pAry); int StrArray_Find(TSTRARY* pAry, char* pData); int StrArray_SaveToFile(TSTRARY* pAry, const char* szFileName); int StrArray_LoadFromFile(TSTRARY* pAry, const char* szFileName);
これ、よくよくみると、C++や、Java、というか、今ならほとんどの言語でクラスとして提供されているものを、関数で実装したものになります。
文字列の場合は、【文字列】という対象に対しての各種操作を定義すれば、【文字列】操作が簡単になるということです。
対象を「オブジェクト」として見つけだして、その対象に対する「処理・操作」をまとめると、その対象「オブジェクト」を共通に使えるようになるのです。
これが、「クラス」と「メソッド」の考え方です。
プログラミングするときは、最近の言語であれば、「使う立場」では使いこなしているとは思いますが、「作る立場」で考えてみると、ひとつ世界が広がります。