Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
267
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать
j-f.
в TOM случае, если режим порождения является
// общедоступное наследование

680 Часть

Такое поведение возможно только общедоступным.

struct Faculty : public Person {

private:

 

 

// только для преподавателей

char* rank;

 

 

public:

 

char icl[], const char nm[], const char г []);

Faculty(const

void write ()

const;

// отображение записи

-FacultyO;

}

;

// возвращение памяти динамически

struct Student

: public Person {

// распределяемой области памяти

// общедоступное наследование

private:

 

 

char* major;

 

 

// только для студентов

public:

 

 

Student(const char id[], const

char nm[], const char m[]);

void write () const;

// отображение записи

-StudentO;

}

;

 

 

 

// возвращение памяти динамически

 

 

 

// распределяемой области памяти

Производные классы Faculty и Student наследуют все элементы данных своего

базового класса Person. Они определяют свои собственные данные, конкретные для каждого вида Person (rank или major).

Конструкторы производных классов Faculty и Student принимают параметры, необходимые для инициализации всех своих полей, независимо от того, опреде­ лены ли они впроизводном классе или унаследованы из базового класса Person. Задача конструктора производного класса состоитв передачеданных конструктору

базового класса в списке инициализации. Как можно установить из интерфейса конструктора Person, к ним относятся спецификации вида создаваемых объектов.

Faculty или Student.Для многих программистов это означает,что список пара­ метров конструкторов производных классовдолжен включатьданные для инициа­ лизации базовой части (три параметра) и данные для инициализации производной части (major для Student, rank для Faculty).

Faculty(const char id[], const char nm[], Kind k, const char r[])

: Person(id,nm, k)

//список инициализации

{ rank = new char[strlen(r)+l];

if (rank == 0) {cout «

"Out of memory\n"; exit(O); }

strcpy(rank,r); }

 

Это типичный пример делегирования обязанности в клиентскую часть производ­ ного класса. Клиентская программа (функция readO) создает объекты Faculty примерно так:

person = new Faculty(id,name,FACULTY,buf); }

// объектом является Faculty

H o это обман, клиентская программа уже объявила о создании объекта Faculty.

Зачем делать бесполезную работу по передаче параметров, объявляя это как

Faculty? Подобная обязанность должна быть передана объекту Faculty. О н

знает, что он является Faculty, и должен сказать об этом своей части Person, не втягивая клиента read() в цикл совместной работы.

Faculty(const char id[], const char nm[],

const char r[])

: Person (id, nm,FACULTY)

// именно в этом суть ООП

{ rank = newchar[strlen(r)+l];

 

if (rank == 0) {cout « "Out ofmemory\n"; exit(O); } strcpy(rank,r); }

Глава 15 • Виртуальные функции и использование наследования

681

Теперь клиентская программа должна выполнить более простую работу.

person = new Faculty(id,name,buf); }

/ /

объектом является

Faculty

Обратите внимание, что прототипы

конструктора

в спецификациях

Faculty

и Student содержат три параметра, а не четыре. В этом состоит суть объектноориентированного программирования: правильное распределение обязанностей между взаимодействующими классами.

Деструкторы производных классов возвращают память, выделенную в динами­ чески распределяемой области памяти конструкторами (rank для Faculty, major для Student).

Функция-член write() реализуется в обоих производных классах. Они подоб­ ны, поэтому их имена могут быть одинаковыми. Необходимо реализовать write() в основном классе. Но алгоритмы для различных видов Person не идентичны. Именно поэтому для каждого класса существует отдельная функция, но они ис­ пользуют одно и то же имя. По существу эти функции называются полиморфными.

Функции write() представляют часть функциональных возможностей функции mainO из предыдущего варианта. Поскольку каждый производный класс Faculty и Student знает свои возможности, вам не надо выяснять вид целевого объекта.

void Faculty::write

()

const

 

/ /

отображение

записи

 

{

cout

«

"

i d : " «

id

 

«

endl;

/ /

вывод на печать идентификатора,

имени

 

cout

«

"

name: " «

 

name" «

endl

;

 

 

 

 

 

cout

«

"

rank: " «

 

rank «endl «endl; - }

/ /

