Типичные ошибки при работе со строками

Использование С-строк чревато ошибками из-за слишком низкого уровня реализации. Ниже приведены типичные ошибки при работе со строками.

Пример:вычисление длины строки.

Первый вариант программы:

#include <iostream>

const char *st = "Цена бутылки вина\n";

int main( ) {

int len = 0;

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++ )

Класс string

Применение С-строк позволяет достичь высокой эффективности, но при этом небезопасно и неудобно из-за слишком низкого уровня реализации. Применение строкового класса повышает уровень абстракции и позволяет решить данные проблемы. Основные действия со строками выполняются в нем с помошью операций и методов, а длина строки изменяется динамически в соответствии с потребностями. Хотя при этом строковый класс уступает С-строкам в эффективности.

Стандартный строковый класс должен обладать следующим минимальным набором операций:

· инициализация массивом символов (С-строкой) или другим объектом типа string (для С-строк такая возможность недоступна);

· копирование одной строки в другую (для С-строк используется функция strcpy);

· доступ к отдельным символам строки для чтения и записи (для С-строк используется операция индексации или косвенная адресация);

· сравнение двух строк на равенство (для С-строк используется функция strcmp);

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

· вычисление длины строки (для С-строк используется функция strlen);

· возможность узнать, пуста ли строка (для С-строк необходимо проверять условие ! str || ! *str ).

В классе string стандартной библиотеки С++ реализуется большое колическтво функций, в том числе и все вышеперечисленные операции. Для того чтобы использовать объекты класса string, необходимо включить соответствующий заголовочный файл string.h.

Пример: сравнение работы с С-строками и строками класса string.

#include <string.h>

#include <iostream.h>

int main ( ){

char c1[80], c2[80], с3[80]; // C-строки

string s1, s2, s3; // строки класса string

strcpy(c1, "С-string one"); // Присваивание C-строк

strcpy(c2, c1);

s1 = "С++ string one"; // Присваивание строк класса string

s2 = s1;

strcpy(c3, c1); // Конкатенация C-строк

strcat (c3, c2);

s3 = s1 + s2; // Конкатенация строк класса string

if (strcmp(c2, c3) < 0 ) cout << c2; // Сравнение C-строк

else cout << c3;

if (s2 < s3) cout << s2; // Сравнение C-строк

else cout << s3;

return 0;

}

Как видно из примера, выполнение любых действий с C-строками требует использования функций и менее наглядно. Кроме того, необходимо проверять, достаточно ли места в строке-приемнике при копировании, т.е. код для работы с С-строками может быть еще и более длинным (в примере проверка не реализуется).

Строки класса string защищены от выхода информации за их границы и с ними можно работать так же, как с любым встроенным типом данных, т.е. с помощью операций.