Оператор =
Присваивание одного объекта другому объекту того же класса выполняется с помощью копирующего оператора присваивания. (Этот специальный случай был рассмотрен в разделе 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, и trap._string
Если trap._string
напрямую ссылался на ia, то объект trap
демонстрировал бы своеобразное поведение: его значение может изменяться без вызова функций-членов класса String. Поэтому мы полагаем, что выделение области памяти для хранения копии значения C-строки менее опасно.
Обратите внимание, что в операторе присваивания используется delete. Член _string
содержит ссылку на массив символов, расположенный в хипе. Чтобы предотвратить утечку, память, выделенная под старую строку, освобождается с помощью delete до выделения памяти под новую. Поскольку _string адресует массив символов, следует использовать версию delete для массивов (см. раздел 8.4).
И последнее замечание об операторе присваивания. Тип возвращаемого им значения – это ссылка на класс String. Почему именно ссылка? Дело в том, что для встроенных типов операторы присваивания можно сцеплять:
// сцепление операторов присваивания int iobj, jobj; |
Они ассоциируются справа налево, т.е. в предыдущем примере присваивания выполняются так:
iobj = (jobj = 63);
Это удобно и при работе с объектами класса String: поддерживается, к примеру, следующая конструкция:
String ver, noun; |
При первом присваивании из этой цепочки вызывается определенный ранее оператор для const char*. Тип полученного результата должен быть таким, чтобы его можно было использовать как аргумент для копирующего оператора присваивания класса String. Поэтому, хотя параметр данного оператора имеет тип const char *, возвращается все же ссылка на String.
Операторы присваивания бывают перегруженными. Например, в нашем классе String
есть такой набор:
// набор перегруженных операторов присваивания String& operator=( const String & ); |
Отдельный оператор присваивания может существовать для каждого типа, который разрешено присваивать объекту String. Однако все такие операторы должны быть определены как функции-члены класса.