Управление доступом к элементам и функциям класса

В С++ можно ограничить видимость данных и функций класса с помощью

меток-спецификаторов private, protected и public (или режимов доступа).

Таблица 4

Спецификаторы доступа

Метка доступа Значение Описание  
Private частный, закрытый Элементы-данные и функции-элементы доступны только для функций-элементов этого класса; нет доступа извне.
protected защищен-ный Элементы-данные и элементы-функции доступны для функций-элементов данного класса и производных от него классов.
public общий Элементы-данные и функции-элементы класса доступны для функций-элементов других функций программы (доступны любому внешнему объекту).

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

По умолчанию все элементы класса приватные, поэтому ключевое слово private можно опускать.

Указанные ключевые слова могут встречаться в объявлении классов в любом порядке и любое количество раз, но хорошим стилем программирования является использование каждого слова единожды.

В С++ классы, структуры и объединения рассматриваются как типы классов. Структура и класс подходят друг другу за исключением доступа по умолчанию. В структуре элементы имеют по умолчанию доступ public (общий), в классе - private (приватный). Аналогично структуре, объединения по умолчанию предоставляют доступ public; аналогично объединениям в С, его элементы-данные размещаются начиная с одного и того же места в памяти.

Элементы класса делятся на две основных категории:

– данные, называемые элементами-данными (data memberes) и

– код, называемый элементом-функцией, который обрабатывает эти данные (member functions) или методом.

Свойства элементов класса:

– элементы-данные не могут определяться как auto, extern или register;

– элементами-данными могут быть перечислимые типы, битовые поля или представители ранее объявленного класса.

– элементом данных не может быть представитель самого этого класса.

– элементами-данными класса может являться указатель или ссылка на пред-ставитель этого класса.

Элемент-функция является функцией, объявленной (описанной) внутри опре-деления класса. Тело функции может также находиться внутри определения класса. В этом случае функция называется встроеннойфункцией-элементом. Когда тело функции определяется вне тела класса, перед именем функции ставится префикс из имени класса и операции разрешения видимости (::) или операция принадлежности. Она показывает к какому классу принадлежит эта функция.

Рассмотрим пример, иллюстрирующий характеристику ООП – "инкап-суляция": совмещения элементов данных с функциями (методами), пред-назначенными для (манипулирования этими данными). Данные и способы работы с ними сводятся воедино в одном объекте данных, называемом классом. Создим класс, позволяющий работать со строкой символов [6]. Набор данных, описывающий такой объект как символьная строка в простейшем случае должен состоять из

– собственно строки str,

– позиции на экране, начиная с которой будет выводиться строка row, col,

– видеоатрибутов символов (цвета текста и фона) attr.

Выделим функции (методы), необходимые для работы с любой строкой:

– ввод строки,

– установка координат для дальнейшего вывода строки,

– установка видеоатрибутов символов,

– вывод строки в указанное место

и напишем программу вывода строки в заданное место экрана.

#include <conio.h> class.cpp

#include <string.h>

Class String

{

char str[80]; // данные-члены класса имеют тип доступа private,

int attr; // т.е. доступны только через функции-члены

int row, col; // класса, которые объявлены общедоступными

public:

void setAttr(int a) {attr=a;} // установка видеоатрибутов

void setStr(char* s) {strcpy(str,s);} // ввод (копированием) строки

void setCoord(int x, int y) {row=y; col=x;} // установка координат

void printStr(int=0, int=0); // использованы аргументы по умолчанию

}; // для вывода строки в указанное место

inline void String:: printStr(int x, int y) // подставляемая функция

{

textattr(attr);

gotoxy(x == 0? col: x, y == 0? row: y); // условная операция

cputs(str);

}

Void main()

{

String Str, *sp = &str;

str.setAttr((BLUE<<4) + YELLOW); // пишем желтым по синему фону

str.setStr("Hello, USER !!!");

sp -> printStr(5, 20); // c 5 позиции в 20 строке сверху

}

Классы – это не сами объекты, а шаблоны для создания объектов. Когда необходимо, создается экземпляр класса, который называется объектом. Отношение между классом и объектом такое же, как между типом и переменной.

Объект можно создать сразу при определении класса:

class имя класса

{

private: // частные элементы-данные и функции

protected: // защищенные элементы-данные и элементы-функции

public: // общие элементы-данные и функции

} [список объектов] ;

Объекты могут отсутствовать. При объявлении класса, как правило, используются прототипы функций-членов класса. Например объявляем класс queue (очередь)

Class queue

{

private:

int q[10];

int sloc, rloc; // маркеры начала и конца очереди

public: // режим доступа public к нижеследующим элементам

void init(void); // функция-член класса «инициализация» (прототип)

int qget(void); // функция извлечения элементов из очереди

void qput(int); // функция заполнения очереди

};

