Cтроки. Обработка строк с завершающим нулём
В самом общем виде строка представляет собой массив символов, т.е. элементы такого массива имеют символьный тип char или wchar_t (char16_t и char32_t используются намного реже). Тем не менее, обработка строк имеет свою специфику, связанную с тем, что количество символов в строке (длина строки) обычно становится известным только после того, как строка уже введена, а различные действия над строкой часто приводят к изменению её длины.
Как уже говорилось, в С++ имеется два основных способа работы со строками, основанных на разных способах их представления в памяти:
1. использование строк с завершающим нулём (С-строк) – способ, доставшийся в наследство от языка С, при котором строка описывается как обычный символьный массив, размер которого определяется исходя из ограничений задачи на максимальный размер вводимого текста;
2. использование типов string и wstring, определённых в стандартной библиотеке С++, не накладывающих ограничений на размер текста; для каких-то задач обработки текстов можно использовать и тип vector<char> (vector<wchar_t>).
В данном разделе рассмотрим строки в стиле языка С. Строка в C – это последовательность символов, заканчивающаяся символом c кодом 0 ('\0') - этот символ кодовой таблицы всегда используется только для служебных целей (не перепутайте с цифрой 0 – её код 48). В памяти строка представлена как массив с элементами символьного типа, при этом завершающий ноль отделяет текст от оставшейся области памяти, выделенной под строку, – память обычно выделяется с избытком. Обратим внимание, что при использовании строк в стиле С вся ответственность за выделение памяти целиком ложится на программиста!
Описание строкрассмотрим на примерах.
char color[16] – создали массив символов без его заполнения.
char color [16] = "blue " – создали массив из 16 элементов, заполнили первые четыре элемента, пятому элементу автоматически присвоится '\0'.
сhar color [] = "blue " – в отличие от предыдущего варианта, размер массива будет подсчитан автоматически как размер строковой константы+1 (у нас 5).
Ввод-вывод строк.При использовании библиотеки iostream для ввода строк рекомендуется использовать функции cin.getline или cin.get. Например:
char s[256]; cin.getline(s, 256); // или cin.get(s,256);
Разница между cin.getline и cin.get в том, что cin.get при считывании строки оставляет в буфере клавиатуры символ-ограничитель (по умолчанию это символ перевода строки, которым мы заканчиваем ввод), при этом следующий cin.get (если он есть), прочитает этот ограничитель и без остановки программы введёт пустую строку.
Рекомендуем использовать для ввода cin.getline, чтобы не попасть в эту ловушку.
Обратим внимание, что cin>>s; прочитает символы строки до первого пробела (!) – этот способ можно использовать, если вводимая строка не содержит пробелов.
Вывод строки можно выполнить обычным способом через выходной поток cout, например: cout<<s;
Можно вывести строку и посимвольно с помощью цикла – ведь строка это массив символов. Например: for (i=0; s[i]!=0;i++) cout<<s[i];
Следующий пример – демонстрация того, что строку можно обрабатывать как обычный массив символов, не забывая про завершающий нуль.
Пусть требуется удалить все пробелы из введённой строки текста.
Поскольку удаление каждого символа из массива – длительная операция перемещения всех следующих за ним символов, то возникает вопрос, как сократить число перемещений при удалении всех разбросанных по строке пробелов. Первое решение приходит в голову сразу – использовать вспомогательную строку, в которую переписать все символы, кроме пробелов. Разумеется, потом вспомогательная строка копируется в исходную. Этот вариант реализован в примере 1.7.
// Пример 1.7 – удаление пробелов из строки
// с использованием вспомогательной строки
#include <iostream>
using namespace std;
int main()
{
int r=0,i=0; char s[80],s1[80];
cout<<"?"; cin.getline(s,80);
// перепишем в s1 все символы, кроме пробелов
for(i=0;s[i]!=0;i++)
if (s[i]!=' ') s1[r++]=s[i];
s1[r]=0; // нельзя забыть про завершающий нуль
for (i=0;s1[i]!=0;i++) s[i]=s1[i];
s[i]=0; // скопировали s1 обратно в s
cout<<s<<endl; system("pause"); return 0;
}
Первый пришедший в голову вариант не всегда самый лучший. Немного подумав, можно представить более эффективный вариант, который удаляет пробелы и без использования вспомогательной строки:
// Пример 1.8 – удаление пробелов из строки
// без использования вспомогательной строки
#include <iostream>
using namespace std;
int main()
{
int r=0,i=0; char s[80];
cout<<"?"; cin.getline(s,80);
for (i = 0; s[i] != 0 && s[i] != ' '; i++);// ищем первый пробел
for (int j = i; s[j] != 0; j++) {
if (s[j] != ' ') {
s[i] = s[j];// передвигаем символы внутри строки
i++;
}
}
s[i] = 0;
cout<<s<<endl; system("pause"); return 0;
}
Этот вариант можно ускорить только использованием указателей вместо индексов. Далее мы рассмотрим возможности эффективной обработки массивов и строк, связанные с применением указателей.