C++17 STL Стандартная библиотека шаблонов — страница 63 из 119

std::cout
, но не выводит данные на консоль. Вместо этого он выводит данные в строковый буфер. После того, как мы поместим все переменные в поток, разделив их пробелами с помощью оператора
<<
, можем создать и вывести на экран новый объект строки, задействовав конструкцию
o.str()
.


  ostringstream o;

  o << a << " " << b << " " << c << " " << d;

  auto concatenated (o.str());

  cout << concatenated << '\n';


7. Мы также можем преобразовать эту новую строку путем, например, перевода всех ее символов в верхний регистр. Библиотечная функция C

toupper
, которая соотносит символы в нижнем регистре и символы в верхнем, оставляя другие символы неизменными, уже доступна, и ее можно объединить с функцией
std::transform
, поскольку строка, по сути, представляет собой итерабельный контейнер, содержащий элементы типа
char
.


  transform(begin(concatenated), end(concatenated),

            begin(concatenated), ::toupper);

  cout << concatenated << '\n';

}


8. Компиляция и запуск программы дадут следующий результат, который полностью оправдывает наши ожидания:


$ ./creating_strings

a, b

c, d

ab

ac

a b c d

A B C D


Как это работает

Очевидно, строки можно складывать с помощью оператора

+
прямо как числа. Математика здесь не используется, но в итоге мы получаем сконкатенированные строки. Чтобы иметь возможность работать с объектами класса
string_view
, сначала нужно выполнить их преобразование к типу
std::string
.

Однако очень важно отметить: при объединении в коде строк и строковых представлений мы никогда не должны предполагать, что хранящаяся в экземпляре класса

string_view
строка завершается нулевым символом! Именно поэтому мы используем конструкцию
"abc"s + string{some_string_view}
вместо конструкции
"abc"s + some_string_view.data()
. Кроме того, класс
std::string
предоставляет функцию-член
append
, которая может работать с экземплярами класса
string_view
, но изменяет строку вместо того, чтобы вернуть новую строку, к которой прикреплено содержимое строкового представления.


 Класс

std::string_view
очень полезен, но будьте осторожны при смешивании его со строками и строковыми функциями. Нельзя рассчитывать на то, что эти строки будут оканчиваться нулями, поэтому весь порядок быстро нарушается в стандартном строковом окружении. К счастью, зачастую предусмотрены подходящие перегруженные версии функций, которые могут обрабатывать подобные ситуации.


Сложную конкатенацию строк с форматированием и т.д. не следует выполнять шаг за шагом для экземпляров строк. Классы

std::stringstream
,
std::ostringstream
и
std::istringstream
подходят для этого гораздо лучше, так как позволяют более качественно управлять памятью при объединении строк и предоставляют все возможности по форматированию, характерные для потоков. Для данного раздела мы выбрали класс
std::ostringstream
, поскольку собираемся создать строку вместо того, чтобы ее анализировать. Экземпляр класса
std::istringstream
можно создать на основе существующей строки, которая при необходимости преобразуется в переменные других типов. Если мы хотим объединить обе возможности, то нам подойдет класс
std::stringstream
.

Удаляем пробелы из начала и конца строк

Полученные из пользовательского ввода строки зачастую содержат лишние пробелы. В одном из предыдущих примеров мы удаляли повторяющиеся пробелы между словами.

Взглянем на пробелы, расположенные вокруг строк, и удалим их. Класс

std::string
содержит удобные вспомогательные функции, которые нам помогут.


 После рассмотрения данного примера, в котором показано, как работать с простыми строковыми объектами, взгляните также и на следующий пример. Там вы увидите, как избежать ненужного копирования или изменения данных при работе с классом

std::string_view
.


Как это делается

В этом примере мы напишем вспомогательную функцию, которая определяет наличие пробелов в начале и конце строки и возвращает ее копию, но уже без таких пробелов. Затем немного протестируем ее.


1. Как и обычно, сначала идут заголовочные файлы и директива

using
:


#include 

#include 

#include 

#include 


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[] {" \t\n"};

  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 {" \t\n string surrounded by ugly"

            " whitespace \t\n "};


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 ")
, то она вернет значение