3.15. Типы классовМеханизм классов позволяет создавать новые типы данных; с его помощью введены
типы string, vector, complex и pair, рассмотренные выше. В главе
2 мы рассказывали о концепциях и механизмах, поддерживающих объектный и
объектно-ориентированный подход, на примере реализации класса Array. Здесь мы,
основываясь на объектном подходе, создадим простой класс String, реализация
которого поможет понять, в частности, перегрузку операций – мы говорили о ней
в разделе 2.3. (Классы подробно рассматриваются в главах
13, 14 и 15). Мы дали краткое описание класса для того, чтобы приводить более
интересные примеры. Читатель, только начинающий изучение С++, может пропустить
этот раздел и подождать более систематического описания классов в следующих
главах.) #include <iostream> class String; istream& operator>>( istream&, String& ); Класс String имеет три конструктора. Как было сказано в разделе 2.3,
механизм перегрузки позволяет определять несколько реализаций функций с одним
именем, если все они различаются количеством и/или типами своих параметров.
Первый конструктор String object("Danny"); // доступ к члену для objects (.); // objects имеет размер 5 sizes[ 0 ] = object.size(); // доступ к члену для pointers (->) Она возвращает соответственно 5, 4 и 0. String namel( "Yadie" ); String name2( "Yodie" ); // bool operator==(const String&) Объявление функции-члена должно находиться внутри определения класса, а определение функции может стоять как внутри определения класса, так и вне его. (Обе функции size() и c_str() определяются внутри класса.) Если функция определяется вне класса, то мы должны указать, кроме всего прочего, к какому классу она принадлежит. В этом случае определение функции помещается в исходный файл, допустим, String.C, а определение самого класса – в заголовочный файл (String.h в нашем примере), который должен включаться в исходный: // содержимое исходного файла: String.С // включение определения класса String Напомним, что strcmp() – функция стандартной библиотеки С. Она сравнивает две
строки встроенного типа, возвращая 0 в случае равенства строк и ненулевое значение
в случае неравенства. Условный оператор (?:) проверяет значение, стоящее перед
знаком вопроса. Если оно истинно, возвращается значение выражения, стоящего
слева от двоеточия, в противном случае – стоящего справа. В нашем примере значение
выражения равно false, если strcmp() вернула ненулевое значение, и true – если
нулевое. (Условный оператор рассматривается в разделе 4.7.) inline bool String::operator==(const String &rhs) { // то же самое } Определение встроенной функции должно находиться в заголовочном файле, содержащем
определение класса. Переопределив оператор == как встроенный, мы должны переместить
сам текст функции из файла String.C в файл String.h. inline bool String::operator==(const char *s) { return strcmp( _string, s ) ? false : true; } Имя конструктора совпадает с именем класса. Считается, что он не возвращает значение, поэтому не нужно задавать возвращаемое значение ни в его определении, ни в его теле. Конструкторов может быть несколько. Как и любая другая функция, они могут быть объявлены встроенными. #include <cstring> // default constructor inline String::String() inline String::String( const char *str ) { if ( ! str ) { _size = 0; _string = 0; } else { _size = strlen( str ); _string = new char[ _size + 1 ]; strcpy( _string, str ); } // copy constructor Поскольку мы динамически выделяли память с помощью оператора new, необходимо
освободить ее вызовом delete, когда объект String нам больше не нужен. Для этой
цели служит еще одна специальная функция-член – деструктор, автоматически вызываемый
для объекта в тот момент, когда этот объект перестает существовать. (См. главу
7 о времени жизни объекта.) Имя деструктора образовано из символа тильды (~)
и имени класса. Вот определение деструктора класса String. Именно в нем мы вызываем
операцию delete, чтобы освободить память, выделенную в конструкторе: 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 ) Операция взятия индекса практически совпадает с ее реализацией для массива Array, который мы создали в разделе 2.3: #include <cassert> inline char& Операторы ввода и вывода реализуются как отдельные функции, а не члены класса. (О причинах этого мы поговорим в разделе 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; } Оператору вывода необходим доступ к внутреннему представлению строки String. Так как operator<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода: inline ostream& operator<<( ostream& os, const String &s ) { return os << s.c_str(); } Ниже приводится пример программы, использующей класс String. Эта программа берет слова из входного потока и подсчитывает их общее число, а также количество слов "the" и "it" и регистрирует встретившиеся гласные. #include <iostream> #inc1ude "String.h" int main() { int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0; // Слова "The" и "It" Протестируем программу: предложим ей абзац из детского рассказа, написанного одним из авторов этой книги (мы еще встретимся с этим рассказом в главе 6). Вот результат работы программы: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, 1ike a fiery bird in flight. A beautiful fiery bird, he tells her, magical but untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks, "I mean, Daddy, is there?" Слов: 65 Упражнение 3.26В наших реализациях конструкторов и операций присваивания содержится много повторов. Попробуйте вынести повторяющийся код в отдельную закрытую функцию-член, как это было сделано в разделе 2.3. Убедитесь, что новый вариант работоспособен. Упражнение 3.27Модифицируйте тестовую программу так, чтобы она подсчитывала и согласные b, d, f, s, t. Упражнение 3.28Напишите функцию-член, подсчитывающую количество вхождений символа в строку String, используя следующее объявление: class String { public: // ... int count( char ch ) const; // ... }; Упражнение 3.29Реализуйте оператор конкатенации строк (+) так, чтобы он конкатенировал две строки и возвращал результат в новом объекте String. Вот объявление функции: class String { public: // ... String operator+( const String &rhs ) const; // ... };Назад Вперед Содержание |
Нет комментариев. Оставить комментарий: |