Как мы только что видели, применение встроенного строкового типа чревато ошибками и не очень удобно из-за того, что он реализован на слишком низком уровне. Поэтому достаточно распространена разработка собственного класса или классов для представления строкового типа – чуть ли не каждая компания, отдел или индивидуальный проект имели свою собственную реализацию строки. Да что говорить, в предыдущих двух изданиях этой книги мы делали то же самое! Это порождало проблемы совместимости и переносимости программ. Реализация стандартного класса 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(), которые возвращают указатели на начало и конец строки, и заменяет элементы, равные третьему своему параметру, на четвертый.
|