Инкапсуляция

Доступ к членам класса

Создание экземпляра класса

Определение класса

Инициализация выделенной памяти

Выделение памяти массивам оператором new

При выделении памяти для размещения массивов с помощью оператора new задается базовый тип данных (т.е. тип данных элементов массива) и число элементов, указываемое внутри квадратных скобок "[]", например:

void Func (int Size) {char *String = new char [25]; //массив из 25 символовint *ArrayInt = new int [Size]; //массив из 'Size' целыхdouble *ArrayDouble;ArrayDouble = new double [32]; //массив из 32 двойной точности// ... }

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

Чтобы освободить массив, к оператору delete при вызове требуется добавить пару квадратных скобок "[]", обозначающих, что освобождается массив, а не единичный объект базового типа. Например, следующие операторы освобождают массивы, размещенные в предыдущем примере:

delete [] String; delete [] Arraylnt; delete [] ArrayDouble;

Блок памяти, выделенный с помощью оператора new, не может быть инициализирован автоматически значениями 0. Однако при использовании оператора new для выделения памяти объекту встроенного типа (например, char) можно явно инициализировать объект константой соответствующего типа, используя следующий синтаксис:

char *PChar = new char ('a'); // инициализирует char значением 'а'int *PInt = new int (3); // инициализирует int значением 3

Объект определенного пользователем типа (например, структура) можно также инициализировать существующим объектом этого типа.

struct Node {char *String; int Value; Node *Next; };void Func (){ Node NodeA = {"hello", 1, 0};Node *PNode = new Node (NodeA);

}

Содержимое NodeA будет скопировано поле за полем в новый объект, память для которого выделена оператором new.

 

Класс языка C++ очень похож на стандартную структуру С, хотя средства, предоставляемые классами C++, превосходят возможности структур языка С. Для понимания классов C++ полезно сначала обсудить использование структур в С.

Структуры языка С позволяют сгруппировать набор связанных переменных-членов. Например, если создан прямоугольник, удобно сохранить его координаты в виде структуры, определенной следующим образом.

struct Rectangle {int Left;int Top;int Right;int Bottom;};

Далее можно определить функцию рисования прямоугольника.

void DrawRectangle (struct Rectangle *Rect){Line (Rect->Left, Rect->Top, Rect->Right, Rect->Top);Line (Rect->Right, Rect->Top, Rect->Right, Rect->Bottom);Line (Rect->Right, Rect->Bottom, Rect->Left, Rect->Bottom);Line (Rect->Left, Rect->Bottom, Rect->Left, Rect->Top);}

В этом примере Line – гипотетическая функция, которая позволяет рисовать линию от точки, заданной первыми двумя координатами, до точки, определенной вторыми двумя координатами. Такая функция может быть определена где-либо в программе или вызвана из библиотеки функций.

Наконец, чтобы задать прямоугольник в определенном месте, нужно определить и инициализировать переменную типа Rectangle, а затем передать ее в функцию DrawRectangle.

struct Rectangle SRect = (25, 25, 100, 100); DrawRectangle (SRect);

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

class CRectangle {int Left;int Top;int Right;int Bottom; void Draw (void) {

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

} };

Компоненты данных, определенные внутри класса, называются переменными-членами класса (иногда их называют также полями данных). Функции, определенные внутри класса, называются функциями-членами или методами класса. В этом примере переменные-члены – Left, Top, Right и Bottom, а функция-член – Draw. Обратите внимание: функция-член может содержать ссылку на любую переменную класса, не используя при этом специальный синтаксис.

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

struct Rectangle {int Left;int Top;int Right;int Bottom; };

Чтобы зарезервировать память и создать переменную, нужно задать определение

struct Rectangle Rect;

Аналогично определяется класс, например CRectangle, показанный в предыдущем параграфе. При этом компилятору предоставляется проект класса, но в действительности место в памяти не резервируется. Как и структура, такая переменная-член должна определяться выражением типа

CRectangle Rect;

Это определение создает экземпляр класса CRectangle, который также называют объектом (иногда – представителем или копией класса). В этой книге термины экземпляр класса и объект используются как синонимы. Экземпляр Rect класса CRectangle занимает собственный блок памяти и может использоваться для хранения данных и выполнения операций над ними. Как и переменная встроенного типа, объект существует, пока поток управления не выходит за пределы области видимости его определения (например, если объект определен внутри функции, то уничтожается при выходе из нее).

Примечание

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

Экземпляр класса можно создать, используя имеющийся в языке C++ оператор new, например:

CRectangle *PRect = new CRectangle;

Этот оператор выделяет блок памяти достаточного объема, чтобы разместить в нем экземпляр класса, и возвращает указатель на данный блок. Объект будет оставаться в памяти, пока вы явно не освободите его с помощью оператора delete (как показано в гл. 2 при описании встроенных типов данных).

delete CRectangle;

Можно создать произвольное число экземпляров данного класса.

Совет

При создании экземпляра класса перед именем класса можно не указывать слово class. В C++ определение класса создает новый тип данных, на который можно сослаться, используя одно лишь имя класса.

После создания экземпляра класса организуется доступ к переменным-членам и функциям-членам класса. При этом используется синтаксис, подобный применяемому для работы со структурами языка С. Однако при наличии одного только определения класса CRectangle программа не сможет обратится ни к одному из его членов, так как по умолчанию все переменные и функции, принадлежащие классу, определены как закрытые (private) (иногда говорят внутренние, личные или частные). Это означает, что они могут использоваться только внутри функций-членов самого класса. Так, для функции Draw разрешен доступ к переменным-членам Top, Left, Right и Bottom, потому что Draw – функция-член класса. Для других частей программы, таких как функция main, доступ к переменным-членам или вызов функции-члена Draw запрещен.

К счастью, можно использовать спецификатор доступа public, чтобы создать открытый член класса (иногда называемый общедоступным или публичным), доступный для использования всеми функциями программы (как внутри класса, так и за его пределами). Например, в следующем варианте класса Rectangle все члены являются открытыми.

class CRectangle{public:int Left;int Top;int Right;int Bottom; void Draw (void) {

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

} };

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

Теперь, когда все члены класса CRectangle открыты, доступ к ним, как и доступ к полям структуры в языке С, возможен с использованием оператора "."

CRectangle Rect; // определение объекта CRectangle Rect.Left= 5; // присваивание значений переменным-членамRect.Top = 10; // для указания координат прямоугольникаRect.Right = 100; Rect.Bottom = 150; Rect.Draw (); // создание прямоугольника

С другой стороны, можно создать экземпляр класса оператором new, а затем использовать указатель на экземпляр для доступа к переменным-членам класса, как показано в следующем коде.

CRectangle *PRect = new CRectangle; PRect->Left = 5;PRect->Tор = 10; PRect->Right = 100; PRect->Bottom =150; PRect->Draw ();

Согласно принципам инкапсуляции внутренние структуры данных, используемые в реализации класса, не должны быть доступны пользователю класса непосредственно (преимущества инкапсуляции будут рассмотрены ниже). Однако текущая версия класса CRectangle явно нарушает этот принцип, так как пользователь может непосредственно читать или модифицировать любые переменные-члены.

Для достижения большей инкапсуляции класс CRectangle должен быть определен так, чтобы иметь доступ к функциям-членам (в нашем случае к Draw), но не иметь доступа к внутренним переменным-членам, используемым этими функциями (Left, Top, Right и Bottom).

class CRectangle{private:int Left;int Top;int Right;int Bottom; public:void Draw (void) {

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

} };

Спецификатор доступа private делает переменные, определенные позже, закрытыми. Таким образом, они доступны только функциям-членам класса. Подобно спецификатору доступа public, рассмотренному ранее, спецификатор private воздействует на все объявления, стоящие после него, пока не встретится другой спецификатор. Следовательно, такое определение делает переменные Left, Top, Right и Bottom закрытыми, а функцию Draw открытой. Заметим: в действительности не требуется помещать спецификатор private в начале определения класса, потому что члены класса по умолчанию являются закрытыми. Однако включение спецификатора private облегчает чтение программы.

Примечание

Язык C++ предоставляет еще и третий вид доступа – protected. Этот спецификатор будет рассмотрен в гл. 4, поскольку требует понимания термина "наследование".

Следующий код иллюстрирует как корректное, так и некорректное обращение к членам очередного варианта класса CRectangle.

void main() { CRectangle Rect; // определение объекта CRectangle Rect.Left = 5; // ОШИБКА: нет доступа к закрытому членуRect.Top =10; // ОШИБКАRect.Right =100; // ОШИБКАRect.Bottom =150; // ОШИБКАRect.Draw (); // допускается (но координаты не определены)}

Теперь, когда пользователю класса запрещен прямой доступ к переменным-членам, класс должен предоставить альтернативное средство указания координат перед созданием прямоугольника. Хороший способ для этого – предоставление открытой функции-члена, принимающей требуемые значения координат и использующей эти значения для установки переменных-членов. Например:

void SetCoord (int L, int Т, int R, int B) {L = _min (_max (0,L), 80);Т = _min (_max (0,T), 25);R = _min (_max (0,R), 80);В = _min (_max (0,B), 25);R = _max (R,L) ;В = _max (B,T);Left = L; Top = T; Right = R; Bottom = B;}

Эта функция добавляется в раздел public определения класса CRectangle. Поэтому ее можно вызвать из любой функции программы. Обратите внимание: при необходимости, перед присваиванием параметров переменным класса, функция SetCoord организует проверку нахождения параметров в диапазоне корректных значений и следит, чтобы правая координата была больше левой, а нижняя – больше верхней. Макросы _max и _min предоставляются динамической библиотекой языка C++. Для их применения в программу нужно включить файл заголовков Stdlib.h.

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

void main(){//... CRectangle Rect;Rect.SetCoord (25,25,100,100); // установка координат прямоугольника Rect.Draw (); // отображение прямоугольника// ... };

В этой главе (в параграфе «Конструкторы») вы узнаете, как инициализировать переменные при создании экземпляра класса.

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

void GetCoord (int L, int Т, int R, int B) {*L = Left; *T = Top; *R = Right; *B = Bottom; }

Эта функция также должна быть добавлена в раздел public определения класса. Ниже приведено законченное определение класса Rectangle, содержащее новые функции-члены SetCoord и GetCoord.

#include <Stdlib.h> class Rectangle ,

{

private:int Left;int Top;int Right;int Bottom; public:void Draw (void) {

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

} void GetCoord (int L, int Т, int R, int B) {

*L = Left;

*T = Top;

*R = Right;

*B = Bottom;

}void SetCoord (int L, int Т, int R, int B) {

L = _min (_max (0,L), 80);

Т = _min (_max (0,T), 25);

R = _min (_max (0,R), 80);

В = _min (_max (0,B), 25);

R = _max (R,L) ;

В = _max (B,T);

Left = L; Top = T; Right = R; Bottom = B;

}}

Теперь, с помощью функций-членов SetCoord и GetCoord класс Rectangle предоставляет доступ к закрытым переменным (согласно принципам инкапсуляции) только посредством четко определенного интерфейса, контролирующего корректность новых присвоенных значений и, при необходимости, корректирующего эти значения.