только для преподавателей

void Student::write

()

const

 

/ /

отображение

записи

 

{

cout

«

"

i d : «

"id

 

«

endl;

/ /

вывод на печать идентификатора,

имени

 

cout

«

"

name: " «

 

name «

endl;

 

 

 

 

 

 

cout

«

"

major:

" «

 

major «endl

« e n d l ; }

/ /

только для студента

 

Глобальная функция readO представляет модернизированную модификацию функции из предыдущего варианта программы. Она считывает данные из входного файла в локальные массивы, а затем проверяет массив kind[], чтобы видеть, объект какого типа требуется построить. Если она сообщает "FACULTY", то функ­ ция readO создает новый объект Faculty с помощью оператора new. Если она сообщает "STUDENT", read() задает новый объект Student с помощью оператора new. В любом случае данные отправляются конструктору класса как параметры.

void

read (ifstream& f,

. . . ?? person)

{ char kind[8], id[10]

, name[80], buf[80];

f .getline(kind.80);

 

f. getline(id.10);

 

f .getline(name,80);

 

f.getline(buf,80);

 

i f

(strcmp(kind, "FACULTY") =- 0)

{ person = new Faculty(id,name,buf); } else if (strcmp(kind, "STUDENT") == 0) { person = new Student(id,name,buf); } else

/ /

какой у него тип?

/ /

распознавание входного типа

/ /

считывание идентификатора

// считывание имени

// rank или major?

//объект - Faculty

//объект - Student

{ cout « " Corrupted data: unknown type\n", exit(O); } }

Тип второго параметра этой функции должен быть указателем. В противном случае он не сможет принимать значение, возвращаемое оператором new. Оно должно передаваться по ссылке, а не значением. Иначе новый объект будет обо­ значаться только локальным указателем person, а не фактическим аргументом. Кроме того, новый объект будет недоступен из клиентской программы. Это не может быть указатель Faculty. Такой указатель не может ссылаться на объект Student. Он не может быть указателем Student, поскольку не может указывать на объект Faculty.

682

Чость IV # Роситреииов ыспоАШоваяте C+*t-

Итак, это не может быть ни указатель Faculty, ни указатель Student. Какой же должен быть тип указателя, способный ссылаться на объекты различных классов? Вспомните, если различные классы не связаны наследованием, то отсутствует указатель, который может указывать на объекты этих классов и выполнять любую работу. Кроме того, если разные классы связываются наследованием, то указатель базового класса может указывать на объекты любого производного класса — А, В или иного. "Старший брат" может указывать на все, что захочет, поскольку класс назначения находится в пределах иерархической структуры наследования.

Следовательно, это должен быть указатель Person. Внутри функции read() объекты разных производных классов создаются и подключаются к указателю базового класса.

void read

(ifstream& f

Person*&

person)

/ /

считывание одной записи

{ char kind[8], id[10]

name[80],

buf[80];

/ /

распознавание входного типа

f.getline(kind,80);

 

 

f . getline(id,10);

 

 

/ /

считывание идентификатора

f.getlineCname,80);

 

 

/ /

считывание имени

f.getline(buf,80);

 

 

/ /

rank или major?

if (strcmp(kind, "FACULTY") -= 0)

 

 

 

 

{ person = new Faculty(id,name,buf); }

/ /

объект

-

Faculty

else if (strcmp(kind,

"STUDENT") =- 0)

 

 

 

 

{ person = new Student(id,name, buf); }

/ /

объект

-

Student

else

 

 

 

 

 

 

 

{ cout «

" Corrupted

data: unknown type\n"

; exit(O);

}

}

Функция readO вызывается из main() так же, как и в предыдущем варианте. Отличие состоит в том, что компоненты массива data[] типа Person* теперь ука­ зывают на объекты других производных классов (Faculty или Student).

int

mainO

endl « endl;

 

 

{ cout «

// массив указателей

Person* data[20]; int cnt = 0;

ifstream from("univ.dat");

// файл входных данных

if (!from) {cout « " Cannot open file\n";

return 0; }

while (! from.eofO)

 

 

{

read(from, data[cnt]);

/ /

считывание до eof

 

cnt++;

}

 

 

.

. . }

 

