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

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

719

class Window; class Screen {

//copy() - член класса Screen Screen& copy( Window & );

//...

};

class Window {

//Screen::copy() - друг класса Window friend Screen& Screen::copy( Window & );

//...

};

Screen& Screen::copy( Window & ) { /* ... */ }

Функция-член одного класса не может быть объявлена другом второго, пока компилятор не увидел определения ее собственного класса. Это не всегда возможно. Предположим, что Screen должен объявить некоторые функции-члены Window своими друзьями, а Window объявить таким же образом некоторые функции-члена Screen. В таком случае

class Window; class Screen {

friend class Window; // ...

весь класс Window объявляется другом Screen:

};

К закрытым членам класса Screen теперь можно обращаться из любой функции-члена

Window.

Упражнение 15.6

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

15.3. Оператор =

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

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

String car ("Volks");

поддержать присваивание C-строки объекту String: car = "Studebaker";

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

720

мы предоставляем оператор, принимающий параметр типа const char*. Эта операция

class String { public:

//оператор присваивания для char* String& operator=( const char * );

//...

private:

int _size; char *string;

уже была объявлена в нашем классе:

};

Такой оператор реализуется следующим образом. Если объекту String присваивается нулевой указатель, он становится пустым”. В противном случае ему присваивается

String& String::operator=( const char *sobj )

{

// sobj - нулевой указатель if (! sobj ) {

_size = 0; delete[] _string; _string = 0;

}

else {

_size = strlen( sobj ); delete[] _string;

_string = new char[ _size + 1 ]; strcpy( _string, sobj );

}

return *this;

копия C-строки:

}

_string ссылается на копию той C-строки, на которую указывает sobj. Почему на копию? Потому что непосредственно присвоить sobj члену _string нельзя:

_string = sobj; // ошибка: несоответствие типов

sobj это указатель на const и, следовательно, не может быть присвоен указателю на не-const (см. раздел 3.5). Изменим определение оператора присваивания:

String& String::operator=( const *sobj ) { // ... }

Теперь _string прямо ссылается на C-строку, адресованную sobj. Однако при этом возникают другие проблемы. Напомним, что C-строка имеет тип const char*. Определение параметра как указателя на не-const делает присваивание невозможным:

car = "Studebaker"; // недопустимо с помощью operator=( char *) !

Итак, выбора нет. Чтобы присвоить C-строку объекту типа String, параметр должен иметь тип const char*.

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

721

Хранение в _string прямой ссылки на C-строку, адресуемую sobj, порождает и иные сложности. Мы не знаем, на что именно указывает sobj. Это может быть массив

char ia[] =

{

'd', 'a', 'n', 'c', 'e', 'r' };

String trap

=

ia; // trap._string ссылается на ia

символов, который модифицируется способом, неизвестным объекту String. Например:

ia[3] = 'g'; // а вот это нам не нужно:

// модифицируется и ia, и trap._string

Если trap._string напрямую ссылался на ia, то объект trap демонстрировал бы своеобразное поведение: его значение может изменяться без вызова функций-членов класса String. Поэтому мы полагаем, что выделение области памяти для хранения копии значения C-строки менее опасно.

Обратите внимание, что в операторе присваивания используется delete. Член _string содержит ссылку на массив символов, расположенный в хипе. Чтобы предотвратить утечку, память, выделенная под старую строку, освобождается с помощью delete до выделения памяти под новую. Поскольку _string адресует массив символов, следует использовать версию delete для массивов (см. раздел 8.4).

И последнее замечание об операторе присваивания. Тип возвращаемого им значения это ссылка на класс String. Почему именно ссылка? Дело в том, что для встроенных

// сцепление операторов присваивания int iobj, jobj;

типов операторы присваивания можно сцеплять: iobj = jobj = 63;

Они ассоциируются справа налево, т.е. в предыдущем примере присваивания выполняются так:

iobj = (jobj = 63);

Это удобно и при работе с объектами класса String: поддерживается, к примеру,

String ver, noun;

следующая конструкция: verb = noun = "count";

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

Операторы присваивания бывают перегруженными. Например, в нашем классе String есть такой набор:

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

722

// набор перегруженных операторов присваивания

String& operator=( const String & );

String& operator=( const char * );

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

15.4. Оператор взятия индекса

Оператор взятия индекса operator[]() можно определять для классов, представляющих абстракцию контейнера, из которого извлекаются отдельные элементы. Примерами таких контейнеров могут служить наш класс String, класс IntArray, представленный в главе 2, или шаблон класса vector, определенный в стандартной библиотеке C++. Оператор взятия индекса обязан быть функцией-членом класса.

У пользователей String должна иметься возможность чтения и записи отдельных символов члена _string. Мы хотим поддержать следующий способ применения объектов

String entry( "extravagant" );

String mycopy;

for ( int ix = 0; ix < entry.size(); ++ix )

данного класса:

mycopy[ ix ] = entry[ ix ];

Оператор взятия индекса может появляться как слева, так и справа от оператора присваивания. Чтобы быть в левой части, он должен возвращать l-значение

#include <cassert>

inine char&

String::operator[]( int elem ) const

{

assert( elem >= 0 && elem < _size ); return _string[ elem ];

индексируемого элемента. Для этого мы возвращаем ссылку:

}

String color( "violet" );

Вследующем фрагменте нулевому элементу массива color присваивается символ 'V': color[ 0 ] = 'V';

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