상속 관계는 to-be 또는 is-a 관계이다. 기존의 객체나 클래스에 속성과 행위를 추가한다는 의미에서 확장 관계(extension relationship)이라고도 한다.
코드 박스 8-1 Person 클래스와 Student 클래스 속성 구조체
typedef struct {
char first_name[32];
char last_name[32];
unsigned int birth_year;
} person_t;
typedef struct {
char first_name[32];
char last_name[32];
unsigned int birth_year;
char student_number[16]; // additional attribute
unsigned int passed_credits; // additional attribute
} student_t;
위 코드에서 student_t는 person_t의 속성 집합을 확장하고 있다. person_t는 일반적으로 슈퍼타입(supertype) 또는 베이스 타입(base type), 또는 부모 타입(parent type)이라고 한다. student_t는 자식 타입(child type) 또는 상속받은 서브타입(inherited subtype)이라고 한다.
상속 관계는 본질적으로 서브타입 객체가 슈퍼타입 객체를 비공개 속성으로 갖고 있는 일대일 합성 관계이다.
코드 박스 8-2 Person과 Student 클래스 속성 구조체(중첩됨)
typedef struct {
char first_name[32];
char last_name[32];
unsigned int birth_year;
} person_t;
typedef struct {
person_t person;
char student_number[16]; // additional attribute
unsigned int passed_credits; // additional attribute
} student_t;
구조체 중첩을 사용하면 student_t 포인터를 person_t로 변환하는 업캐스팅(upcasting)을 쉽게 수행할 수 있다.
코드 박스 8-3 [예제 8-1] Student와 Person의 객체 포인터 사이의 업캐스팅
#include <stdio.h>
typedef struct {
char first_name[32];
char last_name[32];
unsigned int birth_year;
} person_t;
typedef struct {
person_t person;
char student_number[16]; // additional attribute
unsigned int passed_credits; // additional attribute
} student_t;
int main(int argc, char** argv) {
student_t s;
student_t* s_ptr = &s;
person_t* p_ptr = (person_t*) &s;
printf("Student pointer points to %p\n", (void*) s_ptr);
printf("Person pointer points to %p\n", (void*) p_ptr);
return 0;
}
셀 박스 8-1 [예제 8-1]의 출력 결과
$ gcc 8_1.c -o 8_1.out
$ ./8_1.out
Student pointer points to 0x7ffeb12e9bc0
Person pointer points to 0x7ffeb12e9bc0
s_ptr, p_ptr은 같은 주소를 가리킨다. 즉, student_t 형 구조체 변수의 메모리 레이아웃에서 person_t 구조체를 상속하고 있다. 그러므로 student 객체를 가리키는 포인터로 Person 클래스의 행위를 재사용할 수 있다.
코드 박스 8-4 컴파일되지 않는 상속 관계 만들기
struct person_t;
typedef struct {
struct person_t person;
char student_number[16];
unsigned int passed_credits;
} student_t;
불완전한 형식(incomplete type)으로 변수를 생성할 수 없다. 그러므로 상속 구현을 위해 서브타입 구조체는 슈퍼타입 정의를 알아야 한다. 하지만 슈퍼타입 구조체의 정의는 비공개여야 하기 때문에 다음 두 가지 접근법이 사용된다.
첫 번째는 자식의 속성 구조체 내부에 부모의 구조체 변수를 두는 방법이다.
코드 박스 8-5 [예제 8-2] Person 클래스의 공용 인터페이스
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_2_PERSON_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_2_PERSON_H
// forward declaration
struct person_t;
// memory allocator
struct person_t* person_new();
// constructor
void person_constructor(struct person_t*,
const char* /* first name */,
const char* /* last name*/,
unsigned int /* birth year */);
// destructor
void person_destructor(struct person_t*);
// behavior function
void person_get_first_name(struct person_t*, char*);
void person_get_last_name(struct person_t*, char*);
unsigned int person_get_birth_year(struct person_t*);
#endif
속성 구조체 person_t는 불완전하기 때문에 Student 클래스는 이 헤더 파일로 상속 관계를 구현할 수 없다. 하지만 이 헤더 파일에 구조체 정의를 포함하면 목적을 넘어서는 행위이기 때문에, 비공개 헤더 파일을 사용해야 한다.
코드 박스 8-6 [예제 8-2] person_t의 실제 정의를 포함하는 비공개 헤더 파일
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_2_PERSON_P_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_2_PERSON_P_H
// private definition
typedef struct {
char first_name[32];
char last_name[32];
unsigned int birth_year;
} person_t;
#endif
이 헤더 파일은 Person 클래스의 일부이므로 비공개 상태여야 하지만 제한적으로 Student 클래스에는 공개되어야 한다.
코드 박스 8-7 [예제 8-2] Person 클래스에 대한 정의
#include <stdlib.h>
#include <string.h>
// this header file defines person_t structure
#include "8_2_person_p.h"
// memory allocator
person_t* person_new() {
return (person_t*) malloc(sizeof(person_t));
}
// constructor
void person_constructor(person_t* person,
const char* first_name,
const char* last_name,
unsigned int birth_year) {
strcpy(person->first_name, first_name);
strcpy(person->last_name, last_name);
person->birth_year = birth_year;
}
// destructor
void person_destructor(person_t* person) {
// no need to do anything
}
// behavior function
void person_get_first_name(person_t* person, char* buffer) {
strcpy(buffer, person->first_name);
}
void person_get_last_name(person_t* person, char* buffer) {
strcpy(buffer, person->last_name);
}
unsigned int person_get_birth_year(person_t* person) {
return person->birth_year;
}
코드 박스 8-8 [예제 8-2] Student 클래스의 공용 인터페이스
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_2_STUDENT_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_2_STUDENT_H
// forward declaration
struct student_t;
// memory allocator
struct student_t* student_new();
// constructor
void student_constructor(struct student_t*,
const char* /* first name */,
const char* /* last name */,
unsigned int /* birth year */,
const char* /* student number */,
unsigned int /* passed credits */);
// destructor
void student_destructor(struct student_t*);
// behavior function
void student_get_student_number(struct student_t*, char*);
unsigned int student_get_passed_credits(struct student_t*);
#endif
student 생성자는 student 내 person 속성을 설정할 책임이 있다. student 객체에서도 Person 클래스의 행위 함수를 사용할 수 있기 때문에 Student 클래스에는 2개의 행위 함수만 정의되어 있다.
코드 박스 8-9 [예제 8-2] Student 클래스의 비공개 정의
#include <stdlib.h>
#include <string.h>
#include "8_2_person.h"
// this header file defines person_t structure
#include "8_2_person_p.h"
// forward declaration
typedef struct {
// at this point all attributes of the person class are inherited
person_t person;
char* student_number;
unsigned int passed_credits;
} student_t;
// memory allocator
student_t* student_new() {
return (student_t*) malloc(sizeof(student_t));
}
// constructor
void student_constructor(student_t* student,
const char* first_name,
const char* last_name,
unsigned int birth_year,
const char* student_number,
unsigned int passed_credits) {
// call the constructor of the parent class
person_constructor((struct person_t*) student, first_name, last_name, birth_year);
student->student_number = (char*) malloc(16 * sizeof(char));
strcpy(student->student_number, student_number);
student->passed_credits = passed_credits;
}
// destructor
void student_destructor(student_t* student) {
// needed to destruct the child object
free(student->student_number);
person_destructor((struct person_t*) student);
}
// behavior function
void student_get_student_number(student_t* student, char* buffer) {
strcpy(buffer, student->student_number);
}
unsigned int student_get_passed_credits(student_t* student) {
return student->passed_credits;
}
student_t 구조체 정의 시 반드시 person_t 구조체 변수를 가장 먼저 선언해야 한다. 그래야만 업캐스팅 후 객체 사용 시 같은 시작 주소로부터 person_t 객체의 범위만큼 접근할 수 있다.
코드 박스 8-10 [예제 8-2]의 메인 시나리오
#include <stdio.h>
#include <stdlib.h>
#include "8_2_person.h"
#include "8_2_student.h"
int main(int argc, char** argv) {
// create and construct a student object
struct student_t* student = student_new();
student_constructor(student, "John", "Doe", 1987, "TA5667", 134);
// call person's behavior function to read its attributes from the student object
char buffer[32];
// upcast to the parent type's pointer
struct person_t* person_ptr = (struct person_t*) student;
person_get_first_name(person_ptr, buffer);
printf("First name: %s\n", buffer);
person_get_last_name(person_ptr, buffer);
printf("Last name: %s\n", buffer);
printf("Birth year: %d\n", person_get_birth_year(person_ptr));
// read the attributes limited to the student object
student_get_student_number(student, buffer);
printf("Student number: %s\n", buffer);
printf("Passed credits: %d\n", student_get_passed_credits(student));
// destruct and free the student object
student_destructor(student);
free(student);
return 0;
}
Person, Student 클래스 모두에 대한 공용 인터페이스를 포함했지만, student 객체 하나만 생성하여 해당 객체를 통해 person 객체의 행위에 접근한다.
셀 박스 8-2 [예제 8-2]를 빌드하고 실행하기
$ gcc -c 8_2_person.c -o person.o
$ gcc -c 8_2_student.c -o student.o
$ gcc -c 8_2_main.c -o main.o
$ gcc person.o student.o main.o -o 8_2.out
$ ./8_2.out
First name: John
Last name: Doe
Birth year: 1987
Student number: TA5667
Passed credits: 134
두 번째는 자식의 속성 구조체 내부에 부모의 구조체 변수에 대한 포인터를 두는 방법이다.
이 방식에서 Student 클래스는 Person 클래스의 공용 인터페이스에만 의존할 뿐, 비공개 정의에는 의존하지 않는다. 클래스가 분리(decouple)되기 때문에 자식 클래스의 구현을 변경하지 않고 부모 클래스의 구현을 유연하게 변경할 수 있다.
코드 박스 8-11 [예제 8-3] Student 클래스의 새로운 공용 인터페이스
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_3_STUDENT_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_3_STUDENT_H
// forward declaration
struct student_t;
// memory allocator
struct student_t* student_new();
// constructor
void student_constructor(struct student_t*,
const char* /* first name */,
const char* /* last name */,
unsigned int /* birth year */,
const char* /* student number */,
unsigned int /* passed credits */);
// destructor
void student_destructor(struct student_t*);
// behavior function
void student_get_first_name(struct student_t*, char*);
void student_get_last_name(struct student_t*, char*);
unsigned int student_get_birth_year(struct student_t*);
void student_get_student_number(struct student_t*, char*);
unsigned int student_get_passed_credits(struct student_t*);
#endif
student_t 포인터를 person_t 포인터로 업캐스팅할 수 없기 때문에 Student 클래스는 Person 클래스에서 선언한 모든 행위 함수를 반복하도록 행위 함수를 수정해야 한다.
코드 박스 8-12 [예제 8-3] Person 클래스의 새로운 구현
#include <stdlib.h>
#include <string.h>
// private definition
typedef struct {
char first_name[32];
char last_name[32];
unsigned int birth_year;
} person_t;
// memory allocator
person_t* person_new() {
return (person_t*) malloc(sizeof(person_t));
}
// constructor
void person_constructor(person_t* person,
const char* first_name,
const char* last_name,
unsigned int birth_year) {
strcpy(person->first_name, first_name);
strcpy(person->last_name, last_name);
person->birth_year = birth_year;
}
// destructor
void person_destructor(person_t* person) {
// no need to do anything
}
// behavior function
void person_get_first_name(person_t* person, char* buffer) {
strcpy(buffer, person->first_name);
}
void person_get_last_name(person_t* person, char* buffer) {
strcpy(buffer, person->last_name);
}
unsigned int person_get_birth_year(person_t* person) {
return person->birth_year;
}
person_t의 비공개 정의가 소스 파일 내부에 있으므로 더 이상 비공개 헤더 파일은 사용되지 않는다. 이는 다른 클래스로 정의를 공유하지 않고 은닉하겠다는 의미이다.
코드 박스 8-13 [예제 8-3] Student 클래스의 새로운 구현
#include <stdlib.h>
#include <string.h>
// public interface for person class
#include "8_3_person.h"
// forward declaration
typedef struct {
char* student_number;
unsigned int passed_credits;
// at this line, a pointer is needed as person_t is the incomplete type
struct person_t* person;
} student_t;
// memory allocator
student_t* student_new() {
return (student_t*) malloc(sizeof(student_t));
}
// constructor
void student_constructor(student_t* student,
const char* first_name,
const char* last_name,
unsigned int birth_year,
const char* student_number,
unsigned int passed_credits) {
// allocate memory for the parent object
student->person = person_new();
person_constructor(student->person, first_name, last_name, birth_year);
student->student_number = (char*) malloc(16 * sizeof(char));
strcpy(student->student_number, student_number);
student->passed_credits = passed_credits;
}
// destructor
void student_destructor(student_t* student) {
// at first, need to destruct the child object
free(student->student_number);
// and then, call the destructor of the parent class
person_destructor(student->person);
// finally, release the memory allocated for the parent object
free(student->person);
}
// behavior function
void student_get_first_name(student_t* student, char* buffer) {
// need to call person's behavior function
person_get_first_name(student->person, buffer);
}
void student_get_last_name(student_t* student, char* buffer) {
// need to call person's behavior function
person_get_last_name(student->person, buffer);
}
unsigned int student_get_birth_year(student_t* student) {
// need to call person's behavior function
return person_get_birth_year(student->person);
}
void student_get_student_number(student_t* student, char* buffer) {
strcpy(buffer, student->student_number);
}
unsigned int student_get_passed_credits(student_t* student) {
return student->passed_credits;
}
Student 클래스의 생성자에서는 부모 클래스의 생성자를 호출하고, 소멸자에서는 부모 클래스의 소멸자를 호출한다. 즉, 합성 관계와 같이 컨테이너 객체가 포함된 객체의 생명주기를 제어한다.
Student 클래스는 상속받은 속성 및 비공개 속성을 노출하기 위해 자신의 행위 함수들을 노출해야 한다. 이때 부모인 person 객체의 비공개 속성을 노출하기 위해 래퍼(wrapper) 함수를 노출한다.
코드 박스 8-14 [예제 8-3]의 메인 시나리오
#include <stdio.h>
#include <stdlib.h>
#include "8_3_student.h"
int main(int argc, char** argv) {
// create and construct a student object
struct student_t* student = student_new();
student_constructor(student, "John", "Doe", 1987, "TA5667", 134);
// need to call Student's behavior function as the student's pointer cannot be casted to the person's pointer
char buffer[32];
student_get_first_name(student, buffer);
printf("First name: %s\n", buffer);
student_get_last_name(student, buffer);
printf("Last name: %s\n", buffer);
printf("Birth year: %d\n", student_get_birth_year(student));
student_get_student_number(student, buffer);
printf("Student number: %s\n", buffer);
printf("Passed credits: %d\n", student_get_passed_credits(student));
// destruct and release the student object
student_destructor(student);
free(student);
return 0;
}
student_t, person_t 포인터를 상호 변환할 수 없기 때문에 main 함수에서 더이상 Person의 공용 인터페이스를 사용할 수 없다. 그러므로 이를 포함하지 않는다.
셀 박스 8-3 [예제 8-3]을 빌드하고 실행하기
$ gcc -c 8_3_person.c -o person.o
$ gcc -c 8_3_student.c -o student.o
$ gcc -c 8_3_main.c -o main.o
$ gcc person.o student.o main.o -o 8_3.out
$ ./8_3.out
First name: John
Last name: Doe
Birth year: 1987
Student number: TA5667
Passed credits: 134
다형성은 같은 공용 인터페이스(또는 행위 함수의 집합)를 통해 상이한 행위를 갖는다는 의미이다.
코드 박스 8-15 Animla, Cat, Duck 자료형에 대한 객체 세 개를 생성하기
struct animal_t* animal = animal_malloc();
animal_constructor(animal);
struct cat_t* cat = cat_malloc();
cat_constructor(cat);
struct duck_t* duck = duck_malloc();
duck_constructor(duck);
다형성이 없다면 세 클래스가 공유하는 sound라는 함수는 다음과 같이 호출되었을 것이다.
코드 박스 8-16 생성된 객체에서 행위 함수를 호출하기
animal_sound(animal);
cat_sound(cat);
duck_sound(duck);
셀 박스 8-4 함수 호출에 대한 출력 결과
Animal: Beeep
Cat: Meow
Duck: Quack
다음은 다형성이 있을 때의 코드 호출 방식이다.
코드 박스 8-17 세 개의 객체 모두에서 같은 행위 함수 sound를 호출하기
animal_sound(animal);
animal_sound((struct animal_t*) cat);
animal_sound((struct animal_t*) duck);
셀 박스 8-5 함수 호출에 대한 출력 결과
Animal: Beeep
Cat: Meow
Duck: Quack
다형성 메커니즘을 활용하려면 C에서 상속을 구현하는 첫 번째 접근법을 취해야 한다.
코드 박스 8-18 Animal, Cat, Duck 클래스의 속성 구조체 정의
typedef struct {
...
} animal_t;
typedef struct {
animal_t animal;
...
} cat_t;
typedef struct {
animal_t animal;
...
} duck_t;
duck_t, cat_t 포인터는 각각 animal_t 포인터로 변환할 수 있다. 이후 두 자식 클래스에 대한 같은 행위 함수를 사용할 수 있다.
코드 박스 8-19 아직 다형적이지 않은 animal_sound
void animal_sound(animal_t* ptr) {
printf("Animal: Beeeep");
}
animal_sound(animal);
animal_sound((struct animal_t*) cat);
animal_sound((struct animal_t*) duck);
셀 박스 8-6 [코드 박스 8-19]의 함수 호출에 대한 출력 결과
Animal: Beeep
Animal: Beeep
Animal: Beeep
위와 같은 구현은 다형성을 활용한다고 할 수 없다.
다형성이 필요한 이유는 어떤 슈퍼타입에 관한 여러 서브타입을 둘 때 코드는 그대로 두기를 원하기 때문이다. 다형성은 변경이 필요할 때 그 범위를 좁히는 역할을 한다.
추상화(abstraction) 또한 다형성이 필요한 이유 중 하나이다. 추상적인 클래스는 자식 클래스에서 오버라이딩 되어야 하는 추상적인 행위를 갖는다. 다형성은 이를 구현하기 위한 핵심 방법이다.
다형적 행위를 구현하기 위해 함수 포인터(function pointer)를 속성 구조체의 필드로 둘 수 있다.
코드 박스 8-20 [예제 8-4] Animal 클래스의 공용 인터페이스
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_4_ANIMAL_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_4_ANIMAL_H
// forward declaration
struct animal_t;
// memory allocator
struct animal_t* animal_new();
// constructor
void animal_constructor(struct animal_t*);
// destructor
void animal_destructor(struct animal_t*);
// behavior function
void animal_get_name(struct animal_t*, char*);
void animal_sound(struct animal_t*);
#endif
Animal 클래스는 두 개의 행위 함수를 갖는다. animal_sound 함수만 다형성이 필요하고 자식 클래스에서 오버라이딩될 수 있다. animal_get_name은 그렇지 않다.
코드 박스 8-21 [예제 8-4] Animal 클래스의 비공개 헤더
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_4_ANIMAL_P_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_4_ANIMAL_P_H
// a function pointer type needed to point the other morphs of animal_sound function
typedef void (*sound_func_t)(void*);
// forward declaration
typedef struct {
char* name;
// this member is a pointer to the function which behaves sound function
sound_func_t sound_func;
} animal_t;
#endif
모든 자식 클래스는 animal_sound 함수에 대한 자신만의 버전을 제공할 수 있다. 그러므로 animal_t의 각 인스턴스는 animal_sound 행위 전용 함수 포인터를 갖고, 그 포인터나 클래스 내 다형적 함수에 대한 실제 정의를 가리킨다.
코드 박스 8-22 [예제 8-4] Animal 클래스에 대한 정의
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "8_4_animal_p.h"
// the basic definition of animal_sound at the level of the parent
void __animal_sound(void* this_ptr) {
animal_t* animal = (animal_t*) this_ptr;
printf("%s: Beeeep\n", animal->name);
}
// memory allocator
animal_t* animal_new() {
return (animal_t*) malloc(sizeof(animal_t));
}
// constructor
void animal_constructor(animal_t* animal) {
animal->name = (char*) malloc(10 * sizeof(char));
strcpy(animal->name, "Animal");
// set the function pointer to point to the basic definition
animal->sound_func = __animal_sound;
}
// destructor
void animal_destructor(animal_t* animal) {
free(animal->name);
}
// behavior function
void animal_get_name(animal_t* animal, char* buffer) {
strcpy(buffer, animal->name);
}
void animal_sound(animal_t* animal) {
// call the function, which is pointed by the function pointer
animal->sound_func(animal);
}
다형적 행위는 animal_sound 함수 내부에서 발생한다. 서브 클래스가 오버라이딩하지 않는다면 비공개 함수 __animal_sound가 기본 행위가 되어야 한다.
코드 박스 8-23 [예제 8-4] Cat 클래스의 공용 인터페이스
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_4_CAT_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_4_CAT_H
// forward declaration
struct cat_t;
// memory allocator
struct cat_t* cat_new();
// constructor
void cat_constructor(struct cat_t*);
// destructor
void cat_destructor(struct cat_t*);
// all behavior functions are inherited from the animal class
#endif
코드 박스 8-24 [예제 8-4] Cat 클래스의 비공개 구현
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "8_4_animal.h"
#include "8_4_animal_p.h"
typedef struct {
animal_t animal;
} cat_t;
// define a new behavior function for the sound of a cat
void __cat_sound(void* ptr) {
animal_t* animal = (animal_t*) ptr;
printf("%s: Meow\n", animal->name);
}
// memory allocator
cat_t* cat_new() {
return (cat_t*) malloc(sizeof(cat_t));
}
// constructor
void cat_constructor(cat_t* cat) {
animal_constructor((struct animal_t*) cat);
strcpy(cat->animal.name, "Cat");
// sound_func points to the new behavior function and this is the line where the overriding occurs
cat->animal.sound_func = __cat_sound;
}
// destructor
void cat_destructor(cat_t* cat) {
animal_destructor((struct animal_t*) cat);
}
코드 박스 8-25 [예제 8-4] Duck 클래스의 공용 인터페이스
#ifndef EXTREME_C_EXAMPLES_CHAPTER_8_4_DUCK_H
#define EXTREME_C_EXAMPLES_CHAPTER_8_4_DUCK_H
// forward declaration
struct duck_t;
// memory allocator
struct duck_t* duck_new();
// constructor
void duck_constructor(struct duck_t*);
// destructor
void duck_destructor(struct duck_t*);
// all behavior functions are inherited from the animal class
#endif
코드 박스 8-26 [예제 8-4] Duck 클래스의 비공개 구현
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "8_4_animal.h"
#include "8_4_animal_p.h"
typedef struct {
animal_t animal;
} duck_t;
// define a new behavior function for the sound of a duck
void __duck_sound(void* ptr) {
animal_t* animal = (animal_t*) ptr;
printf("%s: Quacks\n", animal->name);
}
// memory allocator
duck_t* duck_new() {
return (duck_t*) malloc(sizeof(duck_t));
}
// constructor
void duck_constructor(duck_t* duck) {
animal_constructor((struct animal_t*) duck);
strcpy(duck->animal.name, "Duck");
// sound_func points to the new behavior function and this is the line where the overriding occurs
duck->animal.sound_func = __duck_sound;
}
// destructor
void duck_destructor(duck_t* duck) {
animal_destructor((struct animal_t*) duck);
}
위와 같이 부모 객체가 갖고 있는 sound_func 포인터를 자식 객체의 행위 함수에 대한 포인터로 설정하여 오버라이딩을 구현한다. 이것이 C++에서 다형성이 도입된 방식이다.
코드 박스 8-27 [예제 8-4]의 메인 시나리오
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// include only public interfaces
#include "8_4_animal.h"
#include "8_4_cat.h"
#include "8_4_duck.h"
int main(int argc, char** argv) {
struct animal_t* animal = animal_new();
struct cat_t* cat = cat_new();
struct duck_t* duck = duck_new();
animal_constructor(animal);
cat_constructor(cat);
duck_constructor(duck);
animal_sound(animal);
animal_sound((struct animal_t*) cat);
animal_sound((struct animal_t*) duck);
animal_destructor(animal);
cat_destructor(cat);
duck_destructor(duck);
free(duck);
free(cat);
free(animal);
return 0;
}
main 함수는 Animal, Cat, Duck 클래스의 공용 인터페이스에만 의존하고 구현에는 의존하지 않는다.
셀 박스 8-7 [예제 8-4]의 컴파일, 실행과 출력 결과
$ gcc -c 8_4_animal.c -o animal.o
$ gcc -c 8_4_cat.c -o cat.o
$ gcc -c 8_4_duck.c -o duck.o
$ gcc -c 8_4_main.c -o main.o
$ gcc animal.o cat.o duck.o main.o -o 8_4.out
$ ./8_4.out
Animal: Beeeep
Cat: Meow
Duck: Quacks
가상 함수(virtual function)는 자식 클래스에서 오버라이딩할 수 있는 행위 함수이다. C++ 같은 언어에서는 특정 함수가 다형적인 함수임을 나타내는 특별한 키워드를 사용하여 가상 함수를 지정한다. 이는 컴파일러가 추적하여 오버라이딩 시 실제 정의를 가리킬 수 있게 해야 한다.