Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
16. Рекурсия функций.
В программировании рекурсия вызов функции (процедуры) из неё же самой, непосредственно (простая рекурсия) или через другие функции (сложная или косвенная рекурсия), например, функция A вызывает функцию B, а функция B функцию A. Количество вложенных вызовов функции или процедуры называется глубиной рекурсии.
Рекурсия см. «Рекурсия».
Рекурсией программисты на своём мунспике называют вызов функцией самой себя прямо (в теле функции расположен вызов себя) или косвенно (функция вызывает функцию, которая вызывает функцию, которая… и в одной из этих функций расположен вызов самой первой функции). Типичным примером функций, использующих рекурсию, являются вычисление факториала и функция Аккермана. В более общем смысле, включение некоторой сущностью самой себя целиком (см. пример с зеркалами ниже).
Помимо своего матанского и программистского значения, рекурсия мем, используемый в наших интернетах в основном среди читателей башорга. Более сложный вариант без гиперссылок: «Чтобы понять рекурсию, нужно сперва понять рекурсию».
18. Динамическая организация данных. Двоичные деревья.
Динамические структуры данных: двоичные деревья
Дерево это совокупность элементов, называемых узлами (при этом один из них определен как корень), и отношений (родительскийдочерний), образующих иерархическую структуру узлов. Узлы могут являться величинами любого простого или структурированного типа, за исключением файлового. Узлы, которые не имеют ни одного последующего узла, называются листьями.
В двоичном (бинарном) дереве каждый узел может быть связан не более чем двумя другими узлами. Рекурсивно двоичное дерево определяется так: двоичное дерево бывает либо пустым (не содержит ни одного узла), либо содержит узел, называемый корнем, а также два независимых поддерева левое поддерево и правое поддерево.
Двоичное дерево поиска может быть либо пустым, либо оно обладает таким свойством, что корневой элемент имеет большее значение узла, чем любой элемент в левом поддереве, и меньшее или равное, чем элементы в правом поддереве. Указанное свойство называется характеристическим свойством двоичного дерева поиска и выполняется для любого узла такого дерева, включая корень. Далее будем рассматривать только двоичные деревья поиска. Такое название двоичные деревья поиска получили по той причине, что скорость поиска в них примерно такая же, что и в отсортированных массивах: O(n) = C • log2n (в худшем случае O(n) = n).
Пример. Для набора данных 9, 44, 0, 7, 10, 6, 12, 45 построить двоичное дерево поиска.
Согласно определению двоичного дерева поиска число 9 помещаем в корень, все значения, меньшие его на левое поддерево, большие или равные на правое. В каждом поддереве очередной элемент можно рассматривать как корень и действовать по тому же алгоритму. В итоге получаем
Поскольку определение двоичного дерева рекурсивно, то все указанные типовые операции могут быть реализованы в виде рекурсивных подпрограмм (на практике именно такой вариант чаще всего и применяется). Отметим лишь, что использование рекурсии замедляет работу программы и расходует лишнюю память при её выполнении.
Основные принципы ООП. Конструкторы и деструкторы.
19
В настоящее время в программировании существуют четыре принципиально отличающихся подхода:
· процедурный (алгоритмический),
· логический (декларативный),
· функциональный,
· объектно-ориентированный.
Объектно-ориентированный подход в разработке программ [1012] появился позже других. Его появление стало следствием все возрастающей сложности программного обеспечения, необходимости его регулярного обновления из-за постоянно изменяющихся требований к программам и условиям их функционирования. Именно объектно-ориентированный подход позволяет решать задачи наивысшей сложности, повышает надежность программ, облегчает их модернизацию.
Логическая единица объектно-ориентированной программы объект. Объект в реальной жизни это сущность, обладающая некоторым набором свойств и моделью поведения. Объект в программе представляет собой абстракцию реального объекта, т. е. в программе объект наделяется теми чертами и характеристиками реального времени, которые существенны для решаемой задачи. Второстепенные же характеристики в программную абстракцию не включаются, при этом характеристики одного и того же реального объекта, существенные для одной задачи, оказываются второстепенными в другой задаче. Рассмотрим хорошо знакомый нам объект принтер. Если разрабатывать программу для обслуживания магазина, продающего принтеры, существенными будут модель принтера, цена, габариты, скорость печати, цвет корпуса и т. д. Если рассматривать принтер с точки зрения администратора вычислительной сети, то существенными характеристиками будут логическое имя устройства в сети, его IP-адрес, драйверы, обеспечивающие работу принтера.
Объект в программе имеет имя, ограниченный набор свойств, набор допустимых состояний (т. е. набор конкретных значений свойств), набор методов (операций), определяющих модель поведения объекта (действия объекта).
Пример абстракции:
Модель принтера
Имя:
HP_5P
Набор свойств:
Модель, год выпуска, состояние готовности
Значения свойств:
Модель = "HP 5P",
Год выпуска = 1999,
Состояние готовности=Ready (готов к печати)
Действия объекта:
Печатает
Как видно, эта абстракция включает в себя далеко не полный перечень свойств принтера, но для определенной задачи этого набора будет достаточно. Если задача изменится (усложнится), в модель принтера можно будет добавить недостающие свойства и описания новых действий.
В программе свойства объекта представлены с помощью переменных, а действия объекта с помощью подпрограмм (в C++ функций). Подпрограммы, содержащие описания действий объекта, называются методами. Набор конкретных значений переменных, характеризующих объект, определяет состояние объекта. Объект имеет ограниченный набор допустимых состояний. Например, в нашей модели принтера возможны два состояния: «готов к печати» и «печатает». Остальные свойства задаются для конкретного принтера изначально и не изменяются в процессе работы программы.
Таким образом, объект в объектно-ориентированном программировании (ООП) это совокупность переменных состояния и связанных с ними методов (операций). Эти методы, с одной стороны, определяют, как объект взаимодействует с внешним миром. С другой стороны, методы осуществляют переход объекта из одного состояния в другое, причем метод проверяет, возможен ли такой переход в данной ситуации. Например, принтеру дается команда отпечатать документ. Если принтер находился в этот момент в состоянии «готов к печати», изменит свое состояние на состояние «печатает». Если принтер в момент получения команды печатал другой документ, то он остается в прежнем состоянии «печатает», а наш документ ставится в очередь на печать. Когда принтер закончит печатать один документ, он приступит к печати нашего документа. Отпечатав наш документ, принтер проверит, есть ли еще документы в очереди на печать, и, если очередь пуста, перейдет в режим «готов к печати». Таким образом, в ответ на наше действие принтер сам определил свои дальнейшие действия, и его выбор зависел от предыдущего состояния принтера. Объектно-ориентированный подход позволяет объектам самим решать, как выполнить ту или иную задачу.
В основе разработки программы с использованием методов ООП применяется объектная декомпозиция задачи. Объектная декомпозиция задачи включает в себя разбиение задачи на отдельные объекты, выявление существенных свойств объектов и способов их взаимодействия. Программа представляет при этом совокупность объектов, взаимодействующих между собой.
В процессе декомпозиции задачи могут быть выявлены объекты с одинаковыми наборами свойств и поведением. Например, для программы, моделирующей футбольный матч, необходимы две команды объектов-«футболистов». Каждого игрока матча можно охарактеризовать следующими свойствами: название команды, форма одежды, номер, координаты на поле. Поведение игроков на поле можно описать следующими действиями: стоит, бежит, отдает мяч, получает мяч. Объекты с одинаковым набором свойств и методов образуют класс. Класс основа объектно-ориентированной методологии служит для создания в программе любого количества объектов с заданным набором свойств и заданной моделью поведения. Если проводить параллель с алгоритмической методологией, то класс выступает в роли типа данных, однако это специфический тип, так как кроме данных в класс включаются действия (функции-члены класса, манипулирующие с данными).
Теперь рассмотрим основные термины ООП.
Класс (class) это тип данных, включающий в себя группу данных различных типов (свойства) и методы работы с этими данными.
Объект (object) это экземпляр класса, обладающий набором свойств с заданными значениями.
Свойство (property) характеристика объекта, представленная в виде переменной, являющейся членом класса.
Метод (method) это подпрограмма, входящая в состав класса и управляющая данными объекта.
Событие (event) какое-либо происшествие в программе, системе, например, была нажата кнопка мыши.
Сообщение (message) адресованная конкретному объекту информация о происшедшем событии; объект, получивший сообщение, должен на него ответить своими действиями, вызвав метод, соответствующий этому событию. Например, если получено сообщение о том, что была нажата левая кнопка мыши на пункте меню Exit, произойдет завершение программы, если был выбран пункт меню Save, будет выполнено сохранение информации.
Также к основным терминам ООП нужно отнести такие понятия, как инкапсуляция, наследование и полиморфизм, которые будут рассмотрены далее.
Поскольку C++ объектно-ориентированный язык, он позволяет использовать при разработке программ как готовые классы, доступные через соответствующие библиотеки, так и классы, разработанные пользователем.
Рассмотрим создание класса пользователем и применение класса, созданного пользователем, в программе.
Объявление класса:
class <имя класса>
{
public:
<объявление переменных, констант, функций>
private:
<объявление переменных, констант, функций>
protected:
<объявление переменных, констант, функций>
};
Объявление класса начинается с ключевого слова class. Переменные, константы, функции, включенные в класс, называются членами класса. Члены класса объявляются в одном из трех разделов класса: public, private, protected. Включение члена класса в тот или иной раздел влияет на доступность этого члена для объектов других классов.
Раздел public обеспечивает доступность помещенных в него свойств и методов для любых объектов. Это внешняя часть класса, или интерфейс его взаимодействия с другими классами.
Раздел private обеспечивает сокрытие помещенных в него свойств и методов от других объектов, делая их недоступными объектам других классов. Таким образом, класс защищает свои данные (свое состояние) от внешнего воздействия. Посторонний объект может воздействовать на объект данного класса только через интерфейс этого класса.
Раздел protected позволяет, используя механизм наследования, передавать включенные в него члены класса по наследству.
Рассмотрим в качестве примера класс принтеров. Включим в его состав следующие свойства: модель, год выпуска и состояние принтера; спрячем эти свойства от постороннего воздействия, поместив в раздел private. Ограничимся двумя возможными состояниями принтера: 0 принтер готов к работе, 1 принтер печатает. Методы сделаем доступными для других объектов. Метод init_printer() позволит установить начальные значения свойствам принтера. Метод set_print() переведет принтер в состояние печати, если принтер до того находился в состоянии готовности, и не изменит его состояния, если принтер уже печатал. Метод stop_print() приводит к остановке печати. Метод show() отображает состояние принтера на экране:
class Printer
{
private:
char model[15]; //модель принтера
int year; //год выпуска
int status; //состояние принтера
public:
void init_printer(char* _model, int _year);
void set_print();
void stop_print();
void show();
};
Замечание. Если раздел private включен в объявлении класса первым, ключевое слово private можно опустить:
class Printer
{
char model[15]; //модель принтера
int year; //год выпуска
int status; //состояние принтера
public:
void init_printer(char*_model,int_year);
void set_print();
void stop_print();
void show();
};
Описание методов:
void Printer::init_printer(char* _model,int _year)
{
strcpy(model,_model); //инициализация свойства model
year=_year; //инициализация свойства year
status=0; //начальное состояние готов к печати
}
void Printer :: set_print()
{
if (!status) status=1;
}
void Printer :: stop_print()
{
status = 0;
}
void Printer :: show()
{
cout<<"Model:"<<model<<"year:"<<year<<"status:"<<status<<endl;
}
В главную часть программы функцию main() включим следующие действия: создание объекта, инициализация свойств объекта, изменение состояния объекта и вывод его текущего состояния на экран:
int main(void)
{
Printer printer; //создание объекта
//инициализация свойств объекта
printer.init_printer("HP5P", 1999);
printer.show(); //вызов метода объекта
printer.set_print();
printer.show();
printer.set_print();
printer.show();
printer.stop_print();
printer.show();
return 0;
}
В результате работы программы на экране появятся 4 строки текста:
Model: HP5P year: 1999 status: 0,
Model: HP5P year: 1999 status: 1,
Model: HP5P year: 1999 status: 1,
Model: HP5P year: 1999 status: 0.
17.3. Конструкторы и деструкторы
Для создания объекта используется специальный метод, называемый конструктором. Узнать конструктор несложно его имя совпадает с именем класса и для него не указывается тип возвращаемого значения.
Конструктор включается в состав класса в разделе public:
class Printer
{
…
public:
Printer(); //Конструктор
…
};
Конструктор выделяет память под объект, достаточную для размещения всего объекта, и инициализирует переменные состояния объекта:
Printer :: Printer()
{
strcpy(model,"Canon_BJC250");
year=2000;
status=0;
}
Тогда программа, создающая объект и отображающая на экране его содержимое, будет выглядеть так:
int main(void)
{
Printer printer; //Конструктор инициализирует
//переменные объекта
printer.show();
return 0;
}
При выполнении этой программы на экран будет выведено сообщение:
Model: Canon_BJC250 year: 2000 status: 0
Нетрудно заметить, что используя этот конструктор для создания нескольких объектов, получаем объекты с одинаковыми значениями свойств.
Чтобы получать каждый раз объекты с новыми значениями свойств, используется конструктор с параметрами:
class Printer
{
…
public:
//Конструктор с параметрами
Printer(char* _model, int _year);
…
};
где _model имя параметра, соответствующего значению модели, _year имя параметра, соответствующего значению года.
Замечание. Имена параметрам могут быть даны любые, но удобно, когда имена параметров совпадают с названиями соответствующих свойств, отличаясь от них лишь первым символом _ (знак подчеркивания). Такой прием позволяет не запутаться при написании конструктора. Однако применять его можно лишь в том случае, если компилятор позволяет ставить знак _ в начале идентификатора.
Теперь конструктор будет выглядеть так:
Printer :: Printer(char* _model, int _year)
{
strcpy(model, _model);
year=_year;
status=0;
}
Воспользуемся конструктором с параметрами для создания в программе двух объектов с различными свойствами:
int main(void)
{
//Создание первого объекта
Printer printer1("HP5P",1999);
printer1.show();
//Создание второго объекта
Printer printer2("Canon_BJC250",2000);
printer2.show();
return 0;
}
При выполнении этой программы на экран будет выведено сообщение:
Model: HP5P year: 1999 status: 0
Model: Canon_BJC250 year: 2000 status: 0
В состав класса может быть включено несколько конструкторов, что обосновано таким свойством языка C++, как перегрузка функций. Конструкторы обязательно должны отличаться количеством или типами параметров, чтобы компилятор мог подобрать конструктор, соответствующий создаваемому объекту.
Класс может не содержать ни одного конструктора, в этом случае для создания объекта создается конструктор по умолчанию, который выделяет память под объект, но при этом не инициализирует переменные.
Для разрушения объекта используется метод, называемый деструктором. Для деструктора, также как и конструктора, не указывается тип возвращаемого значения, имя совпадает с именем класса, только перед именем помещается знак ~ (тильда).
Деструктор не имеет параметров и включается в раздел public:
class Printer
{
…
public:
Printer(char* _model, int _year); //Конструктор //с параметрами
~Printer(); //Деструктор
…
};
Деструктор может отсутствовать в составе класса, тогда для разрушения объекта создается деструктор по умолчанию. Деструктор обязательно должен быть включен в класс, если какие-либо свойства класса помещаются в динамической памяти.
Рассмотрим пример с классом Printer, где одно из свойств (название модели) помещается в динамической памяти:
class Printer
{
char* model; //указатель на строку, которая будет
//содержать название модели и разместится
//в динамической памяти
int year; //год выпуска
int status; //состояние принтера
public:
Printer(char* _model, int _year);
~Printer();
void set_print();
void stop_print();
void show();
};
Конструктор:
Printer :: Printer(char* _model, int _year)
{
int len=strlen(_model); //Определение длины
//строки
//Выделение памяти в динамической области
//для размещения строки и символа '\0'
model=new char[len+1];
strcpy(model, _model);
year=_year;
status=0;
}
Деструктор:
Printer :: ~Printer()
{
//Освобождение динамической памяти
delete[] model;
//Присваивание указателю значения пустого
//указателя, обязательно в Visual C++ 6.0
model = NULL;
}
Тогда программа, выполняющая все этапы работы с объектом, будет выглядеть так:
int main(void)
{
Printer printer("HP5P",1999); //Создание объекта
printer.show();
return 0;
}
Явный вызов деструктора не требуется, так как его вызов будет выполнен автоматически в точке программы, где объект должен быть разрушен в соответствии с его временем жизни.
К началу главы
17.4. Инкапсуляция
Инкапсуляция это принцип ООП, объединяющий в одном классе данные и методы, манипулирующие этими данными и защищающие данные класса от внешнего воздействия.
Обратимся к примеру из жизни, чтобы понять смысл этого принципа. Представьте себе, что в вашу квартиру пришел знакомый и переставил вашу мебель по своему усмотрению, не считаясь с вашим вкусом. Скорее всего, вам это не понравится, и вы перестанете приглашать этого человека в свой дом. Если же вы для перестановки мебели позовете помощников, то сначала объясните им, что и как надо переставить, тогда в результат их работы вы получите квартиру, обустроенную так, как это нужно вам.
Класс также защищает свое состояние (значения свойств) от несанкционированного изменения, это реализуется с помощью раздела класса private. Включенные в private свойства доступны только этому объекту, поэтому не могут быть изменены другими объектами. Для доступа к свойствам в раздел класса public включаются методы. Методы проверяют, возможно ли такое изменение состояния объекта под влиянием других объектов, и только после этого изменяют значения переменных состояния. Если переход в новое состояние невозможен, методы оставляют объект в прежнем состоянии.
Изменим пример с принтером. Предположим, во время профилактики вычислительной системы у принтера было отключено электропитание. Естественно, в этом случае наши попытки что-то напечатать ни к чему не приведут. Учтем это в нашей модели принтера. Добавим свойство is_on, отражающее подключение к принтеру электрического питания (0 включено, 1 выключено). Тогда состояние принтера определяется комбинацией значений двух свойств is_on и status (табл. 17.1).
Таблица 17.1
Допустимые состояния принтера
is_on
status
Сообщение на экране
0
0
Принтер включен
1
0
Принтер включен Состояние: готов к работе
1
1
Принтер включен Состояние: печатает
При этом класс представляется в виде:
class Printer
{
char model[15];
int year;
int status;
int is_on; //Принтер включен (0 нет,1 да)
public:
Printer(char* _model, int _year);
void on_off(); //Включение/выключение питания
void set_print();
void stop_print();
void show();
};
//Конструктор
Printer :: Printer(char* _model, int _year)
{
strcpy(model, _model);
year=_year;
status=0;
is_on=0; //Принтер отключен по умолчанию
}
void Printer :: on_off()
{
//Метод моделирует нажатие кнопки включения
//питания:если оно выключено, то произойдет
//включение, и наоборот;
//одновременно при любых переключениях питания //принтер оказывается в состоянии готовности
//и печать не ведет
is_on=!is_on;
status=0;
}
void Printer :: set_print()
{
If (is_on) status=1; //Принтер будет печатать,
//если он включен
}
void Printer :: stop_print()
{
If (status) status=0; //Принтер остановится,
//если он до того печатал
}
void Printer :: show()
{
cout << "Модель: " << model << "Год выпуска: " << year << endl;
if (is_on)
{
cout << "Принтер включен";
if (status) cout << "Состояние: печатает";
else cout << "Состояние: готов к работе";
}
else cout << "Принтер выключен";
cout << endl;
}
Класс запрещает прямой доступ к своим переменным состояния, поэтому не удастся реализовать недопустимое состояние is_on=0, status=1. Методы set_print(), stop_print(), on_off(), предоставляющие доступ к свойствам объекта, реализованы так, что также исключают это состояние объекта. В этом и заключается смысл понятия инкапсуляции.
К началу главы
17.5. Полиморфизм
Полиморфизм это принцип ООП, позволяющий включать в состав класса несколько функций с одинаковыми именами, но выполняющих различные действия. Такие функции должны иметь различные списки параметров, они должны отличаться или количеством параметров в списках, или типами соответствующих параметров. Полиморфизм в ООП есть проявление свойства языка C++, называемого перегрузкой функций (п. 16.4).
Пример полиморфизма класс графических примитивов Draw. Класс может проявить себя как текст, прямоугольник или окружность. Для этого в класс включены три метода show(), различающиеся параметрами.
Компилятор, обрабатывая вызов функции, сопоставляет список фактических параметров со списками формальных параметров всех методов класса, имеющих то же имя, и выбирает ту функцию, у которой список формальных параметров совпал со списком фактических параметров при вызове функции:
#include <string.h>
#include <iostream.h>
class Draw
{
int x1, y1, x2, y2, r;
char *message;
public:
Draw();
virtual ~Draw();
void show();
void show(int xx1, int yy1, int xx2, int yy2);
void show(int xx1, int xx2, int rr);
};
Draw::Draw()
{
x1=x2=y2=y1=r=0;
message=new char[10];
strcpy(message, "text");
}
Draw::~Draw()
{
delete[] message;
message=NULL;
}
void Draw::show()
{
cout << message << endl;
}
void Draw::show(int xx1,int yy1,int xx2,int yy2)
{
cout << "Rectangle" << endl;
}
void Draw::show(int xx1,int xx2,int rr)
{
cout << "Circle" << endl;
}
int main(void)
{
Draw draw;
draw.show();
draw.show(1,1,20,10);
draw.show(10,10,10);
return 0;
}
В результате выполнения программы на экране появляются сообщения:
Text
Rectangle
Circle
К началу главы
17.6. Наследование
Наследование это принцип ООП, посредством которого на базе существующих классов создаются новые классы, получающие по наследству от базовых классов свойства и методы. Наследование позволяет существенно экономить программный код.
Созданные с использованием механизма наследования новые классы называются классами-наследниками или классами-потомками.
//Объявление базового класса
class A
{
… //Члены класса A
};
//Объявление класса-наследника
class B : public A
{
… //Собственные члены класса B
};
Новые классы наследуют только те свойства и методы, которые объявлены в разделах public и protected базовых классов. При этом свойства и методы из раздела protected доступны только базовому классу и его наследникам, тогда как свойства и методы из раздела public доступны объектам любого класса (рис. 17.1). Исключением из этого правила являются конструкторы и деструкторы, которые не наследуются.
Рис. 17.1. Уровни доступа к членам класса
В состав класса B входят собственные члены и члены класса A из разделов public и protected.
Рассмотрим пример наследования, в качестве базового класса возьмем класс person, характеризующий любого человека, и включим в раздел protected свойство класса name имя человека:
class person
{
protected:
char name[20];
};
Создадим на основе класса person класс-наследник student:
class student : public person
{
char department[20]; // факультет
public:
student(char* _name, char* _department);
void message();
};
//Конструктор
student :: student(char* _name, char* _department)
{
strcpy(name, _name);
strcpy(department, _department);
}
//message метод, сообщающий сведения об объекте
void student::message()
{
cout << "My name is " << name << ". I study at " << department << endl;
}
Как видно, методы класса student используют как собственное свойство, так и свойство, полученное по наследству.
Создание объекта класса student и вызов его метода ничем не отличаются от того, как это делается для класса без наследования:
int main(void)
{
student N("Nick", "SS,SK,VT");
N.message();
return 0;
}
Во время выполнения такой программы на экране появляется сообщение:
My name is Nick. I study at SS, SK, VT.
Теперь рассмотрим наследование методов базового класса, включим в базовый класс конструктор, инициализирующий собственное свойство базового класса значением "Noname", и метод message, сообщающий информацию о классе:
class person
{
protected:
char name[20];
public:
person();
void message();
};
person :: person()
{
strcpy(name, "Noname");
}
void person::message()
{
cout << "My name is " << name << endl;
}
Таким образом, в класс-наследник student теперь включены два метода message, имеющие одинаковый тип и одинаковый список параметров один из базового класса, другой собственный. Собственный метод доступен через имя объекта. Однако можно получить доступ к унаследованному методу, если использовать для доступа к методу указатель соответствующего типа указатель на базовый класс:
int main(void)
{
person A;
student B("Ann", "SS,SK,VT");
A.message();
B.message();
person* pperson = &B;
pperson->message();
student* pstudent = &B;
pstudent->message();
return 0;
}
На экране увидим сообщения:
Noname Сообщение об= объекте A
Ann SS,SK,VT Сообщение об объекте B (используется
собственный метод message)
Ann Сообщение об объекте B (используется
метод message, полученный по наследству)
Ann SS,SK,VT Сообщение об объекте B (используется
собственный метод message)
В С++ можно избежать получения по наследству метода, если метод с таким же именем включен в класс-потомок, для этого используется механизм виртуальных функций.
Виртуальная функция - это функция, объявленная в базовом классе с помощью ключевого слова virtual, такая функция в классах-потомках замещается на функцию, принадлежащую производному классу и имеющую то же имя:
class person
{
protected:
char name[20];
public:
person();
virtual void message();
};
Теперь результатом выполнения программы будут следующие сообщения:
Noname Сообщение об объекте A
Ann SS,SK,VT Сообщение об объекте B (используется
собственный метод message)
Ann SS,SK,VT Сообщение об объекте B (используется
собственный метод message)
Ann SS,SK,VT Сообщение об объекте B (используется
собственный метод message)
С помощью виртуальных функций можно создать класс-наследник, имеющий тот же интерфейс, что и базовый класс, но обладающий своей собственной моделью поведения. Механизм виртуальных функций реализуется следующим образом: обычно обработка вызовов функций выполняется на этапе компиляции и завершается на этапе редактирования связей, когда вызов метода жестко связывается с соответствующей функцией (раннее связывание); если метод объявлен как виртуальный, выполняется так называемое позднее связывание, т. е. связывание вызова и функции во время выполнения программы.
К началу главы
17.7. Виды взаимодействия классов
На этапе создания объектной модели задачи необходимо выявить как сами объекты, так и связи между ними, которые следует учитывать при конструировании классов.
Можно выделить четыре вида взаимодействия классов:
· наследование,
· агрегация (включение),
· использование,
· ассоциация.
Объектную модель задачи удобно представлять графически в виде набора диаграмм, иллюстрирующих задачу с разных сторон. Один из видов диаграмм диаграмма классов.
Рис. 17.2. Обозначение класса на диаграммах:а подробное, б упрощенное
Базовый элемент таких диаграмм класс изображается в виде прямоугольника, разделенного на три ячейки: в верхнюю помещается название класса, в среднюю свойства, в нижнюю методы. На рис. 17.2, а представлено графическое изображение класса Printer. Допускается также упрощенное обозначение класса (рис. 17.2, б) на диаграммах: указывается только название класса без перечисления его свойств и методов.
Связи между классами обозначаются прямыми линиями. Для каждого вида взаимодействия используются определенные типы линий (рис. 17.3).
Рассмотрим виды взаимодействия классов.
Наследование, т. е. создание нового класса на базе существующего, рассмотрено выше. Класс-наследник также может иметь наследников, его наследники, в свою очередь, также могут иметь наследников, и так далее, в результате может быть выстроена иерархическая структура дерево классов. Основу любой библиотеки классов (объектов), объектной модели программы, например, браузера, составляет иерархия классов. Пример графического представления наследования приведен на рис. 17.4.
Рис. 17.3. Обозначения вызовов взаимодействия классов
Рис. 17.4. Диаграмма наследования
Агрегация включение объектов одного класса в состав другого класса; cвязь однонаправленная (рис. 17.5).
Код программы, отражающий агрегацию классов, следующий:
class A
{ …
void met_A();
…
}
class B
{ …
A b1; //В состав класса B включен объект класса A
…
}
Рис. 17.5. Диаграмма агрегации классов
Включенный объект b1 не существует самостоятельно, он создается внутри объекта класса B. Доступ к включенному во внешний класс объекту осуществляется только через объект внешнего класса:
B objB;
objB.b1.met_A();
Использование или клиент-серверное взаимодействие это однонаправленная связь двух самостоятельных объектов, один из которых (сервер) предоставляет свои услуги другому (клиенту). Клиент-серверное взаимодействие (рис. 17.6) осуществляется через использование в методах клиента обращения к объекту-серверу.
Если класс A выступает в роли сервера, а класс B клиента, то объект класса A передается в методы класса B в виде значения или адреса:
class A
{
…
}
class B
{
<return_type> met_B(A* a) ;
…
}
Рис. 17.6. Диаграмма использования
(клиент-серверного взаимодействия)
Объект класса B использует ресурсы объекта класса A и может изменить состояние объекта класса A (рис. 17.6)
Ассоциация это двусторонняя связь классов, которая устанавливается между самостоятельными объектами, причем и со стороны одного класса, и со стороны другого класса в связи могут участвовать несколько объектов. Например, классы студентов и преподавателей каждый студент посещает занятия нескольких преподавателей, в то же время каждый преподаватель проводит занятия с группой студентов. Количество объектов, участвующих в связи с обеих сторон, называется мощностью связи.
Классы, с которыми устанавливается ассоциативная связь, должны иметь переменные-указатели на объекты ассоциированного класса:
class A
{
B* varA; //Указатель на один объект класса B
…
}
class B
{
A** varB; //Указатель на массив объектов класса A
…
}
В этом примере со стороны класса B в связи участвует один объект, а со стороны класса A несколько объектов (рис. 17.7).
Рис. 17.7. Диаграмма ассоциации классов:
1, N - количество объектов,
участвующих в связи от класса B и A соответственно
В зависимости от количества объектов, участвующих в связи с обеих сторон, различают три типа ассоциации: один-к-одному, один-ко-многим (этот тип выбран для примера), много-ко-многим.
Ассоциативная связь используется при построении реляционных баз данных.
В заключение рассмотрим диаграмму классов, отображающую объектную модель рабочего места пользователя (рис. 17.8).
Рис. 17.8. Диаграмма классов рабочего места пользователя
На диаграмме представлены пять классов, между которыми установлены различные виды взаимодействия. Класс Пользователь представляет собой набор настроек пользователя и взаимодействует с классами Компьютер и Принтер. Между Пользователем и Компьютером устанавливается ассоциативная связь один-ко-много, так как представители обоих классов независимые объекты, общение между объектами двунаправленное, а количество пользователей, работающих на компьютере, может быть любым. Между Пользователем и Принтером установлено клиент-серверное взаимодействие, так как оба объекта существуют независимо друг от друга, направление их взаимодействия одностороннее от Пользователя к Принтеру. Классы Принтер и Компьютер представляют собой реальные устройства, имеющие некоторые общие свойства. Эти общие характеристики выделены в абстрактный класс Устройство, использующийся в качестве базового при создании классов Принтер и Компьютер. Абстрактный класс имеет особое назначение: он предназначен для создания классов-наследников и не используется в программе для создания объектов. На диаграмме классов абстрактный класс обозначен буквой А. Пятый класс Картридж представляет собой составную часть Принтера и не является самостоятельным объектом, поэтому между этими двумя классами выбран тип взаимодействия агрегация (включение).
К началу главы
17.8. Способы графического представления
объектно-ориентированной задачи
В объектно-ориентированном анализе можно выделить три группы диаграмм [13].
Диаграмма сущность-связь ERD-диаграмма (Entity-Relationship Diagram). Примером таких диаграмм могут служить диаграммы классов, простейшие примеры которых приведены на рис. 17.4 рис. 17.8, а также диаграммы объектов, модулей. Диаграммы демонстрируют статические связи между однотипными сущностями (классами, объектами и т. д.). Сущности на такой диаграмме изображаются прямоугольником, линии, соединяющие прямоугольники, обозначают связи между сущностями.
Диаграмма потоков данных DFD-диаграмма (Data Flow Diagram). Пример такой диаграммы хорошо знакомые нам блок-схемы алгоритмов. Узлы такой диаграммы (прямоугольники, ромбы, параллелограммы и т. д.) обозначают отдельные действия над данными или подпрограммы, линии, соединяющие их, обозначают направление потока данных и, соответственно, последовательность выполнения действий.
Диаграммы переходов состояний STD-диаграммы (State Transition Diagram). Узлы такой диаграммы представляют собой допустимые состояния объекта, а соединяющие их линии возможность и направление перехода из одного состояния в другое. На диаграмме также могут быть отмечены события, вызывающие данный переход, или соответствующие им методы. Кроме того, на диаграмме можно отметить последовательность переходов, тогда диаграмма будет также описывать поведение объекта.
Для примера рассмотрим объект принтер. Ранее у объекта были выявлены два свойства, изменяющие свои значения во время использования объекта is_on (обозначающее, включен ли принтер) и status (определяющее, печатает принтер в данный момент или нет) и три допустимых состояния, исключив из допустимых состояний возможность печати при выключенном принтере (рис. 17.9).
Рис. 17.9. STD-диаграмма переходов
Такие диаграммы основа современного языка, предназначенного для описания объектно-ориентированной задачи языка UML (Unified Modeling Language универсальный язык моделирования) [11, 14].
К началу главы
17.9. Особенности реализации объектно-ориентированных программ
на языке С++ в различных средах
Если вы решили написать объектно-ориентированную программу, нужно позаботиться о подходящей среде программирования. Выбор невелик и зависит от операционной системы, в которой будет работать ваше приложение.
Универсальной средой, позволяющей создавать объектно-ориентированные программы для MS-DOS и 16-разрядные приложения под Windows, остается продукт Borland C++ 3.1 фирмы Borland. В этой среде можно создавать программы, весь код которых сосредоточен в одном файле, а также можно создавать проекты.
Более новые продукты Borland C++ 5.x предназначены для создания полноценных приложений для Windows 9.x. Среда имеет элементы автоматизации работы программиста средства для модульного построения программы и создания каркаса проекта, графический редактор для визуального редактирования диалоговых панелей, меню, подключаемые библиотеки BWCC (библиотека управляющих элементов), OWL (иерархическая библиотека классов, предназначенных для создания элементов графического интерфейса приложения), MFC (библиотека классов фирмы Microsoft). Возможно создание не только приложения (*.exe), но и динамической библиотеки (*.dll). Разработка программы ведется только на основе проекта.
Еще более новый и совершенный продукт C++ Builder обладает достоинствами предыдущей среды, а также формирует вспомогательную часть кода программы (создает заготовку для кода класса, функции), позволяет использовать современные библиотеки, а также имеет мощный и удобный инструмент для настройки свойств элементов интерфейса пользователя программы (диалоговые панели, кнопки и т. д.). Разработка программы ведется только на основе проекта.
Фирма Microsoft также создала современную среду для разработки 32-разрядных приложений под Windows Microsoft Visual C++, аналогичную по своим возможностям продукту Borland C++ Builder. Разработка программы ведется только на основе проекта.
Microsoft Visual C++ 6.0 входит в состав интегрированной среды разработки Microsoft Visual Studio, позволяющей создавать приложения различного назначения и с использованием различных языковых средств, включающей в себя следующие компоненты для разработки приложений и баз данных: Visual C++, Visual Basic, Visual J++, Visual FoxPro.
Автоматическую генерацию части кода программы выполняют два приложения: AppWizard (Мастер приложений) создает каркас проекта и MFC ClassWizard (Мастер классов) генерирует служебную часть кода класса.
В дополнение к Microsoft Visual Studio как отдельный продукт создана справочная система по программированию с помощью компонентов Microsoft Visual Studio MSDN (Microsoft Developers Network).
Новейший продукт фирмы Microsoft Visual Studio.NET. Этот продукт стал преемником Visual Studio, при этом в его состав включены новые версии компиляторов. Так, компилятор С++ в среде NET в большей степени соответствует современному стандарту языка С++, использует новую версию стандартной библиотеки С++, весьма требователен к совместимости типов данных. Добавлены новые и усовершенствованы прежние мастера для автоматической генерации кода программы: Application Wizard, MFC Class Wisard, Event Handler Wizard (Мастер редактирования обработчиков сообщений), Add Member Variable Wizard (Мастер добавления функции) и т. д.
http://dvo.sut.ru/libr/cvti/i618buz/17.htm