Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

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 такая структура програм­мы приводит к большему соответствию парадигме ООП. Так как в случае измене­ний они, что наиболее вероятно, будут локализованы в одном из файлов и не затронут другие. Если все-таки есть желание размещать тела методов там же, где и описания, то следует помнить, что тела должны быть достаточно простыми. Ком­пилятор сам принимает решение относительно способа реализации тела метода.