C言語でポリモーフィズム
C言語にはオブジェクト指向に関する機能はないですが,ポリモーフィズムを実現する方法をいくつか紹介します.
まずC言語でオブジェクトの継承を表現します.
以下のサンプルにおいて,Person
オブジェクトは「名前」という情報を持っています.
Student
とTeacher
はPerson
を継承しており,Student
には学年,Teacher
には教えてる教科名を持たせます.
new_student
とnew_teacher
はStudent
とTeacher
のコンストラクタです.
#include <stdio.h> #include <stdlib.h> typedef struct Person { char *name; } Person; typedef struct Student { struct Person base; int grade; } Student; typedef struct Teacher { struct Person base; char *subject; } Teacher; Student *new_student(char *name, int grade) { Student *n = malloc(sizeof(Student)); ((Person *)n)->name = name; n->grade = grade; return n; } Teacher *new_teacher(char *name, char *subject) { Teacher *n = malloc(sizeof(Teacher)); ((Person *)n)->name = name; n->subject = subject; return n; } int main(void) { Student *john = new_student("john", 4); Teacher *garin = new_teacher("garin", "English"); printf("%s %s\n", ((Person *)john)->name, ((Person *)garin)->name); printf("%s\n", john == &john->base ? "john == &john->base" : "john != &john->base"); }
実行結果
john garin john == &john->base
以上のように,構造体(子オブジェクト)の一番最初のメンバに継承させたいオブジェクト(親オブジェクト)の変数を持ってくれば継承が実現できます.
子オブジェクトへのポインタの値 == 親オブジェクトへのポインタの値が常に成り立つため(ここではjohn == &john->base
),親オブジェクトへのアップキャストが可能になります.
自己紹介をさせよう
先生と生徒で合計6人いる教室の全員に自己紹介をさせます.
教室にいる人は先生と生徒なので(つまり人間),自己紹介させるプログラムは以下のようになるはずです.
void introduce(Person *person); int main(void) { Person *class_member[6] = { (Person *)new_student("john", 4), (Person *)new_teacher("garin", "English"), (Person *)new_student("takashi", 1), (Person *)new_teacher("hoge", "math"), (Person *)new_student("fuga", 6), (Person *)new_student("piyo", 2) }; for(int i = 0; i < 6; i++) { introduce(class_member[i]); } return 0; }
ここからintroduce
関数を2つのやり方で実装していきます.
①Person
構造体に生徒か先生かの情報を持たせる
生徒か先生かという情報を定義して,
enum PersonType {
TYPE_STUDENT,
TYPE_TEACHER,
};
Person
構造体のメンバに追加します.
typedef struct Person { char *name; enum PersonType type; } Person;
そして,各コンストラクタでtype
の初期化を行わせます.
Student *new_student(char *name, int grade) { Student *n = malloc(sizeof(Student)); ((Person *)n)->name = name; ((Person *)n)->type = TYPE_STUDENT; // here n->grade = grade; return n; } Teacher *new_teacher(char *name, char *subject) { Teacher *n = malloc(sizeof(Teacher)); ((Person *)n)->name = name; ((Person *)n)->type = TYPE_TEACHER; // here n->subject = subject; return n; }
introduce
では素直に生徒か先生かを見て出力を変えればOKです.
void introduce(Person *person) { printf("My name is %s. ", person->name); switch(person->type) { case TYPE_STUDENT: printf("%dth student.\n", ((Student *)person)->grade); break; case TYPE_TEACHER: printf("%s teacher.\n", ((Teacher *)person)->subject); break; default: puts("space alien"); } }
出力結果
My name is john. 4th student. My name is garin. English teacher. My name is takashi. 1th student. My name is hoge. math teacher. My name is fuga. 6th student. My name is piyo. 2th student.
②関数ポインタを使う
Person
構造体に関数ポインタをもたせます.introduce
は引数にPerson *
を取り,戻り値はvoid
なので,
typedef struct Person { char *name; void (*introduce)(struct Person *); } Person;
となります.
次に,先生と生徒にそれぞれ自己紹介させる関数を実装します.
void intro_student(Person *student) { printf("My name is %s. %dth student.\n", student->name, ((Student *)student)->grade); } void intro_teacher(Person *teacher) { printf("My name is %s. %s teacher.\n", teacher->name, ((Teacher *)teacher)->subject); }
そして,各コンストラクタでintroduce
を初期化します.
Student *new_student(char *name, int grade) { Student *n = malloc(sizeof(Student)); ((Person *)n)->name = name; ((Person *)n)->introduce = intro_student; // here n->grade = grade; return n; } Teacher *new_teacher(char *name, char *subject) { Teacher *n = malloc(sizeof(Teacher)); ((Person *)n)->name = name; ((Person *)n)->introduce = intro_teacher; // here n->subject = subject; return n; }
この変更に伴い,main
関数でのintroduce
の呼び出しは,
for(int i = 0; i < 6; i++) { class_member[i]->introduce(class_member[i]); }
のようになります.
出力結果
My name is john. 4th student. My name is garin. English teacher. My name is takashi. 1th student. My name is hoge. math teacher. My name is fuga. 6th student. My name is piyo. 2th student.
C言語はいいぞ.
いかがでしたか?