/ /

остальная часть main()

Однако базовый указатель не может вызывать операции, которые определены в производных классах. До тех пор, пока базовый указатель ссылается на произ­ водный объект, всегда суш,ествует способ сообщить компилятору то, что нам из­ вестно: базовый указатель указывает на производный объект. Для этого следует воспользоваться приведением к производному классу.

Этот процесс принятия решения хотелось бы инкапсулировать в функцию, на­ пример write(). Подобные функции для выбора решения должны проектировать­ ся для каждой операции, которые выполняются по-разному для различных типов подобных объектов. Трудно выбрать тип параметра для этой функции. Общая структура данной функции:

void write

( . . . ? ? р)

/ /

отображение записи

{ switch (p.getKindO) {

/ /

получение типа объекта

case

Person::FACULTY:

 

 

 

 

 

 

.

. .;

break;

/ /

выполнить

как

для

Faculty

case

Person::STUDENT:

 

 

 

 

 

 

.

. .;

break; } }

/ /

выполнить

как

для

Student

Глава 15 • Виртуальные функции и использование наследования

683

Тип параметра этой функции должен воспринимать два типа фактических аргументов — объекты Faculty и Student. Если тип объекта — Faculty, то эта функция будет вызывать только Faculty: :write(). Если тип параметра Student, функция будет вызывать только Student: :write().

Именно здесь следует воспользоваться материалом из предыдущего раздела о преобразованиях классов. Можно воспользоваться параметром типа Person, поскольку и объекты Faculty, и объекты Student могут копироваться в объект Person. (Вспомним, что объект производного класса располагает достаточными данными для инициализации объекта базового класса.)

void write

(Person р)

/ /

отображение записи

{ switch (p.getKindO) {

/ /

получение типа объекта

case Person::FACULTY:

/ /

выполнить

как

для

Faculty

.

. .;

break;

case Person::STUDENT:

 

 

 

 

 

.

. .;

break; } }

/ /

выполнить

как

для

Student

Недостатком такого решения является то, что он передает параметр по значению, а это не слишком хорошая практика, когда объекты управляют своей памятью динамически. Кроме того, тело функции остается на уровне объекта Person и от­ сутствует способ для преобразования базового объекта обратно в объект произ­ водного класса. Исходные данные отбрасываются и их невозможно восстановить. Даже если добавить конструктор производного класса, который преобразует ба­ зовый объект в объект производного класса (см. листинг 15.2), этого будет недо­ статочно. Конструктор сможет лишь установить значения по умолчанию полей для производного класса. Нам, однако, требуются исходные значения служебного положения преподавателя или специализации студента.

Вы не можете использовать значения базового объекта в качестве параметра функции, но ничто не мешает вам применить базовый указатель как параметр функции. Указатель производного класса (который выполняет все операции про­ изводного класса) можно преобразовать в указатель базового класса — это безопасное преобразование, поэтому приведение типов не требуется. Данные не отбрасываются и возможно обратное преобразование в указатель производного класса. (Однако это преобразование не является безопасным и, следовательно, требуется приведение типа.)

void write (Person* р)

 

/ /

отображение записи

