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

length: 0 []

length: 20 [a const char * array]

length: 27 [an std::string_view literal]

length: 23 [an std::string instance]

length: 6 [foobar]

length: 3 [abc]


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

Мы только что увидели следующее: можно вызвать функцию, принимающую аргумент типа

string_view
, который способен содержать все, что похоже на строку, — а именно символы, сохраненные непрерывным способом. Во время вызовов нашей функции print не было выполнено ни одной операции копирования.

Интересно отметить, что в вызове

print(argv[0])
строковое представление автоматически определило длину строки, поскольку данная строка по соглашению завершается нулевым символом. С другой стороны, никто не может предполагать, что можно определить длину поля данных объекта типа
string_view
путем подсчета символов до тех пор, пока не встретится нулевой символ. Поэтому нужно всегда соблюдать осторожность при работе с указателями на данные строкового представления с помощью
string_view::data()
. Обычные строковые функции предполагают, что строка будет завершаться нулевым символом, и поэтому использование необработанных указателей способно привести к переполнению буфера. Всегда лучше применять интерфейсы, которые ожидают передачи строкового представления.

За исключением этой особенности, у нас имеется роскошный интерфейс, с которым мы уже знакомы благодаря классу

std::string
.


 Задействуйте класс

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

Считываем значения из пользовательского ввода

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

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

std::cin
— по сути, это объект потока ввода, как и экземпляры классов
ifstream
и
istringstream
.


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

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


1. На сей раз нам понадобится только

iostream
. Так что включим этот единственный заголовочный файл и объявим об использовании пространства имен
std
по умолчанию:


#include 


using namespace std;


2. Пригласим пользователя ввести два числа. Поместим их в переменные типов

int
и
double
. Пользователь может разделить их пробелами. Например,
1 2.3
— это корректные входные данные.


int main()

{

  cout << "Please Enter two numbers:\n> ";

  int x;

  double y;


3. Анализ и проверка ошибок выполняются одновременно в условной части блока

if
. Выведем числа на экран только в том случае, если их можно проанализировать и они имеют смысл:


  if (cin >> x >> y) {

    cout << "You entered: " << x

<< " and " << y << '\n';


4. Если по какой-то причине анализ завершился ошибкой, то мы скажем пользователю об этом. Объект потока

cin
теперь находится в состоянии ошибки и не будет давать другие данные до тех пор, пока мы не избавимся от этого состояния. Чтобы иметь возможность проанализировать новые входные данные после ошибки, вызываем метод
cin.clear()
и отбрасываем все входные данные, полученные ранее. Отбрасывание выполняется с помощью
cin.ignore
, где мы указываем, что нужно отбросить максимальное количество символов до тех пор, пока не встретим символ новой строки (который тоже будет отброшен). После этого символа снова начинаются интересные входные данные:


  } else {

    cout << "Oh no, that did not go well!\n";

    cin.clear();

    cin.ignore(

      std::numeric_limits::max(),

      '\n');

  }


5. Теперь запросим еще какие-нибудь входные данные. Мы позволим пользователю вводить имена. Они могут состоять из нескольких слов, разделенных пробелами, поэтому символ пробела не подходит в качестве разделителя. Так что воспользуемся

std::getline
, принимающим объектом потока наподобие
cin
, ссылкой на строку, в которую будут скопированы входные данные, и символом-разделителем (таковым выступит запятая
','
). Задействуя вместо
cin
конструкцию
cin >> ws
в качестве параметра потока для
getline
, можно дать
cin
команду отбросить пробелы, стоящие перед именами. На каждом шаге цикла выводим текущее имя, но если оно пустое, то завершим цикл:


  cout << "now please enter some "

          "comma-separated names:\n> ";

  for (string s; getline(cin >> ws, s, ',');) {

    if (s.empty()) { break; }

    cout << "name: \"" << s << "\"\n";

  }

}


6. Компиляция и запуск программы дадут следующий результат (для него предполагается введение только корректных значений). Мы ввели числа

"1 2"
, они были преобразованы корректно, а затем ввели некоторые имена, также корректно выведенные на экран. Пустое имя, полученное в виде двух запятых, расположенных рядом друг с другом, завершает цикл:


$ ./strings_from_user_input Please Enter two numbers:

> 1 2

You entered: 1 and 2

now please enter some comma-separated names:

> john doe, ellen ripley, alice, chuck norris,,

name: "john doe"

name: "ellen ripley"

name: "alice"

name: "chuck norris"


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

cin.clear()
и
cin.ignore(...)
, чтобы увидеть, как это пересекается с кодом чтения имен:


$ ./strings_from_user_input

Please Enter two numbers:

> a b

Oh no, that did not go well!

now please enter some comma-separated names:

> bud spencer, terence hill,,

name: "bud spencer"

name: "terence hill"


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

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

Результатом выполнения выражения

cin >> x
является ссылка на
cin
. Таким образом можно создавать конструкции наподобие
cin >> x >> y >> z >> ....
В то же время можно преобразовать данные к булевым значениям, используя их в булевых контекстах, таких как условие
if
. Булево значение говорит нам, была ли корректной последняя операция чтения. Вот почему можно создавать конструкции, аналогичные выражению
if (cin