Шрифт:
Интервал:
Закладка:
Удаляем пробелы из начала и конца строк
Полученные из пользовательского ввода строки зачастую содержат лишние пробелы. В одном из предыдущих примеров мы удаляли повторяющиеся пробелы между словами.
Взглянем на пробелы, расположенные вокруг строк, и удалим их. Класс std::string содержит удобные вспомогательные функции, которые нам помогут.
После рассмотрения данного примера, в котором показано, как работать с простыми строковыми объектами, взгляните также и на следующий пример. Там вы увидите, как избежать ненужного копирования или изменения данных при работе с классом std::string_view.
Как это делается
В этом примере мы напишем вспомогательную функцию, которая определяет наличие пробелов в начале и конце строки и возвращает ее копию, но уже без таких пробелов. Затем немного протестируем ее.
1. Как и обычно, сначала идут заголовочные файлы и директива using:
#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>
using namespace std;
2. Наша функция будет принимать константную ссылку на существующую строку. А возвращать станет новую строку, из начала и конца которой удалены лишние пробелы:
string trim_whitespace_surrounding(const string &s)
{
3. Класс std::string предоставляет две удобные функции, которые будут очень полезны. Первая функция — это string::find_first_not_of, она принимает строку, содержащую все символы, которые мы хотим опустить. В нашем случае таковыми являются символы пробела ' ', табуляции 't' и перехода на новую строку 'n'. Функция возвращает позицию первого символа, не совпадающего с переданными. При наличии в строке только пробелов она вернет значение string::npos. Это значит следующее: если мы удалим все пробелы, то останется только пустая строка. Так что в подобных случаях просто возвращайте пустую строку:
const char whitespace[] {" tn"};
const size_t first (s.find_first_not_of(whitespace));
if (string::npos == first) { return {}; }
4. Теперь мы знаем, где должна начинаться новая строка, но пока неизвестно, где она заканчивается. Поэтому воспользуемся другой полезной функцией строки: string::find_last_not_of. Она вернет позицию последнего символа, не являющегося пробелом.
const size_t last (s.find_last_not_of(whitespace));
5. С помощью функции string::substr мы теперь можем вернуть часть строки, окруженной пробелами, в которой их не будет. Эта функция принимает два параметра: позицию от начала строки и количество символов после данной позиции.
return s.substr(first, (last - first + 1));
}
6. На этом все. Напишем функцию main, в которой создадим строку, окружающую предложение всеми видами пробелов, чтобы обрезать ее:
int main()
{
string s {" tn string surrounded by ugly"
" whitespace tn "};
7. Выведем на экран необрезанную и обрезанную версии строки. Окружив строку скобками, можно показать все пробелы, которые находились в ней до обрезки.
cout << "{" << s << "}n";
cout << "{"
<< trim_whitespace_surrounding(s)
<< "}n";
}
8. Компиляция и запуск программы дадут ожидаемый результат:
$ ./trim_whitespace
{
string surrounded by ugly whitespace
}
{string surrounded by ugly whitespace}
Как это работает
В этом разделе мы применили функции string::find_first_not_of и string::find_ last_not_of. Обе принимают строку, созданную в стиле C, в виде списка символов, которые нужно проигнорировать при поиске другого символа. Если у нас есть экземпляр строки, содержащий строку "foo bar", и мы вызовем для него функцию find_first_not_of("bfo "), то она вернет значение 5, поскольку символ 'a' — первый символ, который не входит в строку "bfo". Порядок символов в строке-аргументе неважен.
Существуют подобные функции с инвертированной логикой, однако мы не использовали их в этом примере:
string::find_first_of and string::find_last_of
По аналогии с функциями, основанными на итераторах, нужно проверять, возвращают ли эти функции реальную позицию в строке или же значение, которое указывает, что позиция символа, отвечающего условию, не была найдена. Если такой позиции нет, то они возвращают значение string::npos.
На основе позиций символов, полученных из этих функций, мы в рамках вспомогательной функции создаем подстроку, не содержащую пробелов в начале и конце, с помощью функции string::substring. Она принимает относительное смещение и длину строки, а затем возвращает новый экземпляр строки, для которого выделен собственный фрагмент памяти, содержащий только эту подстроку. Например, вызов string{"abcdef"}.substr(2, 2) вернет новую строку "cd".
Преимущества использования std::string без затрат на создание объектов std::string
Класс std::string очень полезен, поскольку значительно упрощает работу со строками. Его недостаток заключается в том, что при необходимости передать подстроку нужно передавать указатель и переменную, содержащую длину подстроки, два итератора или копию подстроки. Мы делали это в предыдущем примере, когда удаляли лишние пробелы из строки, вернув копию подстроки, которая не содержит их.
Если мы хотим передать строку или подстроку в библиотеку, которая не предоставляет поддержку класса std::string, то можем передать только необработанный указатель на строку, что несколько разочаровывает, поскольку этот способ использовался еще во времена С. Как и в случае с выделением подстроки, необработанный указатель не несет информации о длине строки. Таким образом, кто-то должен будет реализовать связку указателя и длины строки.
Говоря упрощенно, такой конструкцией как раз и является класс std::string_view. Он доступен, начиная с версии C++17, и предоставляет способ объединения указателя на некую строку и ее размера. Он воплощает идею наличия ссылочного типа для массивов данных.
Представим, что разрабатываем функции, которые ранее в качестве параметров принимали объекты типа std::string, но при этом не изменяли их так, чтобы экземплярам класса string потребовалось повторно выделять память, содержащую реальные данные. Мы могли бы использовать тип std::string_view для повышения совместимости с библиотеками, которые не знают об STL. Можно позволить другим библиотекам предоставлять string_view для строк, содержащих полезные данные, скрытые за сложной реализацией типа string, а затем применять их в нашем коде, совместимом с STL. Таким образом, класс string_view ведет себя как минималистичный и полезный интерфейс, пригодный для использования многими библиотеками.
Еще одной приятной особенностью является