Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdf670Часть IV * Расширенное использование C++
Внастоящее время строгий контроль типов в выражениях и при передаче пара метров рассматривается как доказанный тип. Для каждого объекта вычислений набор допустимых операций для объекта известен заранее как компилятору, так
ипроектировщику клиентской программы и липу, осуществляющему сопровож дение.
Строгий контроль типов обеспечивает так называемое раннее связывание. Тип объектов вычислений фиксируется на этапе компиляции и не изменяется во время выполнения программы. Другой популярный термин — статическое связывание. Оно означает то же самое. Связь между именем объекта и типом объекта фикси руется при компиляции и не может изменяться динамически во время выполнения программы.
Когда сообщение, определенное его именем и списком фактических аргумен тов, посылается объекту, компилятор интерпретирует его в соответствии с клас
сом (типом) этого объекта. Имя класса объекта известно на этапе компиляции и не изменяется во время выполнения.
Статическое связывание является стандартом таких современных языков, как С4-+, С, Java, Ada, Pascal (но не Lisp). Вначале оно было введено для повышения производительности, а не для улучшения качества программы. Динамическое свя зывание, поиск значения вызова функции во время выполнения отнимает время. Когда значение вызова функции фиксируется при компиляции, программа выпол няется быстрее.
Позднее обнаружилось, что статическое связывание может с успехом исполь зоваться для осуществления контроля типов. Если функция вызывается с невер ным количеством или неправильными типами аргументов, этот вызов отклоняется на этапе компиляции. Если имя сообщения (с соответствующей сигнатурой) не об наружено в спецификациях класса, вызов отклоняется при компиляции, а не во время выполнения.
Строгий контроль типов предусматривает контроль типов на этапе компиляции и повышает производительность во время выполнения. Это полезно в большин стве приложений.
Когда еще вам хотелось бы установить связь между идентификатором и объек том вычислений? Можно ответить: на этапе выполнения.
Рассмотрим обработку неоднородного списка объектов или обработку внешне го входного потока с объектами разных типов. При файловом вводе или вводе от интерактивного пользователя программа не знает точно тип объекта, поступаю щего из внешней среды.
Например, программа может выводить на экран изображения, показывая со ставляющие их фигуры одну за другой. Программа должна вызвать Circle:: clraw(), Square: :clraw(). Rectangle: :draw() или еще какую-либо известную ей фигуру. Это было бы замечательно. Однако лучше всего использовать только один оператор в исходной программе и изменять его значение в зависимости от фактической природы объекта shape.
shape. drawO; / / из класса Circle, Square или Rectangle
Если формой объекта в текущем проходе цикла является Circle, то этот опера тор вызывает Circle: :cJraw(). Если — Square, то следует задать Square: :draw(). Если это Rectangle, вызовите Rectangle: :clraw().
При строгом контроле за типами это невозможно. Компилятор найдет объяв ление переменной shape, установит ее класс и проверит определение класса. Если в этом классе не будет найдена пустая функция draw() с отсутствующими парамет рами, компилятор генерирует сообщение об ошибке. Если функция обнаружена, компилятор генерирует объектный код. Но тип функции draw() во время компи ляции будет фиксированным. Во время выполнения уже будет невозможно осуще ствлять поиск значения функции draw().
Глава 15 • Виртуальные функции и использование наследования |
671 |
То, что нам в данный момент нужно, называется связыванием этапа выполне ния или поздним динамическим связыванием. Предположим, что существует не сколько объектов вычисления (функций drawO в различных классах). Требуется связать один из этих объектов вычислений с именем в вызове конкретной функ ции. Более того, желательно, чтобы эта функция draw() в представленном вызове функции означала Circle: :draw(), Square: :draw(), Rectangle: :draw() и т.д. Хотелось бы, чтобы это значение устанавливалось не на этапе компиляции, а при выполнении. Тогда различные фигуры будут нарисованы в зависимости от значе ния вызова функции.
Поговорим о терминологии. Техническим термином для установки значения имени функции является связывание (binding). Компилятор связывает имя функ ции с конкретной функцией. Желательно, чтобы это связывание имело место на этапе выполнения. Поэтому оно называется динамическим связыванием, а не свя зыванием при компиляции. Нужно, чтобы связывание происходило после этапа компиляции, поэтому оно называется поздним, а не ранним, связыванием. Требу ется, чтобы такое связывание допускало присваивание различных значений тому же самому имени функции в зависимости от природы используемого объекта. Именно поэтому оно называется динамическим, а не статическим связыванием.
Способность имени функции принимать при своем вызове различные значения называется полиморфизмом (от "множество форм"). Некоторые авторы исполь зуют термин "полиморфизм" в более широком смысле, включая использование одного имени функции в различных классах, но без динамического связывания. Обратите внимание, что при использовании полиморфизма подразумевается позднее или динамическое связывание, присвоение значения вызова метода на этапе выполнения в зависимости от фактического типа объекта, т. е. назначение сообш,ения.
Все это должны обеспечить виртуальные функции C+ + .
Динамическое связывание: традиционный подход
Динамическое связывание не является каким-то особенным вопросом для объектно-ориентированного программирования. Обработка неоднородных списков всегда была обычной вычислительной задачей, и программисты привыкли реализовывать динамическое связывание на любом языке. Наша задача заключалась в обработке подобных объектов. Они настолько подобны, что имеет смысл исполь зовать одно имя для функции во всех категориях объектов (например, clraw()). Но типы объектов не являются идентичными — каждая функция выполняет заданные действия по-своему.
Как пример рассмотрим обработку списка записей в базе данных университета. Предположим, что суш,ествует только два типа записей: для студентов и преподавателей. Допус тим, что программа сохраняет лишь три части информации: идентификатор университета, наименование и либо служеб ное положение (для преподавателей), либо специализацию (для студентов). Краткий пример данных представлен на рис. 15.9.
Длина значения идентификатора одинакова для каждого лица (девять символов) и может быть реализована как мас сив символов фиксированной длины. Имя, служебное поло жение и специализация имеют разные длины для разных лиц. Следовало бы реализовать их как динамически выделенные массивы. Структура для отдельного лица выглядит следую- ш,им образом
672
struct Person { |
/ / |
1 для преподавателей, 2 для студента |
|
int kind; |
|||
char |
id[10]; |
/ / |
фиксированной длины |
char* |
name; |
/ / |
переменной длины |
char* |
rank; |
/ / |
только для преподавателей |
char* |
major; } |
/ / |
только для студента |
Эту структуру можно реализовать как класс с конструкторами, деструктором и функциями-членами. Но на данном этапе нам непонятно, о чем идет речь. Эти элементы будут введены при обсуждении более современного подхода.
В первом традиционном подходе характеристики различных видов объектов объединяются (например, служебное положение, специализация) в один класс. Чтобы обработать каждый вид объектов по-разному, добавляется поле для описа ния, к какой области принадлежит конкретный объект. В клиентской программе используются либо операторы выбора switch, либо операторы if, ветви которых реализуют обработку различных видов объектов.
Вместо определения полей для обоих видов объектов можно было бы исполь зовать конструктор union. Для массивов фиксированного размера это имеет смысл. Но при динамическом управлении памятью из-за этого могут возникнуть дополни тельные сложности.
Динамическое управление памятью сохраняет пространство и предотвращает переполнение памяти. Простым методом сохранения данных в памяти является определение массива объектов Person. Хотя и используется зарезервированное слово struct, переменные типа Person являются объектами, потому что в С+ + зарезервированные слова struct и class — синонимы (за исключением прав до ступа по умолчанию и наследования по умолчанию).
Person data [1000]; / / массив входных данных
Рекомендуем вам хранить данные в массиве указателей на объекты, а не в мас сиве объектов. Для выделения большого массива указателей не требуются боль шие затраты памяти. В случае переполнения массив указателей можно повторно выделить, не копируя суш,ествуюил,ие данные (см. главу 6). Пространство для каж дого объекта Person будет выделено после считывания данных этого объекта из входного файла.
Person* data [1000]; |
/ / массив указателей |
Для считывания данных из входного файла определен объект if stream библио течного класса. Он всегда открывается для ввода. Для ассоциации физического файла с объектом логического файла имя физического файла должно быть опре делено как параметр вызова конструктора.
ifstream from( "univ.dat"); |
/ / |
файл входных данных |
i f (!from) { cout « " Cannot open f i l e \ n " ; |
return |
0; } |
Для каждого объекта входного файла программа динамически выделяет струк туру, а затем считывает четыре элемента данных: строку, определяющую тип объекта, идентификатор, имя и либо служебное положение (для преподавателей), либо специализацию (для студентов). Программа проверяет значение строки, определяюш,ей тип объекта ("FACULTY" или "STUDENT"), и устанавливает поле типа объекта либо в 1, либо в 2.
char buf[80]; |
// буфер входных данных |
Person *р = new Person; |
// выделение памяти для нового объекта |
from.getline(buf,80) ; |
// распознавание поступающего типа |
i f (strcmp(buf, "FACULTY") |
0) |
p->kind = 1; |
// 1 для преподавателей |
Глава 15 • Виртуальные функции и использование наследования |
673 |
|||
else i f (strcmpCbuf, |
"STUDENT") |
|
|
|
p->kincl |
= 2; |
/ / |
2 для студента |
|
else |
|
/ / |
тип не известен |
|
p->kincl |
= 0; |
|
||
Поскольку длина поля идентификатора известна, его можно непосредственно считать в поле объекта Person. Длина данных для имени, служебного положения и специализации неизвестна до тех пор, пока данные не будут считаны в память. Следовательно, программа должна считать данные в буфер фиксированного размера, измерить длину данных, выделить достаточную память в динамически распределяемой области памяти и скопировать данные из буфера в память дина мически распределяемой области.
f rom.getline(p->icl, 10); |
|
|
/ / |
считывание |
id |
|
|
|
|||||
f ro(T].getline(buf ,80); |
|
|
|
/ / |
считывание |
имени |
|
|
|
||||
p->name = new char[strlen(buf)+1]; |
/ / |
выделение памяти |
|
|
|
||||||||
strcpy(p->name, |
buf); |
|
|
|
/ / |
копирование имени |
|
|
|
||||
from.getline(buf,80); |
|
|
/ / чтение служебное |
положение/специализация |
|||||||||
i f (p->kincl |
== 1) |
|
|
|
/ / |
память для служебного |
положения |
||||||
{ |
p->rank |
- new char[strlen(buf)+1]; |
|||||||||||
|
strcpy(p->rank, buf); } |
|
|
/ / |
копирование служебного положения |
||||||||
else |
i f (p->kincl |
== 2) |
|
|
|
|
|
|
|
|
|
|
|
{ |
p->major |
= new char[strlen(buf)+1] |
|
/ / память для специализации |
|||||||||
|
strcpy(p->major, |
buf); } |
|
|
/ / |
копирование специализации |
|
||||||
|
|
|
|
|
|
ДИНАМИЧЕСКИ РАСПРЕДЕЛЯЕМАЯ |
|
||||||
|
ПАМЯТЬ СТЕКА |
|
|
|
ОБЛАСТЬ ПАМЯТИ |
|
|
|
|||||
|
|
|
ВИД |
|
|
1 |
|
|
|
|
|
|
|
|
data[0] |
1 идентификатор |
W |
U12345678 |
|
|
|
|
|
|
|||
|
|
имя |
|
^ |
|
|
W |
\ |
Smith, John |
| |
|
||
|
|
|
|
|
|
|
|||||||
|
|
|
должность |
|
|
|
W |
|
Associate Professor |
| |
|||
|
|
|
вид |
|
|
2 |
|
|
|
|
|
|
|
|
data[1] |
|
идентификатор |
W |
U12345611 |
|
|
|
|
|
|
||
|
|
имя |
|
' W |
|
|
W |
|
Jones, Jan |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
специализация |
|
|
|
^ |
|
Computer Science |
| |
|||
|
|
|
вид |
|
|
1 |
|
|
|
|
|
|
|
|
data[2] |
|
идентификатор |
W |
U12345689 |
|
|
|
|
|
|
||
|
|
имя |
|
^ |
|
|
W |
Blacl<, Jeanne |
| |
|
|||
|
|
|
|
|
|
|
|||||||
|
|
|
должность |
|
|
|
W |
Assistant Professor |
| |
||||
|
|
|
вид |
|
|
2 |
|
|
|
|
|
|
|
|
Ня!яГЯ1 |
|
идентификатор |
W |
и12345622 |
|
Green, James |
| |
|
||||
|
|
|
имя |
|
|
|
|
W |
|
||||
|
|
|
специализация |
|
|
|
W\ |
Astronomy |
|
|
1 |
||
|
Рис. 15.10. CinpyKrnypa динамически |
распределенной |
памягпи |
|
|||||||||
|
|
|
для |
входных |
данных |
|
|
|
|
|
|
|
|
На рис. 15.10 представлена структура элемента данных для этого примера. Массив clata[ ] в левой части рисунка является стековым массивом, а вся осталь ная память справа от массива (объекты типа Person и их динамическая память) выделяются из динамически распределяемой области памяти.
Хорошей идеей считается инкапсуляция алгоритма считывания в функцию, на пример read О, чтобы клиентская программа передавала этой функции файловый
674 |
Часть iV • Расширенное использование С'^-^- |
|
|
|||||||||||||
|
объект и указатель Person. Функция read() должна выделять память для объекта |
|||||||||||||||
|
Person, считывать данные из файла и заполнять объект Person входными данными. |
|||||||||||||||
|
Person* |
data[20]; |
int |
cnt = 0; |
|
/ / |
массив указателей |
|||||||||
|
ifSt ream fromC'univ.dat"); |
|
|
/ / |
входной файл: библиотечный объект |
|||||||||||
|
i f |
(!from) |
{ |
cout |
« |
" |
Cannot open |
f i l e \ n " ; |
return 0; } |
|
||||||
|
while |
(! f rom.eof 0 ) |
|
|
|
|
/ / |
считывание до eof |
||||||||
|
{ |
read(from, |
data[cnt]); |
|
|
/ / |
data[cnt] имеют тип |
|||||||||
|
Person* |
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
cnt++; |
} |
|
|
|
|
|
|
|
|
|
|
|
||
|
cout |
« |
|
" Total records read: " « |
cnt « |
endl « |
endl; |
|
||||||||
|
|
Соберем функцию |
read() |
из частей, описанных ранее. У этой функции есть |
||||||||||||
|
два главных недостатка, которые относятся к передаче параметра. |
|||||||||||||||
|
void |
read |
(ifstream |
f, |
Person* person) |
|
/ / |
плохой интерфейс |
||||||||
|
{ |
char |
buf[80]; |
|
|
|
|
|
|
|
|
|
||||
|
|
Person* |
p - new Person; |
|
/ / |
выделение памяти для нового объекта |
||||||||||
|
|
f.getline(buf,80); |
|
|
|
|
|
/ / |
распознавание входного типа |
|||||||
|
|
i f |
(strcmp(buf, |
"FACULTY") |
== 0) |
|
|
/ / |
1 для |
преподавателей |
||||||
|
|
|
p->kind |
= 1; |
|
|
|
|
|
|
||||||
|
|
else |
i f |
|
(strcmp(buf. |
"STUDENT") |
== 0) |
|
|
|
|
|||||
|
|
|
p->kind |
= 2 ; |
|
|
|
|
|
|
/ / |
2 для |
студента |
|||
|
|
else |
|
|
|
|
|
|
|
|
|
|
/ / |
тип неизвестен |
||
|
|
|
p->kind |
= 0 ; |
|
|
|
|
|
|
||||||
|
|
f.getline(p->id,10); |
|
|
|
|
/ / |
считывание идентификатора |
||||||||
|
|
f.getline(buf,80); |
|
|
|
|
|
/ / |
считывание имени |
|||||||
|
P->name = new char[strlen(buf)+l]; |
|
/ / |
выделение памяти |
||||||||||||
|
strcpy(p->name, |
buf); |
|
|
|
/ / |
копирование имени |
|||||||||
|
f.getline(buf,80) |
; |
/ / считывание служебного положения/специализации |
|||||||||||||
|
i f |
(p->kind == 1) |
|
|
|
|
|
|
|
|
||||||
|
|
|
{ |
p->rank = new char[strlen(buf)+l]; |
/ / память для служебного положения |
|||||||||||
|
|
|
|
strcpy(p->rank, |
buf); |
} |
|
/ / |
копирование служебного положения |
|||||||
|
else |
i f |
(p->kind |
== 2) |
|
|
|
|
|
|
||||||
|
|
|
{ |
p->major = new char[strlen(buf)+l]; |
/ / |
память для специализации |
||||||||||
|
|
|
|
strcpy(p->major, buf); } |
|
|
/ / |
копирование специализации |
||||||||
|
person |
= p; |
} |
|
|
|
|
|
|
/ / |
присоединение к массиву |
|||||
Параметры передаются функции по значению. Это очевидно в случае файлово го объекта. Когда данные считываются из файла, внутреннее состояние файлового объекта изменяется. Если внутреннее состояние остается без изменений, то в сле дующий раз файл считает эти же данные, а не следующую запись. Когда файловый объект передается по значению, внутреннее состояние объекта параметра изме няется, а внутреннее состояние параметра файлового объекта останется тем же. Объекты не следует передавать по значению. Они должны передаваться по ссылке.
void read (ifstream& f, Person* |
person) |
/ / считывание одной записи |
{ char buf[80] ; |
|
|
Person* p = new Person; |
/ / |
выделение памяти для нового объекта |
person=p; } |
/ / |
остальная часть readO |
/ / |
присоединение нового объекта |
Рекомендуем помечать ссылки на объект как константы, если функция не из меняет состояние объекта во время ее выполнения. В данном примере модифика тор const отсутствует, потому что при считывании информации из файла файловый объект изменяется. Программист серверного класса if st ream клиентской части не должен проектировать серверный класс. Чтобы передать операции ввода/вывода, ему достаточно знать, что состояние файлового объекта изменяется.
IT 676 |
Часть IV # Расширенное использование С-^-^ |
Здесь Person* person можно интерпретировать либо как объект Person, переда ваемый по указателю, либо как указатель Person (типа Person*), передаваемый по значению. Передать этот указатель по ссылке совсем не сложно. Стандартное правило C + + (описанное в главе 7 "Программирование с использованием функций C++") указывает, что для перехода от передачи по значению к передаче по ссылке необходимо только вставить амперсанд (&) между наименованием типа и именем параметра. Другие изменения не нужны, ни в теле функции, ни в син таксисе вызова функции. Вот так он должен выглядеть.
void read (ifstream& f, Person* &person) |
/ / считывание одной записи |
||
{ char but[80] ; |
|
|
|
Person* |
р = new Person; |
/ / |
выделение памяти для нового объекта |
|
|
/ / |
остальная часть read() |
person |
= р; } |
/ / |
присоединение нового объекта |
Чтобы облегчить этот переход, параметру было первоначально присвоено имя Person* person, а не Person *person. Но это не играет роли. C + + не учитывает пробелы в этом случае, и можно расположить звездочку (и амперсанд) между наименованием типа и именем параметра наиболее подходящим способом.
Передача указателя по указателю не является проблемой. Необходимо быть осторожным при вызове функции, интерфейсе функции и в теле функции. В вызо ве функции звездочка вставляется перед именем переменной (т. е. перед именем указателя data[cnt]). Вызов функции выглядит так:
while (! f rom.eofO) |
/ / |
считывание до eof |
{ read(from, &data[cnt]); |
/ / |
передача указателя по указателю |
cnt++; } |
|
|
Винтерфейсе функции звездочка вставляется перед наименованием Парамет ра, т. е. вместо Person* person следует указывать Person* *person (или Person** person).
Втеле функции, и именно здесь чаще всего возникают ошибки, звездочку следует использовать перед именем параметра. Имя параметра — person, а не *person или **person. Следовательно, последний оператор в функции read() должен использовать разыменование:
void read |
(ifstream& f, Person^ |
person) |
/ / |
указатель no указателю |
{ char buf[80]; |
|
|
|
|
Person |
*p = new Person; |
/ / |
выделение памяти для нового объекта |
|
*person |
= p; } |
/ / |
остальная |
часть readO |
/ / |
приз! |
|
||
Это совсем нетрудно, но передача указателя по ссылке намного проще передачи указателя по указателю.
До сих пор обсуждались технические детали ввода данных в массив внутри компьютера. Мы не рассматривали динамическое связывание, полиморфизм и другие вопросы. Мы считаем, что повторение материала из 6 и 7 глав по дина мическому управлению памятью, файлам ввода/вывода и передаче параметров пойдет только на пользу.
Динамическое связывание становится проблемой, когда программа начинает обработку данных, которые уже находятся в памяти. Объекты разных видов тре буют разной обработки, поэтому программе необходимо распознавать в каждом конкретном вызове, из какой области обрабатывается объект. Здесь пригодится поле kind класса Person. В этом простом примере "обработка данных" означает тщательный просмотр массива указателей и вывод на печать каждого объекта Person либо как преподавателя (с отображенным rank — служебным положением), либо как студента (с отображенной major — специализацией). В реальной жизни
I |
678 ^ |
|
|
Чость IV ^ Расширенное использование C-^-^ |
|||||||||||
|
strcpy(p->name, |
buf); |
|
|
|
|
|
// копирование имени |
|||||||
|
f.getline(buf.80); |
|
|
|
|
|
|
// считывание rank/major |
|||||||
|
i f (p->kincj == 1) |
|
|
|
|
|
|
// память для rank |
|||||||
|
{ p->rank = new char[strlen(buf)+1]; |
|
|
||||||||||||
|
strcpy(p->rank, buf); } |
|
|
|
|
// копирование rank |
|||||||||
|
else if (p->kind == 2) |
|
|
|
|
// память для major |
|||||||||
|
{ p->major = new char[strlen(buf)+1]; |
|
|
||||||||||||
|
strcpy(p->major, buf); } |
|
|
|
|
// копирование major |
|||||||||
|
person = p; |
|
|
|
|
|
|
|
|
// присоединение к массиву |
|||||
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int mainO |
|
|
|
|
|
|
|
|
|
|
|
|
||
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
// массив указателей |
|
|
Person* clata[20]; int cnt = 0; |
|
|
|
|
||||||||||
|
ifSt ream fromC'univ.dat"); |
|
|
|
|
// файл входных данных |
|||||||||
|
if (!from) {cout « |
" Cannot open file\n"; return 0; } |
|||||||||||||
|
while |
(Ifrom.eofO) |
|
|
|
|
|
|
|
||||||
|
{ read |
(from, |
data[cnt]); |
|
|
|
|
/ / считывание до eof |
|||||||
|
cnt++; |
} |
|
|
|
|
|
|
|
|
|
|
|||
|
cout |
« |
|
" Total |
records read: " |
« |
cnt |
« |
endl « |
endl; |
|||||
|
for |
(int |
i=0; |
i |
< cnt; |
i++) |
|
|
|
|
/ / |
вывод на печать идентификатора, имени |
|||
|
{ cout |
« " |
i d : |
" |
« d a t a [ i ] - > i d |
« e n d l ; |
|
|
|||||||
|
cout |
« " |
name: " «data[i]->name |
« e n d l ; |
|
|
|||||||||
|
i f |
(data[i]->kind |
== 1) |
|
|
|
|
/ / |
должность на факультете |
||||||
|
|
cout |
« " |
rank: " |
«data[i] - >rank |
« e n d l ; |
|||||||||
|
else |
i f |
|
(data[i]->kind == 2) |
|
|
|
|
/ / |
специализация студента |
|||||
|
|
cout |
« " |
major: |
" |
«data[i]->major |
« e n d l |
||||||||
|
cout |
« |
|
endl; |
} |
|
|
|
|
|
|
|
|
||
|
for |
(int |
j=0; |
j |
< cnt; |
j++) |
|
|
|
|
|
|
|||
|
{ delete |
[ ] data[j]->name; |
|
|
|
|
/ / |
удаление имени |
|||||||
|
i f |
(data[j]->kind |
== 1) |
|
|
|
|
|
|
||||||
|
|
delete [ ] |
data[j]->rank; |
|
|
|
|
/ / |
удаление rank/major |
||||||
|
else |
i f |
|
(data[j]->kind == 1) |
|
|
|
|
|
|
|||||
|
|
delete [ ] |
data[j]->major; |
|
|
|
|
|
|
||||||
|
delete |
data[j]; } |
|
|
|
|
|
|
/ / |
удаление записи |
|||||
|
return |
0 ; |
|
|
|
|
|
|
|
|
|
|
|
||
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
В этом решении нет ничего сложного или запутанного (за исключением, возмож но, записи указателя). Хотя здесь и используется много ловушек языка С+ + (например, структуры, указатели, динамическое управление памятью в операто рах new и delete, передача параметра по ссылке, библиотечные файловые объек ты), подобную программу можно написать на любом языке. В ней реализовано динамическое связывание (каждый объект обрабатывается в соответствии с его собственным типом). Однако преимуш.ества объектно-ориентированных возмож ностей языка не используются (например, связывание данных и операций вместе, конструкторы и деструкторы, передача обязанностей серверам, наследование и т. д.).
Динамическое связывание: объектно-ориентированный подход
вследуюш,ем ниже варианте программы создадим три класса: Person, Faculty
иStudent. Все возможности, обш^е для обработки данных преподавателей и сту дентов, перейдут в базовый класс Person — поля id, name и kind. Вместо исполь зования чисел для обозначения типа объекта (1 — для преподавателей, 2 — для
