Введение в язык Си++

Классы


Давайте посмотрим, как мы могли бы определить тип потока вывода ostream. Чтобы упростить задачу, предположим, что для буферизации определен тип streambuf. Тип streambuf на самом деле определен в , где также находится и настоящее определение ostream. Пожалуйста, не испытывайте примеры, определяющие ostream в этом и последующих разделах; пока вы не сможете полностью избежать использования , компилятор будет возражать против переопределений.

Определение типа, определяемого пользователем (который в C++ называется class, т.е. класс), специфицирует данные, необходимые для представления объекта этого типа, и множество операций для работы с этими объектами. Определение имеет две части: закрытую (private) часть, содержащую информацию, которой может пользоваться только его разработчик, и открытую (public) часть, представляющую интерфейс типа с пользователем:

class ostream { streambuf* buf; int state; public: void put(char*); void put(long); void put(double); }

Описания после метки public задают интерфейс: пользователь может обращаться только к трем функциям put(). Описания перед меткой public задают представление объекта класса ostream; имена buf и state могут использоваться только функциями put(), описанными в открытой части.

class определяет тип, а не объект данных, поэтому чтобы использовать ostream, мы должны один такой объект описать (так же, как мы описываем переменные типа int):

ostream my_out;

Считая, что my_out был соответствующим образом проинициализирован (как, объясняется в ), его можно использовать например так:

my_out.put("Hello, world\n");

С помощью операции точка выбирается член класса для данного объекта этого класса. Здесь для объекта my_out вызывается член функция put().

Функция может определяться так:

void ostream::put(char* p) { while (*p) buf.sputc(*p++); }

где sputc() - функция, которая помещает символ в streambuf. Префикс ostream необходим, чтобы отличить put() ostream'а от других функций с именем put().

Для обращения к функции члену должен быть указан объект класса. В функции члене можно ссылаться на этот объект неявно, как это делалось выше в ostream::put(): в каждом вызове buf относится к члену buf объекта, для которого функция вызвана.



Можно также ссылаться на этот объект явно посредством указателя с именем this. В функции члене класса X this неявно описан как X* (указатель на X) и инициализирован указателем на тот объект, для которого эта функция вызвана. Определение ostream::put() можно также записать в виде:

void ostream::put(char* p) { while (*p) this-buf.sputc(*p++); }

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


Эти типы не "абстрактны", они столь же реальны, как int и float. - Дуг МакИлрой

В этой главе описываются возможности определения новых типов в C++, для которых доступ к данным ограничен заданным множеством функций доступа. Объясняются способы защиты структуры данных, ее инициализации, доступа к ней и, наконец, ее уничтожения. Примеры содержат простые классы для работы с таблицей имен, манипуляции стеком, работу с множеством и реализацию дискриминирующего (то есть, "надежного") объединения. Две следующие главы дополнят описание возможностей определения новых типов в C++ и познакомят читателя еще с некоторыми интересными примерами.




Классовые объекты могут присваиваться, передаваться функциям как параметры и возвращаться функциями. Другие возможные операции, как, например, проверка равенства, могут быть определены пользователем; см. #8.5.10.




Описание date в предыдущем подразделе дает множество функций для работы с date, но не указывает, что эти функции должны быть единственными для доступа к объектам типа date. Это ограничение можно наложить используя вместо struct class:

class date { int month, day, year; public: void set(int, int, int); void get(int*, int*, int*); void next(); void print(); };

Метка public делит тело класса на две части. Имена в первой, закрытой части, могут использоваться только функциями членами. Вторая, открытая часть, составляет интерфейс к объекту класса. Struct - это просто class, у которого все члены общие, поэтому функции члены определяются и используются точно так же, как в предыдущем случае. Например:

void date::ptinr() // печатает в записи, принятой в США { cout

Однако функции не члены отгорожены от использования закрытых членов класса date. Например:

void backdate() { today.day--; // ошибка }

В том, что доступ к структуре данных ограничен явно описанным списком функций, есть несколько преимуществ. Любая ошибка, которая приводит к тому, что дата принимает недопустимое значение (например, Декабрь 36, 1985) должна быть вызвана кодом функции члена, поэтому первая стадия отладки, локализация, выполняется еще до того, как программа будет запущена. Это частный случай общего утверждения, что любое изменение в поведении типа date может и должно вызываться изменениями в его членах. Другое преимущество - это то, что потенциальному пользователю такого типа нужно будет только узнать определение функций членов, чтобы научиться им пользоваться.

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



Содержание раздела