Когда требуется описать функцию-член класса, необходимо указать, к какому классу она относится. Например, можно определить функцию init() класса queue следующим образом

void queue:: init(void) // описание функции-члена класса queue

{ rloc = sloc = 0; }

Запись queue::initназывают полным или квалификационным именем функции-члена класса. В С++ разные функции в разных классах могут иметь одинаковые имена. Данная операция позволит компилятору определить принадлежность функции.

Чтобы вызвать функцию-член класса в той части программы, которая частью класса не является, надо использовать имя объекта и операцию «точка» (.) –доступа к члену (элементу) класса. Например, если объявлены два объекта а и b класса queue (queue a,b), то для вызова функции init() для объекта а нужно написать a.init().

Следует помнить, что a и b два разных объекта. Данные-члены занимают в памяти разное место, однако функции-члены класса общие. Функции-члены класса для того чтобы отличить один объект от другого, имеют неявный параметр – указатель на объект. (Этот указатель называется this.)

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

Рассмотрим модель структуры данных "очередь" [7, c. 169] Очередь - структура данных, для которой определены действия "поставить в очередь" и "обслужить". Пусть очередь состоит из целочисленного массива и двух целочисленных переменных: маркера начала очереди и маркер конца очереди.

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

#include <iostream.h> class2.cpp

#include <conio.h> // для функции getch()

class queue // Объявляем класс queue

{

private: // режим доступа private к следующим элементам массива

int q[10];

int sloc, rloc; // маркеры начала и конца очереди

public: // режим доступа public

void init(void); // функции-члены класса

int qget(void);

void qput(int);

};

/*--------------------------- описание функций-членов класса queue------------------*/

void queue:: init(void)

{ rloc = sloc = 0; }

int queue:: qget(void)

{

if ( sloc == rloc ) {

cout<<"Очередь пуста";

return 0;

}

return q[rloc++];

}

Void queue:: qput(int i)

{

if ( sloc == 10 ) {

cout<< "Oчередь полна";

return;

}

q[sloc++] = i;

}

/*--------------------------------------------------------------------------------------------------*/

Void main()

{

queue a,b; // Созданы два объекта

a.init(); // Инициализация объектов

b.init();

a.qput(7); // Заполнение очереди а

a.qput(9);

a.qput(11);

cout<<a.qget() <<" "<<a.qget() <<" "<<a.qget() <<" "<<a.qget() <<"\n";

for ( int i = 0; i<12; i++) // заполнение очереди b значением i2

b.qput( i*i );

for ( i=0; i<12; i++ )

cout<<b.qget() <<" ";

cout<<"\n";

getch(); // Задержка результата работы на экране

}

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

Конструкторы и деструкторы

Для большинства объектов естественно требовать, чтобы они были иници-ализированы до начала их использования. Примером является вышерассмотренный класс queue. Прежде, чем объект класса будет использоваться, переменным sloc и rloc должно быть присвоено нулевое значение. Это было реализовано функцией init( ). Инициализация объектов является общим требованием, поэтому С++ предоставляет возможность это делать автоматически при создании объекта, т. е. при его объявлении.

Автоматическая инициализация реализуется использованием функции, которая называется конструктором. Конструктор – это специальная функция, являющаяся членом класса и имеющая то же самое имя, что и класс. Например, класс queue можно было объявить следующим образом:

Class queue

{

int q[1Æ];

int sloc, rloc;

queue (void); // конструктор

int qget (void);

void qput (int);

};

Функция-конструктор не может иметь тип возвращаемого значения. Описание такого конструктора имеет вид

queue :: queue (void)

{

rloc=sloc=0;

cout<<”Очередь инициализирована \ n”;

}

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

Если в классе не объявлен ни один конструктор, компилятор сам создает функцию - конструктор класса.

Деструктор

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

Деструктор имеет такое же имя, как и имя класса, но перед ним ставится знак тильды (~). Деструктор не может иметь типа возвращаемого значения и не может иметь параметров. В отличие от конструктора, деструктор не может быть вызван явно.

В примере класс queue не нуждается в деструкторе, тем не менее включим эту функцию в качестве иллюстрации процесса обращения кдеструктору.

Class queue

{ int q[10];

int sloc, rloc;

publiс:

queue (void); // конструктор

~queue (void); // деструктор

int q get (void);

void qput (int);

}; // объявлен класс queue с конструктором и деструктором

queue :: queue (void)

{rloc = sloc = 0; cout<< «Очередь инициализирована \n»;

} // описание конструктора

queue :: queue (void)

{

cout<< «Очередь разрушена \n»;

} // описание деструктора, который лишь выдает сообщение.

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

– деструктор не может иметь аргументов.

– деструктор не может возвращать значение.

– деструктор не наследуется.

– деструктор не может быть объявлен как const, volotile или static.

– деструктор может быть объявлен как virtual.

