Определение класса начинается ключевым словом class, за которым следует идентификатор – имя класса, или типа. В общем случае класс состоит из секций, предваряемых словами public (открытая) и private (закрытая). Открытая секция, как правило, содержит набор операций, поддерживаемых классом и называемых методами или функциями-членами класса. Эти функции-члены определяют открытый интерфейс класса, другими словами, набор действий, которые можно совершать с объектами данного класса. В закрытую секцию обычно включают данные-члены, обеспечивающие внутреннюю реализацию. В нашем случае к внутренним членам относятся _string – указатель на char, а также _size типа int. _size будет хранить информацию о длине строки, а _string – динамически выделенный массив символов. Вот как выглядит определение класса:
#include iostream
class String;
istream operator( istream, String );
ostream operator( ostream, const String );
class String {
public:
// набор конструкторов
// для автоматической инициализации
// String strl; // String()
// String str2( "literal" ); // String( const char* );
// String str3( str2 ); // String( const String );
String();
String( const char* );
String( const String );
// деструктор
~String();
// операторы присваивания
// strl = str2
// str3 = "a string literal"
String operator=( const String );
String operator=( const char* );
// операторы проверки на равенство
// strl == str2;
// str3 == "a string literal";
bool operator==( const String );
bool operator==( const char* );
// перегрузка оператора доступа по индексу
// strl[ 0 ] = str2[ 0 ];
char operator[]( int );
// доступ к членам класса
int size() { return _size; }
char* c_str() { return _string; }
private:
int _size;
char *_string;
}
Класс String имеет три конструктора. Как было сказано в разделе 2.3, механизм перегрузки позволяет определять несколько реализаций функций с одним именем, если все они различаются количеством и/или типами своих параметров. Первый конструктор
String();
является конструктором по умолчанию, потому что не требует явного указания начального значения. Когда мы пишем:
String str1;
для str1 вызывается такой конструктор.
Два оставшихся конструктора имеют по одному параметру. Так, для
String str2("строка символов");
вызывается конструктор
String(const char*);
а для
String str3(str2);
конструктор
String(const String);
Тип вызываемого конструктора определяется типом фактического аргумента. Последний из конструкторов, String(const String), называется копирующим, так как он инициализирует объект копией другого объекта.
Если же написать:
String str4(1024);
то это вызовет ошибку компиляции, потому что нет ни одного конструктора с параметром типа int.
Объявление перегруженного оператора имеет следующий формат:
return_type operator op (parameter_list);
где operator – ключевое слово, а op – один из предопределенных операторов: +, =, ==, [] и так далее. (Точное определение синтаксиса см. в главе 15.) Вот объявление перегруженного оператора взятия индекса:
char operator[] (int);
Этот оператор имеет единственный параметр типа int и возвращает ссылку на char. Перегруженный оператор сам может быть перегружен, если списки параметров отдельных конкретизаций различаются. Для нашего класса String мы создадим по два различных оператора присваивания и проверки на равенство.
Для вызова функции-члена применяются операторы доступа к членам – точка (.) или стрелка (-). Пусть мы имеем объявления объектов типа String:
String object("Danny");
String *ptr = new String ("Anna");
String array[2];
Вот как выглядит вызов функции size() для этих объектов:
vectorint sizes( 3 );
// доступ к члену для objects (.);
// objects имеет размер 5
sizes[ 0 ] = object.size();
// доступ к члену для pointers (-)
// ptr имеет размер 4