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

3.4.2. Класс string

Как мы только что видели, применение встроенного строкового типа чревато ошибками и не очень удобно из-за того, что он реализован на слишком низком уровне. Поэтому достаточно распространена разработка собственного класса или классов для представления строкового типа – чуть ли не каждая компания, отдел или индивидуальный проект имели свою собственную реализацию строки. Да что говорить, в предыдущих двух изданиях этой книги мы делали то же самое! Это порождало проблемы совместимости и переносимости программ. Реализация стандартного класса string стандартной библиотекой С++ призвана была положить конец этому изобретению велосипедов.

Попробуем специфицировать минимальный набор операций, которыми должен обладать класс string:

  • инициализация массивом символов (строкой встроенного типа) или другим объектом типа string. Встроенный тип не обладает второй возможностью;

  • копирование одной строки в другую. Для встроенного типа приходится использовать функцию strcpy();

  • доступ к отдельным символам строки для чтения и записи. Во встроенном массиве для этого применяется операция взятия индекса или косвенная адресация;

  • сравнение двух строк на равенство. Для встроенного типа используется функция strcmp();

  • конкатенация двух строк, получая результат либо как третью строку, либо вместо одной из исходных. Для встроенного типа применяется функция strcat(), однако чтобы получить результат в новой строке, необходимо последовательно задействовать функции strcpy() и strcat();

  • вычисление длины строки. Узнать длину строки встроенного типа можно с помощью функции strlen();

  • возможность узнать, пуста ли строка. У встроенных строк для этой цели приходится проверять два условия:

char str = 0;

//...

if ( ! str || ! *str )


return;

Класс string стандартной библиотеки С++ реализует все перечисленные операции (и гораздо больше, как мы увидим в главе 6). В данном разделе мы научимся пользоваться основными операциями этого класса.

Для того чтобы использовать объекты класса string, необходимо включить соответствующий заголовочный файл:

#include <string>

Вот пример строки из предыдущего раздела, представленной объектом типа string и инициализированной строкой символов:

#include <string>


string st( "Цена бутылки вина\n" );

Длину строки возвращает функция-член size() (длина не включает завершающий нулевой символ).

cout << "Длина "

<< st

<< ": " << st.size()

<< " символов, включая символ новой строки\n";

Вторая форма определения строки задает пустую строку:

string st2; // пустая строка

Как мы узнаем, пуста ли строка? Конечно, можно сравнить ее длину с 0:

if ( ! st.size() )

// правильно: пустая

Однако есть и специальный метод empty(), возвращающий true для пустой строки и false для непустой:

if ( st.empty() )

// правильно: пустая

Третья форма конструктора инициализирует объект типа string другим объектом того же типа:

string st3( st );

Строка st3 инициализируется строкой st. Как мы можем убедиться, что эти строки совпадают? Воспользуемся оператором сравнения (==):

if ( st == st3 )

// инициализация сработала

Как скопировать одну строку в другую? С помощью обычной операции присваивания:

st2 = st3; // копируем st3 в st2

Для конкатенации строк используется операция сложения (+) или операция сложения с присваиванием (+=). Пусть даны две строки:

string s1( "hello, " );

string s2( "world\n" );

Мы можем получить третью строку, состоящую из конкатенации первых двух, таким образом:

string s3 = s1 + s2;

Если же мы хотим добавить s2 в конец s1, мы должны написать:

s1 += s2;

Операция сложения может конкатенировать объекты класса string не только между собой, но и со строками встроенного типа. Можно переписать пример, приведенный выше, так, чтобы специальные символы и знаки препинания представлялись встроенным типом, а значимые слова – объектами класса string:

const char *pc = ", ";

string s1( "hello" );


string s2( "world" );

string s3 = s1 + pc + s2 + "\n";

Подобные выражения работают потому, что компилятор знает, как автоматически преобразовывать объекты встроенного типа в объекты класса string. Возможно и простое присваивание встроенной строки объекту string:

string s1;

const char *pc = "a character array";

s1 = pc; // правильно

Обратное преобразование, однако, не работает. Попытка выполнить следующую инициализацию строки встроенного типа вызовет ошибку компиляции:

char *str = s1; // ошибка компиляции

Чтобы осуществить такое преобразование, необходимо явно вызвать функцию-член с несколько странным названием c_str():

char *str = s1.c_str(); // почти правильно

Функция c_str() возвращает указатель на символьный массив, содержащий строку объекта string в том виде, в каком она находилась бы во встроенном строковом типе.

Приведенный выше пример инициализации указателя char *str все еще не совсем корректен. c_str() возвращает указатель на константный массив, чтобы предотвратить возможность непосредственной модификации содержимого объекта через этот указатель, имеющий тип

const char *

(В следующем разделе мы расскажем о ключевом слове const). Правильный вариант инициализации выглядит так:

const char *str = s1.c_str(); // правильно

К отдельным символам объекта типа string, как и встроенного типа, можно обращаться с помощью операции взятия индекса. Вот, например, фрагмент кода, заменяющего все точки символами подчеркивания:

string str( "fa.disney.com" );

int size = str.size();

for ( int ix = 0; ix < size; ++ix )

if ( str[ ix ] == '.' )


str[ ix ] = '_';

Вот и все, что мы хотели сказать о классе string прямо сейчас. На самом деле, этот класс обладает еще многими интересными свойствами и возможностями. Скажем, предыдущий пример реализуется также вызовом одной-единственной функции replace():

replace( str.begin(), str.end(), '.', '_' );

replace() – один из обобщенных алгоритмов, с которыми мы познакомились в разделе 2.8 и которые будут детально разобраны в главе 12. Эта функция пробегает диапазон от begin() до end(), которые возвращают указатели на начало и конец строки, и заменяет элементы, равные третьему своему параметру, на четвертый.

Упражнение 3.12

Найдите ошибки в приведенных ниже операторах:

(a) char ch = "The long and winding road";

(b) int ival = &ch;

(c) char *pc = &ival;

(d) string st( &ch );

(e) pc = 0; (i) pc = '0';

(f) st = pc; (j) st = &ival;

(g) ch = pc[0]; (k) ch = *pc;


(h) pc = st; (l) *pc = ival;

Упражнение 3.13

Объясните разницу в поведении следующих операторов цикла:

while ( st++ )


++cnt;

while ( *st++ )

++cnt;

Упражнение 3.14

Даны две семантически эквивалентные программы. Первая использует встроенный строковый тип, вторая – класс string:

// ***** Реализация с использованием C-строк *****


#include <iostream>

#include <cstring>

int main()

{

int errors = 0;

const char *pc = "a very long literal string";

for ( int ix = 0; ix < 1000000; ++ix )

{

int len = strlen( pc );

char *pc2 = new char[ len + 1 ];

strcpy( pc2, pc );

if ( strcmp( pc2, pc ))

++errors;

delete [] pc2;

}

cout << "C-строки: "

<< errors << " ошибок.\n";

}

// ***** Реализация с использованием класса string *****


#include <iostream>

#include <string>

int main()

{

int errors = 0;

string str( "a very long literal string" );

for ( int ix = 0; ix < 1000000; ++ix )

{

int len = str.size();

string str2 = str;

if ( str != str2 )

}

cout << "класс string: "

<< errors << " ошибок.\n;

}

Что эти программы делают?

Оказывается, вторая реализация выполняется в два раза быстрее первой. Ожидали ли вы такого результата? Как вы его объясните?

Упражнение 3.15

Могли бы вы что-нибудь улучшить или дополнить в наборе операций класса string, приведенных в последнем разделе? Поясните свои предложения.