while ( st++ ) ++len;
cout len ": " st;
return 0;
}
В этой версии указатель st не разыменовывается. Следовательно, на равенство 0 проверяется не символ, на который указывает st, а сам указатель. Поскольку изначально этот указатель имел ненулевое значение (адрес строки), то он никогда не станет равным нулю, и цикл будет выполняться бесконечно.
Во второй версии программы эта погрешность устранена. Программа успешно заканчивается, однако полученный результат неправилен. Где мы не правы на этот раз?
#include iostream
const char *st = "Цена бутылки вина\n";
int main()
{
int len = 0;
while ( *st++ ) ++len;
cout len ": " st endl;
return 0;
}
Ошибка состоит в том, что после завершения цикла указатель st адресует не исходный символьный литерал, а символ, расположенный в памяти после завершающего нуля этого литерала. В этом месте может находиться что угодно, и выводом программы будет случайная последовательность символов.
Можно попробовать исправить эту ошибку:
st = st – len;
cout len ": " st;
Теперь наша программа выдает что-то осмысленное, но не до конца. Ответ выглядит так:
18: ена бутылки вина
Мы забыли учесть, что заключительный нулевой символ не был включен в подсчитанную длину. 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