Мы забыли учесть, что заключительный нулевой символ не был включен в подсчитанную длину. st должен быть смещен на длину строки плюс 1. Вот, наконец, правильный оператор:
st = st – len - 1;
а вот и и правильный результат:
18: Цена бутылки вина
Однако нельзя сказать, что наша программа выглядит элегантно. Оператор
st = st – len - 1;
добавлен для того, чтобы исправить ошибку, допущенную на раннем этапе проектирования программы, – непосредственное увеличение указателя st. Этот оператор не вписывается в логику программы, и код теперь трудно понять. Исправления такого рода часто называют заплатками – нечто, призванное заткнуть дыру в существующей программе. Гораздо лучшим решением было бы пересмотреть логику. Одним из вариантов в нашем случае может быть определение второго указателя, инициализированного значением st:
const char *p = st;
Теперь p можно использовать в цикле вычисления длины, оставив значение st неизменным:
while ( *p++ )
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;