Использование С-строк чревато ошибками из-за слишком низкого уровня реализации. Ниже приведены типичные ошибки при работе со строками.
Пример: вычисление длины строки.
Первый вариант программы:
#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 защищены от выхода информации за их границы и с ними можно работать так же, как с любым встроенным типом данных, т.е. с помощью операций.