Добавить в цитаты Настройки чтения

Страница 10 из 20



#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=%fn&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;}

15

И продолжает применяться.

16

В разных версиях UNIX требования разные; это всего лишь пример.