Выбрать главу

class Window;

class Screen {

friend class Window;

// ...

};

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

Упражнение 15.6

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

15.3. Оператор =

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

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

String car ("Volks");

car = "Studebaker";

мы предоставляем оператор, принимающий параметр типа const char*. Эта операция уже была объявлена в нашем классе:

class String {

public:

// оператор присваивания для char*

String& operator=( const char * );

// ...

private:

int _size;

char *string;

};

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

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;

}

_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*.

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

char ia[] = { 'd', 'a', 'n', 'c', 'e', 'r' };

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

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.