Компилятор генерирует деструктор по умолчанию, если он в программе не определен.

Конструктор с параметрами

Часто бывает необходимо инициализировать элементы создаваемого объекта определенными конкретными значениями. Это достигается передачей значений через аргументы конструктора объекта.

Еще раз модифицируем класс queue, добавив, в него новую переменную id и еще один конструктор. Конструктор без параметров обычно называется конструктором поумолчанию, конструктор с параметрами – конструктором инициализации. Пусть в примере конструктор инициализации будет иметь один параметр, который будет передаваться переменной id, эта переменная будет идентификатором области. Конструктор по умолчанию будет присваивать этой переменной значение нуль. Изменим класс queue слудующим образом.

Class queue

{

int q[10];

int sloc,rloc;

int id; // это значение будем определять

publiс:

queue (void); // конструктор по умолчанию.

queue (int i); // конструктор инициализации

~ queue (void); // деструктор

int qget (void);

void qput (int);

};

Переменная id содержит значение параметра, инициализирующего объект. Он будет определён при создании объекта типа queue. Перепишем конструктор по умолчанию:

queue :: queue (void)

{

rloc = sloc = 0;

id = 0;

cout<< «конструктор по умолчанию очереди»<< id << « \n »;

}

Определим конструктор инициализации

Queue :: queue (int i)

{

rloc = sloc = 0

id = i;

cout<< «Конструктор инициализации очереди» << id << « \n »;

}

Для передачи значения параметра в конструктор можно использовать два способа:

1) queue a = que(101): // редко используемая форма

2) queue a(101);

Общая форма передачи аргумента конструктора:

class_type переменная (список аргументов);

например, queue a=que(101), b (102);

Приведем текст программы «Модель структуры данных "очередь"» [7, с.180]

с использованием конструктора с параметром и перегруженных конструкторов

#include <iostream.h> kons1.cpp

class queue // Объявляем класс queue

{ // режим доступа private к элементам массива опущен

int q[10];

int sloc, rloc; // маркеры конца и начала очереди

int id; /*это значение будем определять*/

public: // режим доступа public к следующим элементам

queue(void); /* конструктор по умолчанию */

queue(int i); /* конструктор инициализации */

~queue(void); /* деконструктор */

int qget(void);

void qput(int);

};

void queue::queue(void)

{

rloc = sloc = 0;

id = 0;

cout<<"Конструктор по умолчанию очереди" <<id <<"\n";

}

Void queue::queue(int i)

{

rloc = sloc = 0;

id = i;

cout<<"Конструктор инициализации очереди" <<id <<"\n";

}

queue::~queue(void)

{ cout<<"Очередь " <<id <<"разрушена\n"; }

int queue:: qget(void)

{ if ( sloc == rloc)

{ cout<<"Очередь пуста"; return 0;}

return q[rloc++];

}

Void queue:: qput(int i)

{ if ( sloc == 10 ) { cout<< "Oчередь полна"; return; }

q[sloc++] = i;

}

void main() // без очистки экрана

{ queue a = queue(101), // Объявление объекта с инициализацией

b(102), // объявление другого объекта

c; // Объявление объекта без передачи параметра

a.qput(10); // Заполнение очереди а

b.qput(19);

a.qput(20); // Результаты:

b.qput(1); // Конструктор инициализации очереди 101

for ( int i =0; i<3; i++) // Конструктор инициализации очереди 102

c.qput(200+l); // Конструктор по умолчанию очереди 0

cout<<a.qget() <<" "; // 10 20 19 1

cout<<a.qget() <<" "; // 200 201 202

cout<<b.qget() <<" "; // Очередь 0 разрушена

cout<<b.qget() <<"\n"; // Очередь 102 разрушена

for ( i=0; i<3; i++) // Очередь 101 разрушена

cout<<c.qget() <<" ";

cout<<"\n";

}

Результаты:

Конструктор инициализации очереди 101

Конструктор инициализации очереди 102

Конструктор по умолчанию очереди 0

10 20 19 1

200 201 202

Очередь 0 разрушена (вызов деструкторов)

Очередь 102 разрушена

Очередь 101 разрушена

Конструктор может иметь не только один аргумент. Аргументов может быть несколько, причем разного типа [7]. Например,

#include <iostream.h> konst2.cpp

Class c1

{

int i;

float j;

public:

c1(int a, float b);

void show(void);

};

c1:: c1(int a, float b) //конструктор инициализации с параметрами

{ i = a; j = b;

}

void c1:: show(void)

{ cout<<" i="<<i<<" j="<<j<<"\n";

}

void main(void)

{

c1 x(1, 2.1), y(3,4), z(100,101.1);

x.show();

y.show();

z.show();

}

Результаты:

i =1 j=2,1

i =3 j=4

i =100 j=101,099998

Наши рекомендации