>#include "namedPoint.h"
>#include
>int main(int ac, char** av) {
> struct NamedPoint* origin = makeNamedPoint(0.0, 0.0, "origin");
> struct NamedPoint* upperRight = makeNamedPoint
> (1.0, 1.0, "upperRight");
> printf("distance=%f\n",
> distance(
> (struct Point*) origin,
> (struct Point*) upperRight));
>}
Внимательно рассмотрев основной код в файле >main.c
, можно заметить, что структура данных >NamedPoint
используется, как если бы она была производной от структуры >Point
. Такое оказалось возможным потому, что первые два поля в >NamedPoint
совпадают с полями в >Point
. Проще говоря, >NamedPoint
может маскироваться под >Point
, потому что >NamedPoint
фактически является надмножеством >Point
и имеет члены, соответствующие структуре >Point
, следующие в том же порядке.
Этот прием широко применялся[15] программистами до появления ОО. Фактически именно так C++ реализует единственное наследование.
То есть можно сказать, что некоторая разновидность наследования у нас имелась задолго до появления языков ОО. Впрочем, это утверждение не совсем истинно. У нас имелся трюк, хитрость, не настолько удобный, как настоящее наследование. Кроме того, с помощью описанного приема очень сложно получить что-то похожее на множественное наследование.
Обратите также внимание, как в >main.c
мне пришлось приводить аргументы >NamedPoint
к типу >Point
. В настоящем языке ОО такое приведение к родительскому типу производится неявно.
Справедливости ради следует отметить, что языки ОО действительно сделали маскировку структур данных более удобной, хотя это и не совсем новая особенность.
Итак, мы не можем дать идее ОО ни одного очка за инкапсуляцию и можем дать лишь пол-очка за наследование. Пока что общий счет не впечатляет.
Но у нас есть еще одно понятие.
Полиморфизм?
Была ли возможность реализовать полиморфное поведение до появления языков ОО? Конечно! Взгляните на следующую простую программу copy на языке C.
>#include
>void copy() {
> int c;
> while ((c=getchar())!= EOF)
> putchar(c);
>}
Функция >getchar()
читает символы из >STDIN
. Но какое устройство в действительности скрыто за ширмой >STDIN
? Функция >putchar()
записывает символы в устройство >STDOUT
. Но что это за устройство? Эти функции являются полиморфными – их поведение зависит от типов устройств >STDIN
и >STDOUT
.
В некотором смысле >STDIN
и >STDOUT
похожи на интерфейсы в силе Java, когда для каждого устройства имеется своя реализация этих интерфейсов. Конечно, в примере программы на C нет никаких интерфейсов, но как тогда вызов >getchar()
передается драйверу устройства, который фактически читает символ?
Ответ на этот вопрос прост: операционная система UNIX требует, чтобы каждый драйвер устройства ввода/вывода реализовал пять стандартных функций[16]: >open
, >close
, >read
, >write
и >seek
. Сигнатуры этих функций должны совпадать для всех драйверов.
Структура FILE имеет пять указателей на функции. В нашем случае она могла бы выглядеть как-то так:
>struct FILE {
> void (*open)(char* name, int mode);
> void (*close)();
> int (*read)();
> void (*write)(char);
> void (*seek)(long index, int mode);
>};
Драйвер консоли определяет эти функции и инициализирует указатели на них в структуре FILE примерно так:
>#include "file.h"
>void open(char* name, int mode) {/*…*/}
>void close() {/*…*/};
>int read() {int c;/*…*/ return c;}
>void write(char c) {/*…*/}
>void seek(long index, int mode) {/*…*/}
>struct FILE console = {open, close, read, write, seek};
Если теперь предположить, что символ >STDIN
определен как указатель >FILE*
и ссылается на структуру console, тогда