Поскольку мы динамически выделяли память с помощью оператора new, необходимо освободить ее вызовом delete, когда объект String нам больше не нужен. Для этой цели служит еще одна специальная функция-член – деструктор, автоматически вызываемый для объекта в тот момент, когда этот объект перестает существовать. (См. главу 7 о времени жизни объекта.) Имя деструктора образовано из символа тильды (~) и имени класса. Вот определение деструктора класса String. Именно в нем мы вызываем операцию delete, чтобы освободить память, выделенную в конструкторе:
inline String: :~String() { delete [] _string; }
В обоих перегруженных операторах присваивания используется специальное ключевое слово this.
Когда мы пишем:
String namel( "orville" ), name2( "wilbur" );
namel = "Orville Wright";
this является указателем, адресующим объект name1 внутри тела функции операции присваивания.
this всегда указывает на объект класса, через который происходит вызов функции. Если
ptr-size();
obj[ 1024 ];
то внутри size() значением this будет адрес, хранящийся в ptr. Внутри операции взятия индекса this содержит адрес obj. Разыменовывая this (использованием *this), мы получаем сам объект. (Указатель this детально описан в разделе 13.4.)
inline String
String::operator=( const char *s )
{
if ( ! s ) {
_size = 0;
delete [] _string;
_string = 0;
}
else {
_size = str1en( s );
delete [] _string;
_string = new char[ _size + 1 ];
strcpy( _string, s );
}
return *this;
}
При реализации операции присваивания довольно часто допускают одну ошибку: забывают проверить, не является ли копируемый объект тем же самым, в который происходит копирование. Мы выполним эту проверку, используя все тот же указатель this:
inline String
String::operator=( const String rhs )
{
// в выражении
// namel = *pointer_to_string
// this представляет собой name1,
// rhs - *pointer_to_string.
if ( this != rhs ) {
Вот полный текст операции присваивания объекту String объекта того же типа:
inline String
String::operator=( const String rhs )
{
if ( this != rhs ) {
delete [] _string;
_size = rhs._size;
if ( ! rhs._string )
_string = 0;
else {
_string = new char[ _size + 1 ];
strcpy( _string, rhs._string );
}
}
return *this;
}
Операция взятия индекса практически совпадает с ее реализацией для массива Array, который мы создали в разделе 2.3:
#include cassert
inline char
String::operator[] ( int elem )
{
assert( elem = 0 elem _size );
return _string[ elem ];
}
Операторы ввода и вывода реализуются как отдельные функции, а не члены класса. (О причинах этого мы поговорим в разделе 15.2. В разделах 20.4 и 20.5 рассказывается о перегрузке операторов ввода и вывода библиотеки iostream.) Наш оператор ввода может прочесть не более 4095 символов. setw() – предопределенный манипулятор, он читает из входного потока заданное число символов минус 1, гарантируя тем самым, что мы не переполним наш внутренний буфер inBuf. (В главе 20 манипулятор setw() рассматривается детально.) Для использования манипуляторов нужно включить соответствующий заголовочный файл:
#include iomanip
inline istream
operator( istream io, String s )
{
// искусственное ограничение: 4096 символов
const int 1imit_string_size = 4096;
char inBuf[ limit_string_size ];
// setw() входит в библиотеку iostream
// он ограничивает размер читаемого блока до 1imit_string_size-l
io setw( 1imit_string_size ) inBuf;
s = mBuf; // String::operator=( const char* );
return io;
}