目次
はじめに
C言語は今から40年ほど前に作成された言語ですが、
未だに、OSや組み込みなど幅広いアプリケーションで使用されています。
C言語はかなり機械語に近い言語なので、
一般的なプログラミング言語に標準で搭載されている
オブジェクト指向に関する機能はついていません。
しかし、対象機器のリソースの増加に伴い、
C言語を使用しなければなら無い場合でも、
複雑な状態遷移を伴う大規模なアプリケーションを
作成しなくてはならない場合があります。
そのようなC言語を使用しなくてはならない場合でも、
オブジェクト指向に基づいたアプリケーションを作成したくなるのですが、
前述のようにC言語はオブジェクト指向プログラミングに
言語機能として対応していないので、
C言語の標準機能でオブジェクト指向プログラミングをするのは難しい
という問題がありました。
しかし、
冒頭の参照資料で提案されているように、C言語の標準機能をうまく使うことにより、
C言語でもオブジェクト指向のプログラミングを実現することは可能です。
今回の記事では、
C言語によるオブジェクト指向の実施方法の
一例を説明したいと思います。
オブジェクト指向プログラミングの要素
下記のwikipediaの記事にある通り、
オブジェクト指向プログラミングは
主に4つの機能によって実現されます。
これから、これらの4つの機能をそれぞれ
C言語で実装する一例を説明したいと思います。
オブジェクト指向に関する詳しい説明は
冒頭の参考資料を参照下さい。
1. カプセル化
カプセル化は、ソフトウェアの機能をそれぞれのモジュールに分割し、
自分のモジュールの変数や関数を、
関係の無い、他のモジュールから隠す方法です。
これにより、そのモジュールを使用する人は
モジュールのインターフェースと大まかな機能だけを理解すればよく、
詳しい中身の実装を理解する必要はなくなります。
また、モジュールの設計者が変更して欲しくない値などを
ユーザに隠蔽することができ、
意図しないモジュールの使用を防ぐことができるようになります。
C言語において、このカプセル化はファイルの分割と
static変数&static関数で実現することができます。
一つのモジュールをそれぞれの.cファイルと.hファイルに分割し、
そのファイルのみで使用するグローバル変数や関数をstatic宣言します。
このようにstaic宣言することにより、変数や関数のスコープを
そのファイル(モジュール)のみに限定することができるのです。
つまり、他のファイル(モジュール)からは、
static変数やstatic関数は利用できない(見えない)ようになります。
これは個々のモジュールをクラスであると考えると、
オブジェクト指向言語における
static変数はメンバ変数、static関数はprivate関数のような機能を有することができ、
C言語においても、モジュール化によるカプセル化を実現することができます。
サンプルコードでは、Person.cのMAGIC_NUMBERという変数や、
Person_Secretという関数は、static宣言されており、
ヘッダファイルにも記述されていないため、
他のモジュール(Person.cファイル以外)からは利用することができません。
つまり、カプセル化が実現できていることがわかります。
ちなみに、この方法のカプセル化では、
各ファイルがクラスになっているので、
各クラスの関数名には、クラス名_関数名とするのが一般的のようです。
(例: Person_ShowSecret PersonクラスのShowSecret関数)
2. 多様性
多様性(ポリモーフィズム)は、
同じ名前の関数をそれぞれのオブジェクトによって、
処理を変えるようにする手法です。
複数のオブジェクトにある共通の処理を実施してもらいたい場合、
その処理の関数名を同じ名前にすることにより、
ユーザは同じ名前の関数を利用して、複数の処理を実施できるようになり、
再利用性の高いソフトを作成することができます。
C言語でこれを実現するためには、
インターフェースとなる共通の関数ポインタを格納した構造体を作成し、
その構造体に格納する関数ポインタをオブジェクト毎に切り替えることにより、
多様性を実現します。
サンプルコードにおいては、OOP.cのAnimalという構造体がインターフェースになり、
その中のBarkという関数をそれぞれの動物クラスが共通で持っているとします。
そして、それぞれの動物クラス(Person, Dog)のヘッダファイルに書かれている
マクロを使ってAnimal構造体を初期化すると、
それぞれのクラスで定義された関数を同じ関数名で使用することができます。
実際は関数ポインタを渡しているだけですが、
コードの見た目はまさにポリモーフィズムですね。
printf("===ポリモーフィズムのサンプル===\n"); Animal person=newPerson();//人間のオブジェクトを作成 Animal dog =newDog(); //犬のオブジェクトを作成 person.Bark(); dog.Bark();
3. 継承
継承は複数のクラスの共通部分を抽出し、
それらをスーパークラスとして定義し、
残りの異なる部分を
スーパークラスと共有する形でサブクラス化するものです。
これは、構造体の中にスーパークラスのオブジェクトを含めることで実現できます。
この方法はどちらかというと委譲という方法に近いですが、
共通部分を括りだすという目的は実現できます。
サンプルプログラムでは、
Personクラス(構造体)をスーパークラスとし、
Teacherクラス(構造体)をサブクラスとしました。
下記のサンプルのように、
Teacherクラスは、Teacherクラスのメンバ変数(nStudent)だけでなく、
スーパークラス(Personクラス)のメンバ変数(age)も
利用できるようになっています。
//===継承のサンプル=== printf("===継承のサンプル===\n"); Person student; student.age=15; Person_SayAge(student); Teacher teacher; teacher.base.age=30; Person_SayAge(teacher.base); teacher.nStudent=10; Teacher_Bark(teacher);
4. 動的型付け (ダイナミックバインディング)
動的型付けに関しては、動的型付けの定義にもよりますが、
2のポリモーフィズムの項で説明した方法を使って、
その時の状況に応じて、
Animalクラスのオブジェクトに入れるクラスを変更することにより、
動的型付けを実現することができます。