Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Липпман.doc
Скачиваний:
8
Добавлен:
14.08.2019
Размер:
7.54 Mб
Скачать

15. Перегруженные операторы и определенные пользователем преобразования

В главе 15 мы рассмотрим два вида специальных функций: перегруженные операторы и определенные пользователем преобразования. Они дают возможность употреблять объекты классов в выражениях так же интуитивно, как и объекты встроенных типов. В этой главе мы сначала изложим общие концепции проектирования перегруженных операторов. Затем представим понятие друзей класса со специальными правами доступа и обсудим, зачем они применяются, обратив особое внимание на то, как реализуются некоторые перегруженные операторы: присваивание, взятие индекса, вызов, стрелка для доступа к члену класса, инкремент и декремент, а также специализированные для класса операторы new и delete. Другая категория специальных функций, которая рассматривается в этой главе, – это функции преобразования членов (конвертеры), составляющие набор стандартных преобразований для типа класса. Они неявно применяются компилятором, когда объекты классов используются в качестве фактических аргументов функции или операндов встроенных или перегруженных операторов. Завершается глава развернутым изложением правил разрешения перегрузки функций с учетом передачи объектов в качестве аргументов, функций-членов класса и перегруженных операторов.

15.1. Перегрузка операторов

В предыдущих главах мы уже показывали, что перегрузка операторов позволяет программисту вводить собственные версии предопределенных операторов (см. главу 4) для операндов типа классов. Например, в классе String из раздела 3.15 задано много перегруженных операторов. Ниже приведено его определение:

#include <iostream>

class String;

istream& operator>>( istream &, const String & );

ostream& operator<<( ostream &, const String & );

class String {

public:

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

// для автоматической инициализации

String( const char* = 0 );

String( const String & );

// деструктор: автоматическое уничтожение

~String();

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

String& operator=( const String & );

String& operator=( const char * );

// перегруженный оператор взятия индекса

char& operator[]( int );

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

// str1 == str2;

bool operator==( const char * );

bool operator==( const String & );

// функции доступа к членам

int size() { return _size; };

char * c_str() { return _string; }

private:

int _size;

char *_string;


};

В классе String есть три набора перегруженных операторов. Первый – это набор операторов присваивания:

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

String& operator=( const String & );


String& operator=( const char * );

Сначала идет копирующий оператор присваивания. (Подробно они обсуждались в разделе 14.7.) Следующий оператор поддерживает присваивание C-строки символов объекту типа String:

String name;


name = "Sherlock"; // использование оператора operator=( char * )

(Операторы присваивания, отличные от копирующих, мы рассмотрим в разделе 15.3.)

Во втором наборе есть всего один оператор – взятия индекса:

// перегруженный оператор взятия индекса


char& operator[]( int );

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

if ( name[0] != 'S' )


cout << "увы, что-то не так\n";

(Детально этот оператор описывается в разделе 15.4.)

В третьем наборе определены перегруженные операторы равенства для объектов класса String. Программа может проверить равенство двух таких объектов или объекта и C-строки:

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

// str1 == str2;

bool operator==( const char * );


bool operator==( const String & );

Перегруженные операторы позволяют использовать объекты типа класса с операторами, определенными в главе 4, и манипулировать ими так же интуитивно, как объектами встроенных типов. Например, желая определить операцию конкатенации двух объектов класса String, мы могли бы реализовать ее в виде функции-члена concat(). Но почему concat(), а не, скажем, append()? Выбранное нами имя логично и легко запоминается, но пользователь все же может забыть, как мы назвали функцию. Зачастую имя проще запомнить, если определить перегруженный оператор. К примеру, вместо concat() мы назвали бы новую операцию operator+=(). Такой оператор используется следующим образом:

#include "String.h"

int main() {

String name1 "Sherlock";

String name2 "Holmes";

name1 += " ";

name1 += name2;

if (! ( name1 == "Sherlock Holmes" ) )

cout << "конкатенация не сработала\n";


}

Перегруженный оператор объявляется в теле класса точно так же, как обычная функция-член, только его имя состоит из ключевого слова operator, за которым следует один из множества предопределенных в языке C++ операторов (см. табл. 15.1). Так можно объявить operator+=() в классе String:

class String {

public:

// набор перегруженных операторов +=

String& operator+=( const String & );

String& operator+=( const char * );

// ...

private:

// ...


};

и определить его следующим образом:

#include <cstring>

inline String& String::operator+=( const String &rhs )

{

// Если строка, на которую ссылается rhs, непуста

if ( rhs._string )

{

String tmp( *this );

// выделить область памяти, достаточную

// для хранения конкатенированных строк

_size += rhs._size;

delete [] _string;

_string = new char[ _size + 1 ];

// сначала скопировать в выделенную область исходную строку

// затем дописать в конец строку, на которую ссылается rhs

strcpy( _string, tmp._string );

strcpy( _string + tmp._size, rhs._string );

}

return *this;

}

inline String& String::operator+=( const char *s )

{

// Если указатель s ненулевой

if ( s )

{

String tmp( *this );

// выделить область памяти, достаточную

// для хранения конкатенированных строк

_size += strlen( s );

delete [] _string;

_string = new char[ _size + 1 ];

// сначала скопировать в выделенную область исходную строку

// затем дописать в конец C-строку, на которую ссылается s

strcpy( _string, tmp._string );

strcpy( _string + tmp._size, s );

}

return *this;


}