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