- •Раздел 4. Разработка по Тема 4.1. Проектирование интерфейса с пользователем
- •4.1.1. Типы пользовательских интерфейсов.
- •4.1.2. Пользовательская и программная модели интерфейса.
- •4.1.3. Разработка диалогов.
- •4.1.4. Основные компоненты графических пользовательских интерфейсов.
- •Тема 4.2. Реализация графических пользовательских интерфейсов.
- •4.2.1. Диалоги, управляемые пользователем.
- •4.2.2. Диалоги, управляемые системой.
- •4.2.3. Использование метафор.
- •4.2.4. Технология Drag and Drop.
- •4.2.5. Интеллектуальные элементы.
- •4.3.1. Базовые типы данных.
- •Константы
- •Область действия имен
- •4.3.2. Указатели и адресная арифметика.
- •4.3.3. Составные типы данных. Структуры
- •Битовые поля
- •Определение типов
- •Перечислимые типы
- •4.3.4. Выражения и операции.
- •4.3.5. Управляющие конструкции. Условные операторы
- •Операторы циклов
- •4.4.1. Статические одномерные массивы.
- •4.4.2. Статические многомерные массивы.
- •4.4.3. Динамические массивы.
- •4.4.4. Массивы указателей.
- •4.5.1. Стеки.
- •4.5.2. Очереди.
- •4.5.3. Списки.
- •4.5.4. Бинарные деревья.
- •4.6.1. Объявление классов и экземпляров классов.
- •4.6.2. Инкапсуляция данных и методов.
- •4.6.3. Конструкторы классов.
- •Конструктор по умолчанию
- •Конструктор копирования
- •4.6.4. Деструкторы классов.
- •4.7.1. Разделы в описании класса.
- •4.7.2. Friend-конструкции.
- •4.7.3. Статические члены классов.
- •4.7.4. Использование описателя const в классах.
- •4.8.1. Вложенность классов.
- •4.8.2. Наследование данных и методов.
- •4.8.3. Типы наследования.
- •4.9.1. Полиморфизм раннего связывания.
- •4.9.2. Полиморфизм позднего связывания и виртуальные функции.
- •4.9.3. Абстрактные методы и классы.
- •4.10.1. Функции консольного ввода-вывода.
- •4.10.2. Функции файлового ввода-вывода.
- •4.10.3. Использование библиотеки классов потокового ввода-вывода.
- •4.11.1. Перегрузка операций.
- •4.11.2. Шаблоны функций.
- •4.11.3. Шаблоны классов.
- •4.11.4. Обработка исключений.
- •Тема 4.12. Com-технология.
- •4.12.1. Основные понятия.
- •4.12.2. Типы интерфейсов.
- •Свойства интерфейсов
- •Типы интерфейсов
- •4.12.3. Типы com-объектов.
- •4.12.4. Фабрика классов.
- •Тема 4.13. Построение com-сервера.
- •4.13.1. Язык idl.
- •Содержимое файла idl
- •4.13.2. Определение пользовательского интерфейса.
- •4.13.3. Реализация пользовательского интерфейса.
- •4.13.4. Создание тестового клиента.
- •Тема 4.14. Обзор платформы ms .Net.
- •4.14.1. Общая идея архитектуры .Net.
- •4.14.2. Достоинства и недостатки .Net.
- •4.14.3. Схема трансляции программ в .Net.
- •4.14.4. Язык msil.
- •4.14.5. Объектно-ориентированная модель .Net.
4.6.1. Объявление классов и экземпляров классов.
В ООП каждый объект принадлежит определенному классу. Класс — это основной инструмент, посредством которого осуществляется инкапсуляция и скрытие данных. Если описан класс, то определены характеристики всех объектов, принадлежащих этому классу. Функции, инкапсулированные в классе и предназначенные для выполнения каких-либо операций с данными любого из объектов этого класса, принято называть методами класса.
Начнем рассмотрение класса со структуры (struct), которая в C++ является частным случаем класса и может содержать не только данные, но и методы. Данные и методы, описанные внутри структуры, по умолчанию являются доступными извне. В применении к данным или методам класса описатель public означает их доступность извне, а описатель private обозначает скрытые, недоступные извне данные и методы. Для структуры описатель public принят по умолчанию. При этом отсутствует скрытие данных, но есть инкапсуляция. В общем случае при объявлении класса в нем могут быть инкапсулированы данные и методы как доступные, так и не доступные извне. Для обеспечения доступности члены класса должны располагаться под влиянием описателя public, или, по-другому, находиться в секции public.
В качестве примера рассмотрим описание структуры, которая объединяет минимальный набор данных, характеризующий абстрактного человека. Для этого зададим имя типа структуры, например, Man. Выберем поля данных, характеризующих абстрактного человека, то есть любого представителя класса (структуры) Man. Пусть это будут имя (char* Name) и возраст (uint Age). Дополним структуру функциями (методами), позволяющими производить манипуляции с объектами типа Man. Минимальный набор методов, очевидно, должен позволять вводить и выводить данные, характеризующие человека. Пусть метод void in(); служит для ввода данных структуры, а метод void out(), — для вывода.
typedef unsigned int uint; //Новый тип данных (для удобства) char buff[256];
struct Man // Тип структуры
{
char* Name; // Для данных
uint Age;
void in()
{ // Метод (функция) ввода
puts ("\n\tType Man\n Name: "):
gets (buff);
Name = strcpy(new char[strlen(buff)+l], buff);
puts ("\n Age: ");
scanf ("%d",&Age);
}
// Метод (функция) вывода
void out ()
{
printf ("\n %s,\t Age:%d",Name,Age);
}
};
void main()
//=== Демонстрация использования структуры -
Man m; // Объект m типа Man
m.in();
m.out (); // Ввод, вывод данных
m.Age = -2000; //Доступ к полю данных (извне)
m.out (); // Поле искажено }
4.6.2. Инкапсуляция данных и методов.
В ООП наряду с вызовами обычных внешних функций принято говорить о передаче сообщений объектам классов. Сообщением считается имя того или иного метода. Методом принято называть функцию — член класса в отличие от обычной внешней функции. В нашем примере внутри функции main объявлен объект (структура) m типа Man, который имеет весь набор полей, присущих любой структуре типа Man. В main демонстрируется, как происходит передача сообщения объекту. Так, запись m.in(); означает, что структуре m типа Man передается сообщение in(), которое является именем метода, определенного, а следовательно, и легального для структур типа Man. Внутри метода in данные класса доступны прямо и могут быть модифицированы. Причем эти данные принадлежат тому объекту (представителю) класса, которому было послано сообщение. Метод in позволяет изменить значение данных структуры m. В то же время любое поле этой структуры может быть изменено обычным для структуры способом простого присвоения, как это сделано в операторе m.Age = -2000;. Отметим, что если в функции main пользоваться указателем на класс (например Man m, *p=&m;), то присвоение должно выглядеть так: р->Аge=-2000;. Вместо символа «точка» необходимо использовать другую операцию выбора — два символа («минус» и «больше»), образующие стрелку—указание на объект. Рассмотренный метод обращения к функции, на первый взгляд, кажется просто вычурным способом производить по смыслу те же действия, что и в структурном подходе. Однако мы еще ничего не успели показать из преимуществ ООП. В примере продемонстрировано использование только одной концепции ООП, а именно инкапсуляции. В одну капсулу — тип структура Man — заключены данные char* Name; uint Age; и функции (методы) void in(); void out();.
Как функции — члены структуры Man, так и ее данные доступны внутри функции main. Они также были бы доступны внутри любой другой обычной функции, которая могла бы быть создана и находиться в той же области видимости. Здесь нет скрытия данных. Оператор m.Age = -2000; демонстрирует, как намеренно или по ошибке можно изменить содержимое поля объекта (структуры) m. Этот вариант доступа к полю возможен потому, что все данные структуры по умолчанию имеют тип public, что означает их доступность непосредственно из любой функции, где объявлена и действует структура типа Man. Заметим, что поле Age структуры т принимает недопустимое значение и эту ошибку компилятор, конечно же, не видит. Именно этого стремились избежать, создавая концепцию ООП. В приведенном примере не использована возможность скрытия данных, которая является выражением второго базового понятия ООП. Теперь рассмотрим, как следует поступать в соответствии с концепцией ООП. Определим класс и заставим работать в нашу пользу скрытие данных, осуществляемое в его рамках:
class Man // Класс объектов
{
private: // Секция закрытого доступа
char* Name;
uint Age; // Скрытые внутренние данные
public: // Секция открытого доступа
void in ()// Метод класса
// Здесь те же коды, что и ранее
{ // Метод (функция) ввода
puts ("\n\tType Man\n Name: ");
gets (buff);
Name = strcpy(new char[strlen(buff)+1], buff);
puts ("\n Age: ");
scanf ("%d",&Age);
}
void out ()
{ // Метод класса
printf ("\n %s,\t Age:%d",Name,Age);
}
};
void main()
{
//==Демонстрация использования класса ====// Man m; // Объект m класса Man m.in(); m.out (); // Ввод, вывод данных m.Age = -2000; //Этот оператор ошибочен и ошибка
// выявляется на стадии компиляции
}
Описатели (спецификаторы) секций private и public определяют данные методы, имеющие соответствующий тип доступа. Переменные Name и Age, снабженные описателем private, доступны только внутри методов класса. Теперь уже исключена возможность присвоения Age=-2000; в функции main, так как Аge — это внутреннее private-данное любого объекта класса Man и доступ к нему возможен только с помощью методов, заданных в модуле описания класса. Оператор m.Age=-200 вызовет сообщение об ошибке на стадии компиляции. Теперь стало невозможным изменение значения внутреннего (private) поля данных незаконным способом, то есть минуя законные методы, инкапсулированные в классе. Законным способом считается доступ к данным с помощью методов класса. Поле Age недоступно функции main напрямую, но оно доступно с помощью метода in. Важен факт, что ошибка обнаруживается на этапе компиляции, а также то, что правила игры автоматически соблюдаются для любого объекта класса. Функцию in следует разработать так, чтобы исключить возможность присвоения полям данных класса недопустимых значений. В нашем примере этого не сделано, так как мы стремились минимальными средствами показать возможности инкапсуляции и скрытия данных. Надежность ввода поля Age, конечно, будет выше, если при написании кода тела метода in использовать один из способов ограничения произвола, которые мы рассматривали. Метод класса, согласно парадигме ООП, должен быть тщательно разработан, чтобы быть долговечным кирпичиком создаваемого ПО.
В нашем примере путем инкапсуляции абстрактного понятия Человек в тип Man программист сам себе запрещает в будущем случайно изменять ключевые поля объектов (представителей) класса Man прямым присвоением извне. Инкапсуляция подразумевает заключение в жесткие рамки правила игры и готовность их соблюдать. В приведенном примере описание класса (definition) и реализация его методов (implementation) расположены в одном модуле, так как задача проста. При разработке реальных приложений модули описаний классов (definition module) размещают в отдельных файлах с расширением h. Они компилируются отдельно и именно в них задаются все правила игры с объектами классов. В примере тела методов, то есть коды непосредственной их реализации, приведены внутри описания класса. Это иллюстрирует только одну из возможностей задания функций в C++. Такой способ предлагает компилятору работать с методом (функцией) в режиме inline, суть которого была описана выше. При этом за счет увеличения объема памяти сокращается время выполнения. Компилятор, однако, может игнорировать это предложение. Да и в большинстве случаев такой компромисс не является удовлетворительным, и поэтому тела процедур размещают вне модуля описания класса в этом же файле или в другом файле (с расширением срр).
Другой альтернативой декомпозиции может быть размещение каждого из методов в отдельном файле. Преимуществом такой структуры является возможность подключать компоновщиком только те методы, которые действительно используются в данный момент. Кроме отказа от режима inline такая структура программы приводит к большему соответствию парадигме ООП. Так как в случае изменений они, что наиболее вероятно, будут локализованы в одном из файлов и не затронут другие. Если все-таки есть желание размещать тела методов там же, где и описания, то следует помнить, что тела должны быть достаточно простыми. Компилятор сам принимает решение относительно способа реализации тела метода.