#include "namedPoint.h"

#include <stdlib.h>

struct NamedPoint {

 double x,y;

 char* name;

};

struct NamedPoint* makeNamedPoint(double x, double y, char* name) {

 struct NamedPoint* p = malloc(sizeof(struct NamedPoint));

 p->x = x;

 p->y = y;

 p->name = name;

 return p;

}

void setName(struct NamedPoint* np, char* name) {

 np->name = name;

}

char* getName(struct NamedPoint* np) {

 return np->name;

}

<b>main.c</b>

#include &quot;point.h&quot;

#include &quot;namedPoint.h&quot;

#include &lt;stdio.h&gt;

int main(int ac, char** av) {

 struct NamedPoint* origin = makeNamedPoint(0.0, 0.0, &quot;origin&quot;);

 struct NamedPoint* upperRight = makeNamedPoint

 (1.0, 1.0, &quot;upperRight&quot;);

 printf(&quot;distance=%f\n&quot;,

 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 &lt;stdio.h&gt;

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 &quot;file.h&quot;

void open(char* name, int mode) {/*…*/}

void close() {/*…*/};

int read() {int c;/*…*/ return c;}