{ switch (p->getKind()) {

 

/ /

получение типа объекта

case Person::FACULTY:

 

 

 

 

 

 

. . .;

break;

 

/ /

выполнить

как

для

Faculty

case Person::STUDENT:

 

 

 

 

 

 

. . .;

break;

}

/ /

выполнить

как

для

Student

Во время такого преобразования нельзя выполнять операции, определенные для производного класса. Слабый базовый указатель может лишь достичь функций, которые определены в базовом классе. Но преимущество этого решения состоит в том, что этот базовый указатель все еш,е указывает на объект производного класса. В операторе выбора функция writeO выясняет, указывает ли фактиче­ ский параметр на объект Faculty или на объект Student. Остается только вызвать либо метод writeO из класса Faculty, либо метод write() из класса Student.

void write (const Person* p)

/ /

отображение записи

{ switch (p->getKind()) {

/ /

получение типа объекта

case Person::FACULTY:

 

 

 

 

p->write();

break;

/ /

выполнить

как для

Faculty

case Person::STUDENT:

 

 

 

 

p->write();

break;

/ /

выполнить

как для

Student

684

Часть IV « Расширенное использование С+"^

 

 

 

 

 

Указатель р является указателем базового класса, поэтому он может добраться

 

только до методов базового класса. Следовательно, вызовы write() в обеих ветвях

 

оператора выбора либо будут достигать write() из базового класса (если он есть

 

в классе Person), либо приведут к синтаксической ошибке (если в классе Person

 

метод writeO отсутствует).

 

 

 

 

 

 

 

Функция writeO уже знает, на объект какого типа указывает ее параметр-

 

указатель. Компилятор знает только, что это указатель на класс Person. Значит,

 

функция write() должна сообщить компилятору о том, что она знает. Компилятор

 

должен выполнить приведение базового указателя либо к классу Faculty (первый

 

оператор выбора), либо к классу Student (второй оператор выбора).

 

void write (const Person* p)

 

/ /

отображение записи

 

{

switch

(p->getKind()) {

 

/ /

получение типа объекта

 

 

 

case

Person::FACULTY:

 

 

 

 

 

 

 

 

((Faculty*)p)->write();

break;

/ /

выполнить

как для Faculty

 

 

 

case Person: .-STUDENT:

 

/ /

выполнить

как для Student

 

 

 

 

((Student*)p)->write();

break;

 

}

}

 

 

 

 

 

 

 

Это приведение выглядит внушительным и устрашаюидим. Но оно осуществляет

 

приведение указателя р класса Person* в указатель типа Faculty* или в указатель

 

типа Student*. Скобки используются потому, что операнд выбора в виде стрелки

 

имеет более высокий приоритет, чем операнды приведения. Если опустить скобки

 

и использовать, например, (Faculty*)p->write(), компилятор решит, что нужно

 

преобразовать значение, возвраш.аемое в результате вызова writeO, а не указа­

 

тель р. Сохраните здесь эти скобки.

 

 

 

 

 

 

 

Эта функция writeO будет вызываться в цикле, получая в качестве фактиче­

 

ских аргументов указатели Person, которые указывают либо на Faculty, либо на

 

Student объекты.

 

 

 

 

 

for

(int i=0; i < cnt; i++)

 

 

 

 

 

 

{

write(data[i]); }

/ /

отображение данных

 

Полная программа представлена в листинге 15.4.

Листинг 15.4. Обработка неоднородного списка — объектно-ориентированный подход

#include

<iostream>

 

 

#include

<fstream>

 

 

using

namespace std;

 

 

struct

Person {

 

 

public:

 

 

 

 

 

enum Kind { FACULTY, STUDENT } ;

 

 

protected:

 

 

 

 

Kind

kind;

/ /

FACULTY или STUDENT

 

char

id[10] ;

/ /

данные общие для обоих типов

 

char*

name;

/ /

переменная длина

public:

Person(const char id[], const char nm[], Kind type)

strcpy(Person::id,id);

// копирование идентификатора

name = new char[strlen(nm)+l];

// выделение памяти для имени

if (name ==0) {cout « "Out of memory\n"

exit(O); }

strcpy(name,nm);

// копирование имени

kind = type; }

// помните его тип

Глава 15 • Виртуальные функции и использование носдвАОвания

| 685

Kind getKindO const

 

 

{ return kind; }

/ / доступ к типу Person

 

"PersonO

// возврат памяти динамически распределяемой области памяти

{ delete [] name; }

} ;

 

 

struct Faculty : public Person {

 

 

private:

// только для преподавателей

 

char* rank;

 

public:

 

 

Faculty(const char id[], const char nm[], const char r[])

 

: Person(id,nm,FACULTY)

// список инициализации

 

{rank = new char[strlen(r)+l];

if (rank ==0) {cout « "Out of memory\n"; exit(O); } strcpy(rank,r); }

void write () const

 

 

// отображение записи

{ cout «

" id: " « id « endl;

// вывод на печать идентификатора, имени

cout «

" name: " «

name «

endl;

// только для преподавателей

cout «

" rank: " «

rank «endl «endl; }

"FacultyO

}

// возврат памяти динамически распределяемой области памяти

{ delete [] rank;

}

 

 

 

 

struct Student : public Person {

 

 

private:

 

 

 

// для студента

char* major;

 

 

public:

Student(const char id[], const char nm[], const char m[])

: Person id,nm,STUDENT)

// инициализация списка

{ major = new char[strlen(m)+1];

 

if (major =- 0) {cout «

"Out ofmemory\n"; exit(O); }

strcpy(major,m); }

 

 

void write () const

 

 

// отображение записи

{ cout «

" id: " « id « e n d l ;

// вывод на печать идентификатора, имени

cout «

" name: " «

name «

endl;

// только для студента

cout «

" major: " «

major «endl «endl; }

~Student()

 

// возврат памяти динамически распределяемой области памяти

{ delete [] major; }

} ;

 

 

 

 

void read (if stream& f, Person*& person)

// считывание одной записи

{ char kind[8], id[10],

name[80], buf[80];

// распознавание входного типа

f .getline(kind,80);

 

 

f.getline(id,10);

 

 

// считывание идентификатора

f.getline(name,80);

 

 

// считывание имени

f.getline(buf,80);

 

 

// rank или major?

if (strcmp(kind, "FACULTY") == 0)

// объект - Faculty

{ person = new Faculty(id,name,buf); }

else if (strcmp(kind,

"STUDENT") == 0)

// объект - Student

{ person = new Student(id,name,buf); }

else

" Corrupted data: unknown type\n" exit(O); }

{ cout «

686

Часть!V ^ Расширенное mono.

 

 

void write (const Person* p)

 

// отображение записи

{ switch (p->getKind()) {

 

// получение типа объекта

case Person: .'FACULTY:

break;

// выполнить как для Faculty

 

((Faculty*)p)->write();

case Person::STUDENT:

break;

// выполнить как для Student

} }

((Student*)p)->write();

 

 

 

 

 

int mainO

 

 

 

 

 

cout «

endl « endl;

 

 

// массив указателей of pointers

Person* data[20]; int cnt = 0;

 

ifStream from("univ.dat");

 

// файл входных данных

if (! from) {cout «

" Cannot open file\n"; return 0; }

while (!from.eof())

 

 

// считывание до eof

{ read(from, data[cnt]);

 

cnt++; }

 

 

 

 

cout «

"Total records read: «

cnt « endl «

endl;

for (int i=0; i < cnt;

i++)

 

 

 

{ write(data[i]); }

j++)

 

/ /

отображение данных

for (int j=0; j < cnt;

 

/ /

удаление записи

{ delete data[j]; }

 

 

return 0;

 

 

 

 

}

 

 

 

 

 

Это решение намного элегантнее, чем предыдущее. Данные и операции связаны, действия перенесены в серверные классы, разделение на части связанного кода исключено. Как и при любом объектно-ориентированном подходе, исходная про­ грамма слишком длинная. В ином случае программа выполняет то, что и програм­ ма в листинге 15.3. Ее вывод соответствует выводу программы в листинге 15.3 (см. рис. 15.11).

На слелуюш,ем этапе следует исключить проверки типа объекта, которые вы­ полняются функцией write(). Вместо тестирования типа объекта-цели, приведе­ ния указателя обратно к этому типу, а затем вызова функции соответствуюш.его производного класса, потребуем от компилятора осуш,ествить все это. Компилятор должен сгенерировать код объекта, который тестирует тип объекта, выполняет приведение и вызывает соответствующейй метод. Для этого рекомендуем исполь­ зовать зарезервированное слово virtual в назначении функций членов базового класса.

Динамическое связывание: виртуальные функции

Зарезервированное слово virtual является синтаксическим маневром. Оно создает свойство разрешения типа во время исполнения для сообщения, отправ­ ленного объекту производного типа. Для использования этого свойства в базовом классе и в каждом производном классе реализуется функция с одинаковым именем.

В качестве примера рассмотрим эти средства, реализуя метод writeO для ба­ зового класса Person и для производных классов Faculty и Student. Вы сможете написать глобальную функцию write() следующим образом:

void write (const Person* p)

/ /

отображение записи

{ p->write(); }

/ /

разве это не красиво?

Для связывания в процессе компиляции это просто означает вызов метода write(), определенного в классе Person. Для связывания во время выполнения программа,

Глава 15 • Виртуальные функции и использование наследования

687

сгенерированная компилятором, проанализирует тип объекта, на который указы­ вает базовый указатель р, определит, к какому типу должен относиться вызывае­ мый метод, и вызовет метод writeO из этого типа. В зависимости от объекта, обозначенного указателем, будет вызван либо метод Faculty, либо метод Student.

Чтобы метод работал, необходимо выполнить несколько ограничений. Вир­ туальная функция, принадлежащая производному классу, должна вызываться только через базовый указатель или базовую ссылку. Связывания во время выполнения не произойдет, если сообщение посылается базовому объекту или объекту произ­ водного класса. В каждом случае используется алгоритм для статического связы­ вания. Сообщение вызывается из того класса, каким является тип объекта.

Например, значение x.writeO зависит от типа, к которому принадлежит объ­ ект X. Этот тип определяется в процессе компиляции, а не во время выполнения.

Виртуальная функция не может быть статической. Она не может вызваться из оператора области действия класса, но должна вызываться через базовый указа­ тель (ссылку), которая ссылается на объект производного класса.

Режим наследования для порождения должен быть общедоступным и не может быть защищенным или закрытым. Неявное приведение допускается только для общедоступных порождений.

Функция с этим же именем определяется как виртуальная в базовом классе иерархии наследования. В каждом производном классе должна быть реализована функция с таким же именем, что и базовая виртуальная функция. При переопре­ делении функции в производном классе она должна совпадать по имени, сигнатуре и типу возвращаемого значения с виртуальной функцией базового класса.

Если имя функции в производном классе другое, это не является ошибкой. Однако такая функция не может вызываться с использованием связывания во время исполнения. Динамическое связывание использует вызов той же самой функции, но с другой ее интерпретацией.

Если в производном классе другая сигнатура, то производный метод скрывает базовый метод и разрушает механизм виртуальной функции. Если производные классы определяют пустую функцию write() без параметров, а базовый класс обозначает пустую функцию write(int), то при использовании динамического связывания отсутствует способ для вызова функций производного класса. В этом случае p->write() вызовет функцию, которая принадлежит к классу указателя р. Если она существует, вы сможете ее вызвать. В противном случае имейте в виду, что допущена синтаксическая ошибка.

Если тип результата виртуальных функций в производных классах другой, это синтаксическая ошибка, даже если сигнатура функций одна и та же.

Зарезервированное слово virtual появляется только в спецификации базового класса. В определении функции базового класса, а также в спецификации произ­ водного класса его не требуется повторять.

Если иерархия включает классы более чем двух уровней, виртуальные функции можно определять на любом уровне иерархии. Совсем не обязательно реализовать определенную функцию, например, на самом верхнем или на более низком уровне иерархии наследования. Она может наследоваться косвенно.

Если все ограничения удовлетворены, не надо определять поле kind в базовом классе и метод, который возвращает значение поля kind. Для преобразования программы в листинге 15.3 в программу с виртуальными функциями требуется определить функцию в классе Person. У функции должен быть тип результата void и параметры должны отсутствовать.

struct Person {

 

protected:

 

char

id[10];

/ / Kind отсутствует

char*

name;

 

688

Часть IV » Расширенное использование С+4-

 

 

public:

 

 

 

// Kind отсутствует

 

Person(const char ici[], const char nm[]);

 

virtual void write () const;

 

// const - часть сигнатуры

 

"PersonO; } ;

 

 

 

 

В результате производным классам не требуется передавать базовому классу

 

информацию поля kind.

 

 

 

struct Faculty : public Person {

 

 

 

private:

 

 

 

// только для преподавателей

 

char* rank;

 

 

 

public;

 

 

 

 

 

Faculty(const char id[], const char nm[], const char r[])

 

 

: Person (id, nm)

 

// FACULTY отсутствует

 

{ rank = new char[strlen(r)+l];

 

 

 

if (rank ==0) {cout «

"Out ofmemory\n"; exit(O); }

 

 

strcpy(rank,r); }

 

// теперь является виртуальной

 

void write () const

id « endl;

 

 

{ cout «

" id: " «

// вывод на печать идентификатора, имени

 

cout «

" name: " «

name « endl;

} // только для преподавателей

 

cout «

" rank: " << rank <<endl «endl;

 

"FacultyO

 

 

// возврат памяти динамически

 

{ delete [] rank; } } ;

 

 

 

 

 

 

// распределяемой области памяти

Что более важно, в клиентской программе не требуется проверять тип объекта. В листинге 15.5приведена программа из листинга 15.4,использующая виртуаль­ ную функцию writeO для исключения из клиентской программы анализа подтипа. Вывод программы такой же, что идля предыдущего варианта (см.рис. 15.11).

Листинг 15.5. Обработка неоднородного списка сиспользованием виртуальных функций

#inclucle <iostream> #inclucle <fstream> using namespace std;

struct Person {

 

 

protected:

 

// данные общие для обоих типов

char id[10];

 

char* name;

 

// переменной длины

public:

 

// типа Kind

Person(const char id[], const char nm[])

{ strcpy(Person::id,id);

 

// копирование идентификатора

name = new char[strlen(nm)+l];

// выделение пространства для объекта

if (name == 0) {cout «

"Out ofmemory\n"; exit(O); }

strcpy(name,nm);

 

// копирование имени

}

 

 

virtual void writeO const

// работы немного

{ }

 

 

v~Person()

// возврат памяти динамически распределяемой области памяти

{ delete[] name; }

 

// для объекта Person

} ;

 

 

struct Faculty : public Person {

 

 

private:

 

// только для преподавателей

char* rank;

 

Глава 15 • Виртуальные функции и использование наследования

689

public:

 

 

Faculty(const char id[], const char nm[], const char r[])

 

: Person(icl, nm)

//список инициализации

 

{ rank = new char[strlen(r)+l];

 

if (rank ==0) {cout «

"Out ofmemory\n"; exit(O); }

 

strcpy(rank,r); }

 

 

void write () const

{ cout « " id: " « id « endl; cout « " name: " « name « endl;

cout « " rank: " « rank «endl «endl; }

//отображение записи

//вывод на печать идентификатора, имени

//только для преподавателей

TacultyO

// возврат памяти динамически распределяемой области памяти

[ deleted rank;

}

 

struct Student : public Person

 

private:

// только для студента

char* major;

public:

Student(const char id[], const char nm[], const char m[])

: Person(id, nm) // список инициализации { major = new char[strlen(m)+l];

if (major == 0) {cout « "Out ofmemory\n"; exit(O); } strcpy(major,m); }

void writeO const

 

endl;

// отображение записи

{ cout «

" id: " « id «

// вывод на печать идентификатора, имени

cout «

" name: " «

name « endl ;

 

cout «

"major: " «

major «endl «endl; }

// только для студента

~Student()

 

// возврат памяти динамически распределяемой области памяти

{ delete[] major; }

void read (if stream& f,

Person*& person)

// считывание одной записи

{ char kind[8], id[10], name[80], buf[80]

// распознавание входного типа

f.getline(kind,80);

 

 

f.getline(id.lO);

 

 

// чтение идентификатора

f.getline(name,80);

 

 

// чтение имени

f.getline(buf,80);

 

 

// rank или major?

if (strcmp(kind, "FACULTY") == 0)

// объект - Faculty

{ person = new Faculty(id, name, buf); }

else if (strcmp(kind, "STUDENT") == 0)

// объект - Student

{ person = new Student(id,name,buf); }

else

 

 

 

exit(O); }

{ cout « "Corrupted data: unknown type\n"

}

 

 

 

 

void write (const Person* p)

// отображение записи

{ p->write(); }

 

 

// Faculty или Student

int mainO

 

 

 

 

{ cout « endl « endl;

 

 

// массив указателей

Person* data[20]; int cnt = 0;

ifstream from("univ.dat");

// файл входных данных

if (!from) {cout « " Cannot open file\n" return 0; }

while (! from.eofO)

 

 

// считывание до eof

{ read(from, data[cnt]);

 

cnt++; }

 

 

 

 

Соседние файлы в предмете Программирование на C++