Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

589

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

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

защищенный член ведет себя как открытый по отношению к производному классу и как закрытый по отношению к остальной части программы. (В главе 2 мы видели пример использования защищенных членов в классе IntArray. Детально они рассматриваются в главе 17, где вводится понятие наследования.)

class Screen { public:

void home() { _cursor = 0; }

char get() { return _screen[_cursor]; } char get( int, int );

void move( int, int );

// ...

 

private:

_screen;

string

string::size_type _cursor;

short

_height, _width;

Вследующем определении класса Screen указаны секции public и private:

};

Согласно принятому соглашению, сначала объявляются открытые члены класса. (Обсуждение того, почему в старых программах C++ сначала шли закрытые члены и почему этот стиль еще кое-где сохранился, см. в книге [LIPPMAN96a].) В теле класса может быть несколько секций public, protected и private. Каждая секция продолжается либо до метки следующей секции, либо до закрывающей фигурной скобки. Если спецификатор доступа не указан, то секция, непосредственно следующая за открывающей скобкой, по умолчанию считается private.

13.1.4. Друзья

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

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

class Screen { friend istream&

operator>>( istream&, Screen& ); friend ostream&

operator<<( ostream&, const Screen& ); public:

// ... оставшаяся часть класса Screen

подобные объявления сразу после заголовка класса:

};

С++ для начинающих

590

Операторы ввода и вывода теперь могут напрямую обращаться к закрытым членам

#include <iostream>

ostream& operator<<( ostream& os, const Screen& s )

{

// правильно: можно обращаться к _height, _width и _screen os << "<" << s._height

<< "," << s._width << ">"; os << s._screen;

return os;

класса Screen. Простая реализация оператора вывода выглядит следующим образом:

}

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

13.1.5. Объявление и определение класса

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

Можно объявить класс, не определяя его. Например:

class Screen; // объявление класса Screen

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

Тип объявленного, но еще не определенного класса допустимо использовать весьма ограниченно. Нельзя определять объект типа класса, если сам класс еще не определен, поскольку размер класса в этом момент неизвестен и компилятор не знает, сколько памяти отвести под объект.

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

Член некоторого класса можно объявить принадлежащим к типу какого-либо класса только тогда, когда компилятор уже видел определение этого класса. До этого объявляются лишь члены, являющиеся указателями или ссылками на такой тип. Ниже приведено определение StackScreen, один из членов которого служит указателем на Screen, который объявлен, но еще не определен:

С++ для начинающих

591

 

 

class Screen; // объявление

 

 

 

 

 

 

class StackScreen {

 

 

 

int topStack;

 

 

 

// правильно: указатель на объект Screen

 

 

 

Screen *stack;

 

 

 

void (*handler)();

 

 

 

};

 

 

 

 

 

 

 

Поскольку класс не считается определенным, пока не закончилось его тело, то в нем не

 

может быть данных-членов его собственного типа. Однако класс считается объявленным,

 

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

 

 

 

class LinkScreen {

 

 

 

 

 

 

Screen window;

 

 

 

LinkScreen *next;

 

 

 

LinkScreen *prev;

 

ссылками или указателями на его тип. Например:

 

 

 

};

 

 

 

 

 

 

 

Упражнение 13.1

 

 

 

string _name;

 

 

 

 

Пусть дан класс Person со следующими двумя членами:

 

 

 

string _address;

 

 

 

Person( const string &n, const string &s )

 

 

 

 

 

 

 

 

 

: _name( n ), _address( a ) { }

 

 

 

string name() { return _name; }

 

и такие функции-члены:

 

 

 

string address() { return _address; }

 

 

 

 

 

 

 

 

Какие члены вы объявили бы в секции public, а какие в секции private? Поясните

 

свой выбор.

 

Упражнение 13.2

 

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

 

использовать объявление класса? А определение?

 

13.2. Объекты классов

Определение класса, например Screen, не приводит к выделению памяти. Память выделяется только тогда, когда определяется объект типа класса. Так, если имеется следующая реализация Screen:

С++ для начинающих

592

 

class Screen {

 

 

 

 

public:

 

 

// функции-члены

 

 

private:

_screen;

 

string

 

string:size_type _cursor;

 

short

_height;

 

short

_width;

};

то определение

Screen myScreen;

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

Область видимости объекта класса зависит от его положения в тексте программы. Он

class Screen {

// список членов

};

int main()

{

Screen mainScreen;

определяется в иной области, нежели сам тип класса:

}

Тип Screen объявлен в глобальной области видимости, тогда как объект mainScreen в локальной области функции main().

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

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

Screen bufScreen = myScreen;

//bufScreen._height = myScreen._height;

//bufScreen._width = myScreen._width;

//bufScreen._cursor = myScreen._cursor;

Например:

// bufScreen._screen = myScreen._screen;

С++ для начинающих

593

Указатели и ссылки на объекты класса также можно объявлять. Указатель на тип класса

разрешается инициализировать адресом объекта того же класса или присвоить ему такой адрес. Аналогично ссылка инициализируется l-значением объекта того же класса. (В объектно-ориентированном программировании указатель или ссылка на объект базового

int main()

{

Screen myScreen, bufScreen[10]; Screen *ptr = new Screen; myScreen = *ptr;

delete ptr;

ptr = bufScreen; Screen &ref = *ptr;

Screen &ref2 = bufScreen[6];

класса могут относиться и к объекту производного от него класса.)

}

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

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

Для доступа к данным или функциям-членам объекта класса следует пользоваться соответствующими операторами. Оператор точка” (.) применяется, когда операндом является сам объект или ссылка на него; а стрелка(->)когда операндом служит

#include "Screen.h"

bool isEqual( Screen& s1, Screen *s2 )

{ // возвращает false, если объекты не равны, и true - если равны

if (s1.height() != s2->height() || s2.width() != s2->width() )

return false;

for ( int ix = 0; ix < s1.height(); ++ix ) for ( int jy = 0; jy < s2->width(); ++jy )

if ( s1.get( ix, jy ) != s2->get( ix, jy ) ) return false;

return true; // попали сюда? значит, объекты равны

указатель на объект:

}

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

Для получения высоты и ширины экрана isEqual() должна пользоваться функциями- членами height() и width() для чтения закрытых членов класса. Их реализация тривиальна: