Выбрать главу

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