Iostream stream error

ifstream f; f.open(fileName); if ( f.fail() ) { // I need error message here, like "File not found" etc. - // the reason of the failure } How to get error message as string?
ifstream f;
f.open(fileName);

if ( f.fail() )
{
    // I need error message here, like "File not found" etc. -
    // the reason of the failure
}

How to get error message as string?

László Papp's user avatar

László Papp

51.1k39 gold badges110 silver badges135 bronze badges

asked Jun 27, 2013 at 7:51

Alex F's user avatar

5

Every system call that fails update the errno value.

Thus, you can have more information about what happens when a ifstream open fails by using something like :

cerr << "Error: " << strerror(errno);

However, since every system call updates the global errno value, you may have issues in a multithreaded application, if another system call triggers an error between the execution of the f.open and use of errno.

On system with POSIX standard:

errno is thread-local; setting it in one thread does not affect its
value in any other thread.


Edit (thanks to Arne Mertz and other people in the comments):

e.what() seemed at first to be a more C++-idiomatically correct way of implementing this, however the string returned by this function is implementation-dependant and (at least in G++’s libstdc++) this string has no useful information about the reason behind the error…

S.R's user avatar

S.R

2,1911 gold badge21 silver badges32 bronze badges

answered Jun 27, 2013 at 9:02

Matthieu Rouget's user avatar

5

You could try letting the stream throw an exception on failure:

std::ifstream f;
//prepare f to throw if failbit gets set
std::ios_base::iostate exceptionMask = f.exceptions() | std::ios::failbit;
f.exceptions(exceptionMask);

try {
  f.open(fileName);
}
catch (std::ios_base::failure& e) {
  std::cerr << e.what() << 'n';
}

e.what(), however, does not seem to be very helpful:

  • I tried it on Win7, Embarcadero RAD Studio 2010 where it gives «ios_base::failbit set» whereas strerror(errno) gives «No such file or directory.»
  • On Ubuntu 13.04, gcc 4.7.3 the exception says «basic_ios::clear» (thanks to arne)

If e.what() does not work for you (I don’t know what it will tell you about the error, since that’s not standardized), try using std::make_error_condition (C++11 only):

catch (std::ios_base::failure& e) {
  if ( e.code() == std::make_error_condition(std::io_errc::stream) )
    std::cerr << "Stream error!n"; 
  else
    std::cerr << "Unknown failure opening file.n";
}

Community's user avatar

answered Jun 27, 2013 at 8:19

Arne Mertz's user avatar

Arne MertzArne Mertz

23.9k2 gold badges51 silver badges89 bronze badges

9

Following on @Arne Mertz’s answer, as of C++11 std::ios_base::failure inherits from system_error (see http://www.cplusplus.com/reference/ios/ios_base/failure/), which contains both the error code and message that strerror(errno) would return.

std::ifstream f;

// Set exceptions to be thrown on failure
f.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    f.open(fileName);
} catch (std::system_error& e) {
    std::cerr << e.code().message() << std::endl;
}

This prints No such file or directory. if fileName doesn’t exist.

answered Apr 14, 2016 at 4:35

rthur's user avatar

rthurrthur

1,4361 gold badge15 silver badges15 bronze badges

5

You can also throw a std::system_error as shown in the test code below. This method seems to produce more readable output than f.exception(...).

#include <exception> // <-- requires this
#include <fstream>
#include <iostream>

void process(const std::string& fileName) {
    std::ifstream f;
    f.open(fileName);

    // after open, check f and throw std::system_error with the errno
    if (!f)
        throw std::system_error(errno, std::system_category(), "failed to open "+fileName);

    std::clog << "opened " << fileName << std::endl;
}

int main(int argc, char* argv[]) {
    try {
        process(argv[1]);
    } catch (const std::system_error& e) {
        std::clog << e.what() << " (" << e.code() << ")" << std::endl;
    }
    return 0;
}

Example output (Ubuntu w/clang):

$ ./test /root/.profile
failed to open /root/.profile: Permission denied (system:13)
$ ./test missing.txt
failed to open missing.txt: No such file or directory (system:2)
$ ./test ./test
opened ./test
$ ./test $(printf '%0999x')
failed to open 000...000: File name too long (system:36)

answered Jun 30, 2018 at 22:27

ɲeuroburɳ's user avatar

ɲeuroburɳɲeuroburɳ

6,9103 gold badges23 silver badges22 bronze badges

The std::system_error example above is slightly incorrect. std::system_category() will map the error codes from system’s native error code facility. For *nix, this is errno. For Win32, it is GetLastError(). ie, on Windows, the above example will print

failed to open C:pathtoforbidden: The data is invalid

because EACCES is 13 which is the Win32 error code ERROR_INVALID_DATA

To fix it, either use the system’s native error code facility, eg on Win32

throw new std::system_error(GetLastError(), std::system_category(), "failed to open"+ filename);

Or use errno and std::generic_category(), eg

throw new std::system_error(errno, std::generic_category(), "failed to open"+ filename);

answered Feb 16, 2021 at 17:54

Daniel Cranford's user avatar

h_roman

1 / 1 / 0

Регистрация: 24.01.2014

Сообщений: 16

1

24.01.2014, 22:30. Показов 4389. Ответов 12

Метки нет (Все метки)


при достижении конца файла?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try
    {
        ifstream fin("file.txt");
        fin.exceptions(ios_base::failbit);
 
        int sum = 0;
        int temp;
        while (fin >> temp)
        {
            sum += temp;
        }
        cout << sum;
    }
    catch (exception& x)
    {
        cout << x.what();
    }

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



5493 / 4888 / 831

Регистрация: 04.06.2011

Сообщений: 13,587

24.01.2014, 22:36

2

Цитата
Сообщение от h_roman
Посмотреть сообщение

при достижении конца файла?

Нет. При ошибке в потоке.

Добавлено через 1 минуту
Условные операторы, при проверке состояния потока, вызывают fail(), и если !fail() (fail() вернул 0), то true, т.е. проверяют только failbit и badbit, но не eofbit.



0



1 / 1 / 0

Регистрация: 24.01.2014

Сообщений: 16

24.01.2014, 22:46

 [ТС]

3

ну так почему, например, когда в файле: 1 2 3
и никаких ошибок, в консоль выводит: ios_base :: failbit set: iostream stream error ?



0



Don’t worry, be happy

17781 / 10545 / 2036

Регистрация: 27.09.2012

Сообщений: 26,516

Записей в блоге: 1

24.01.2014, 22:53

4



0



5493 / 4888 / 831

Регистрация: 04.06.2011

Сообщений: 13,587

24.01.2014, 23:08

5

Цитата
Сообщение от h_roman
Посмотреть сообщение

и никаких ошибок,

Что значит никаких ошибок? Выход из while() будет при ошибке в потоке. Написал же:

Цитата
Сообщение от alsav22
Посмотреть сообщение

Условные операторы, при проверке состояния потока, вызывают fail(), и если !fail() (fail() вернул 0), то true, т.е. проверяют только failbit и badbit, но не eofbit.

После чтения последнего числа, будет ещё чтение, которое и вызовет ошибку.



0



Kastaneda

5225 / 3197 / 362

Регистрация: 12.12.2009

Сообщений: 8,101

Записей в блоге: 2

24.01.2014, 23:11

6

Цитата
Сообщение от alsav22
Посмотреть сообщение

Условные операторы, при проверке состояния потока, вызывают fail()

В конечном счете все сводится к вызову fail(), но технически проверка происходит так: оператор >> возвращает ссылку на поток. if’у для проверки условия нужно выражение bool, поэтому поток нужно привести к bool. Явно это сделать нельзя, поэтому, согласно стандарту, поток в итоге приводится к void*. А где-то в недрах std::istream (или где-то в предках) есть operator void *(), который может выглядеть примерно так:

C++
1
2
3
4
operator void *() const
{
    return fail() ? 0 : (void*)this;
}

поэтому код, заключенный в if () {} выполняется в случае !fail();



1



alsav22

5493 / 4888 / 831

Регистрация: 04.06.2011

Сообщений: 13,587

24.01.2014, 23:22

7

Цитата
Сообщение от h_roman
Посмотреть сообщение

ну так почему, например, когда в файле: 1 2 3
и никаких ошибок, в консоль выводит: ios_base :: failbit set: iostream stream error ?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <cstdlib>
#include <iostream>
#include <fstream>
using namespace std;
 
int main()
{
 ifstream fin("file.txt");
 if (!fin) cout << "Error!" << endl;
 else
 {
     int sum = 0;
     int temp;
     while (fin >> temp)
     {
        sum += temp;
     }
     cout << "fail = " << fin.fail() << endl;
     cout << sum << endl;
 }
 
    system("pause");
    return 0;
}



0



Kastaneda

5225 / 3197 / 362

Регистрация: 12.12.2009

Сообщений: 8,101

Записей в блоге: 2

24.01.2014, 23:23

8

Цитата
Сообщение от alsav22
Посмотреть сообщение

Всё понял, кроме этого:

имел ввиду, когда в if’e что-то типа такого

C++
1
if (std::cin >> tmp) {}

ну или

C++
1
while (fin >> temp) {}

как у тебя.



0



alsav22

24.01.2014, 23:27

Не по теме:

Цитата
Сообщение от Kastaneda
Посмотреть сообщение

имел ввиду, когда в if’e что-то типа такого

Уже разобрался, я же сам это написал:

Цитата
Сообщение от alsav22
Посмотреть сообщение

и если !fail() (fail() вернул 0), то true,



0



1 / 1 / 0

Регистрация: 24.01.2014

Сообщений: 16

25.01.2014, 15:48

 [ТС]

10

Цитата
Сообщение от alsav22
Посмотреть сообщение

Что значит никаких ошибок?

имею в виду никаких ошибок в файле)

Цитата
Сообщение от alsav22
Посмотреть сообщение

После чтения последнего числа, будет ещё чтение, которое и вызовет ошибку.

я не очень понимаю
разве не должен только eofbit установиться в 1 после чтения последнего числа?



0



5493 / 4888 / 831

Регистрация: 04.06.2011

Сообщений: 13,587

25.01.2014, 17:03

11

Цитата
Сообщение от h_roman
Посмотреть сообщение

разве не должен только eofbit установиться в 1 после чтения последнего числа?

Установится и что? Вы читайте внимательно, что пишут:

Цитата
Сообщение от alsav22
Посмотреть сообщение

Условные операторы, при проверке состояния потока, вызывают fail(), и если !fail() (fail() вернул 0), то true, т.е. проверяют только failbit и badbit, но не eofbit.

eofbit установится, но failbit (который условные операторы проверяют) нет, поэтому выхода из while() не будет, будет ещё одна итерация. Попытка чтения, при установленном eofbit, приведёт к ошибке в потоке, установится failbit, в результате произойдёт выход и while().

Добавлено через 1 минуту

Цитата
Сообщение от h_roman
Посмотреть сообщение

имею в виду никаких ошибок в файле)

Это, кстати, что такое: ошибки в файле?



0



Kastaneda

25.01.2014, 18:24


    когда failbit устанавливается в 1?

Не по теме:

Цитата
Сообщение от Tulosba
Посмотреть сообщение

В С++11 добавили, не знал.



0



Defined in header <ios>

class failure;

The class std::ios_base::failure defines an exception object that is thrown on failure by the functions in the Input/Output library.

std::ios_base::failure may be defined either as a member class of std::ios_base or as a synonym (typedef) for another class with equivalent functionality.

(since C++17)

std-ios base-failure-2003-inheritance.svg

Inheritance diagram

(until C++11)

std-ios base-failure-inheritance.svg

Inheritance diagram

(since C++11)

Contents

  • 1 Member functions
  • 2 std::ios_base::failure::failure
    • 2.1 Parameters
    • 2.2 Notes
  • 3 std::ios_base::failure::operator=
    • 3.1 Parameters
    • 3.2 Return value
  • 4 std::ios_base::failure::what
    • 4.1 Parameters
    • 4.2 Return value
    • 4.3 Notes
  • 5 Inherited from std::system_error
    • 5.1 Member functions
  • 6 Inherited from std::runtime_error
  • 7 Inherited from std::exception
    • 7.1 Member functions
    • 7.2 Notes
    • 7.3 Example
    • 7.4 Defect reports
    • 7.5 See also

[edit] Member functions

constructs a new failure object with the given message
(public member function)
replaces the failure object
(public member function)
returns the explanatory string
(public member function)

std::ios_base::failure::failure

(1)

explicit failure( const std::string& message );

(until C++11)

explicit failure( const std::string& message,
                  const std::error_code& ec = std::io_errc::stream );

(since C++11)

explicit failure( const char* message,
                  const std::error_code& ec = std::io_errc::stream );

(2) (since C++11)
(3)

failure( const failure& other );

(until C++11)

failure( const failure& other ) noexcept;

(since C++11)

1-2) Constructs the exception object using message as explanation string which can later be retrieved using

what()

. ec is used to identify the specific reason for the failure. (since C++11)

3) Copy constructor. Initialize the contents with those of other. If *this and other both have dynamic type std::ios_base::failure then std::strcmp(what(), other.what()) == 0. (since C++11)

Parameters

message explanatory string
ec error code to identify the specific reason for the failure
other another failure to copy

Notes

Because copying std::ios_base::failure is not permitted to throw exceptions, this message is typically stored internally as a separately-allocated reference-counted string. This is also why there is no constructor taking std::string&&: it would have to copy the content anyway.

std::ios_base::failure::operator=

failure& operator=( const failure& other );

(until C++11)

failure& operator=( const failure& other ) noexcept;

(since C++11)

Assigns the contents with those of other. If *this and other both have dynamic type std::ios_base::failure then std::strcmp(what(), other.what()) == 0 after assignment. (since C++11)

Parameters

other another exception object to assign with

Return value

*this

std::ios_base::failure::what

virtual const char* what() const throw();

(until C++11)

virtual const char* what() const noexcept;

(since C++11)

Returns the explanatory string.

Parameters

(none)

Return value

Pointer to a null-terminated string with explanatory information. The string is suitable for conversion and display as a std::wstring. The pointer is guaranteed to be valid at least until the exception object from which it is obtained is destroyed, or until a non-const member function (e.g. copy assignment operator) on the exception object is called.

Notes

Implementations are allowed but not required to override what().

Inherited from std::system_error

Member functions

returns error code
(public member function of std::system_error) [edit]
returns an explanatory string
(virtual public member function of std::system_error) [edit]

Inherited from std::exception

Member functions

destroys the exception object
(virtual public member function of std::exception) [edit]
returns an explanatory string
(virtual public member function of std::exception) [edit]

[edit] Notes

Before the resolution of LWG issue 331, std::ios_base::failure declared a desctructor without throw(), where std::exception::~exception() was declared with throw()[1]. This means the std::ios_base::failure::~failure() had a weaker exception specification. The resolution is to remove that declaration so that the non-throwing exception specification is kept.

LWG issue 363 targets the same defect and its resolution is to add throw() to the declaraion of std::ios_base::failure::~failure(). That resolution was not applied due to the confict between the two resolutions.

  1. The non-throwing exception specification is now applied gloabally across the standard library, so the destructors of standard library classes are not declared with throw() or noexcept.

[edit] Example

#include <iostream>
#include <fstream>
 
int main()
{
    std::ifstream f("doesn't exist");
 
    try
    {
        f.exceptions(f.failbit);
    }
    catch (const std::ios_base::failure& e)
    {
        std::cout << "Caught an ios_base::failure.n"
                  << "Explanatory string: " << e.what() << 'n'
                  << "Error code: " << e.code() << 'n';
    }
}

Possible output:

Caught an ios_base::failure.
Explanatory string: ios_base::clear: unspecified iostream_category error
Error code: iostream:1

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
LWG 48 C++98 the constructor overload (1) initialized the base class std::exception
with msg, but the base class does not have a matching constructor
corresponding
description removed
LWG 331 C++98 std::ios_base::failure declared a destructor without throw() removed the destructor declaration

[edit] See also

Добавлено 9 октября 2021 в 21:35

Состояния потока

Класс ios_base содержит несколько флагов состояния, которые используются для сигнализации различных условий, которые могут возникнуть при использовании потоков:

Флаг Назначение
goodbit Всё в порядке
badbit Произошла какая-то фатальная ошибка (например, программа попыталась прочитать после конца файла)
eofbit Поток достиг конца файла
failbit Произошла нефатальная ошибка (например, пользователь ввел буквы, когда программа ожидала целое число)

Хотя эти флаги находятся в ios_base, но поскольку ios является производным от ios_base, а ios требует меньше ввода текста, чем ios_base, доступ к ним обычно осуществляется через него (например, как std::ios::failbit).

ios также предоставляет ряд функций-членов для удобного доступа к этим состояниям:

Функция-член Назначение
good() Возвращает true, если установлен goodbit (поток в норме)
bad() Возвращает true, если установлен badbit (произошла фатальная ошибка)
eof() Возвращает true, если установлен eofbit (поток находится в конце файла)
fail() Возвращает true, если установлен failbit (произошла нефатальная ошибка)
clear() Очищает все флаги и восстанавливает поток в состояние goodbit
clear(state) Очищает все флаги и устанавливает флаг состояния, переданный в параметре
rdstate() Возвращает текущие установленные флаги
setstate(state) Устанавливает флаг состояния, переданный в параметре

Чаще всего мы имеем дело failbit, который устанавливается, когда пользователь вводит недопустимые входные данные. Например, рассмотрим следующую программу:

std::cout << "Enter your age: ";
int age;
std::cin >> age;

Обратите внимание, что эта программа ожидает, что пользователь введет целое число. Однако если пользователь вводит нечисловые данные, такие как «Alex«, cin не сможет извлечь что-либо для переменной возраста age, и будет установлен бит отказа failbit.

Если такая ошибка возникает, и для потока устанавливается значение, отличное от goodbit, дальнейшие операции с этим потоком будут проигнорированы. Это условие можно устранить, вызвав функцию clear().

Проверка корректности входных данных

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

При проверке строки мы принимаем весь пользовательский ввод как строку, а затем принимаем или отклоняем эту строку в зависимости от того, правильно ли она отформатирована. Например, если мы просим пользователя ввести номер телефона, мы можем проверить, что вводимые данные содержат десять цифр. В большинстве языков (особенно в скриптовых языках, таких как Perl и PHP) это делается с помощью регулярных выражений. Стандартная библиотека C++ также имеет библиотеку регулярных выражений. Регулярные выражения медленны по сравнению с проверкой строк вручную, и их следует использовать только в том случае, если производительность (время компиляции и время выполнения) не вызывает беспокойства, или ручная проверка слишком обременительна.

При числовой проверке мы обычно заботимся о том, чтобы число, вводимое пользователем, находилось в определенном диапазоне (например, от 0 до 20). Однако, в отличие от проверки строк, пользователь может вводить вещи, которые вообще не являются цифрами, и нам также необходимо обрабатывать эти случаи.

Чтобы помочь нам, C++ предоставляет ряд полезных функций, которые мы можем использовать для определения того, являются ли конкретные символы цифрами или буквами. В заголовке cctype находятся следующие функции:

Функция Назначение
std::isalnum(int) Возвращает ненулевое значение, если параметр представляет собой букву или цифру.
std::isalpha(int) Возвращает ненулевое значение, если параметр представляет собой букву.
std::iscntrl(int) Возвращает ненулевое значение, если параметр является управляющим символом.
std::isdigit(int) Возвращает ненулевое значение, если параметр является цифрой.
std::isgraph(int) Возвращает ненулевое значение, если параметр является печатным символом, который не является пробелом.
std::isprint(int) Возвращает ненулевое значение, если параметр является печатным символом (включая пробелы).
std::ispunct(int) Возвращает ненулевое значение, если параметр не является ни буквенно-цифровым, ни пробельным символом.
std::isspace(int) Возвращает ненулевое значение, если параметр – пробельный символ.
std::isxdigit(int) Возвращает ненулевое значение, если параметр является шестнадцатеричной цифрой (0-9, a-f, A-F).

Проверка строки

Примечание автора


С этого момента мы будем использовать функции, которые (пока) не описаны в этой серии статей. Если вы хорошо разбираетесь в C++, возможно, вы сможете понять, что делают эти функции, исходя из их названий и того, как они используются. Советуем посмотреть эти новые функции и типы в справочнике, чтобы лучше понять, что они делают, и для чего еще их можно использовать.

Давайте рассмотрим простой случай проверки строки, попросив пользователя ввести свое имя. Нашим критерием проверки будет то, что пользователь вводит только буквенные символы или пробелы. Если встретится что-то еще, ввод будет отклонен.

Когда дело доходит до входных данных переменной длины, лучший способ проверить строку (помимо использования библиотеки регулярных выражений) – это пройти по всем символам строки и убедиться, что они соответствует критериям проверки. Это именно то, что мы собираемся здесь сделать, или, лучше сказать, то, что std::all_of сделает для нас.

#include <algorithm> // std::all_of
#include <cctype>    // std::isalpha, std::isspace
#include <iostream>
#include <string>
#include <string_view>

bool isValidName(std::string_view name)
{
  return std::ranges::all_of(name, [](char ch) {
    return (std::isalpha(ch) || std::isspace(ch));
  });

  // До C++20, без диапазонов ranges
  // return std::all_of(name.begin(), name.end(), [](char ch) {
  //    return (std::isalpha(ch) || std::isspace(ch));
  // });
}

int main()
{
  std::string name{};

  do
  {
    std::cout << "Enter your name: ";
    std::getline(std::cin, name); // получаем всю строку, включая пробелы
  } while (!isValidName(name));

  std::cout << "Hello " << name << "!n";
}

Обратите внимание, что этот код не идеален: пользователь мог сказать, что его имя «asf w jweo s di we ao«, или какая-то другая тарабарщина, или, что еще хуже, просто несколько пробелов. Мы могли бы решить эту проблему, уточнив наши критерии проверки, чтобы принимать только строки, содержащие хотя бы один символ и не более одного пробела.

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

  • # будет соответствовать любой цифре в данных, введенных пользователем;
  • @ будет соответствовать любому буквенному символу в пользовательском вводе;
  • _ будет соответствовать любому пробельному символу;
  • ? будет соответствовать чему угодно;
  • в противном случае символы в данных, введенных пользователем, и в шаблоне должны точно совпадать.

Итак, если мы спрашиваем функцию, соответствует ли строка шаблону «(###) ###-####«, это означает, что мы ожидаем, что пользователь введет символ ‘(‘, три цифры, символ ‘)‘, пробел, три числа, дефис и еще четыре числа. Если что-либо из этого не совпадает, ввод будет отклонен.

Вот код:

#include <algorithm> // std::equal
#include <cctype>    // std::isdigit, std::isspace, std::isalpha
#include <iostream>
#include <map>
#include <string>
#include <string_view>

bool inputMatches(std::string_view input, std::string_view pattern)
{
  if (input.length() != pattern.length())
  {
    return false;
  }

  // Мы должны использовать указатель на функцию в стиле C,
  // потому что у std::isdigit и других есть перегрузки,
  // и иначе вызовы будут неоднозначными.
  static const std::map<char, int (*)(int)> validators{
    { '#', &std::isdigit },
    { '_', &std::isspace },
    { '@', &std::isalpha },
    { '?', [](int) { return 1; } }
  };

  // До C++20 используйте следующее
  // return std::equal(input.begin(), input.end(), pattern.begin(), [](char ch, char mask) -> bool {
  // ...

  return std::ranges::equal(input, pattern, [](char ch, char mask) -> bool {
    if (auto found{ validators.find(mask) }; found != validators.end())
    {
      // Текущий элемент шаблона был найден в validators. 
      // Вызов соответствующей функции.
      return (*found->second)(ch);
    }
    else
    {
      // Текущий элемент шаблона не найден в validators.
      // Символы должны точно совпадать.
      return (ch == mask);
    }
  });
}

int main()
{
  std::string phoneNumber{};

  do
  {
    std::cout << "Enter a phone number (###) ###-####: ";
    std::getline(std::cin, phoneNumber);
  } while (!inputMatches(phoneNumber, "(###) ###-####"));

  std::cout << "You entered: " << phoneNumber << 'n';
}

Используя эту функцию, мы можем заставить пользователя вводить данные, точно соответствующие нашему конкретному формату. Однако эта функция всё еще имеет несколько ограничений: Если #, @, _ и ? являются допустимыми символами в пользовательском вводе, эта функция не будет работать, потому что этим символам присвоено особое значение. Кроме того, в отличие от регулярных выражений, здесь нет шаблонного символа, означающего, что «можно ввести переменное количество символов». Таким образом, такой шаблон нельзя использовать для обеспечения того, чтобы пользователь вводил два слова, разделенных пробелом, поскольку он не может обработать тот факт, что слова имеют переменную длину. Для таких задач, как правило, более уместен нешаблонный подход.

Проверка чисел

При работе с числовым вводом очевидный способ обработки – использовать оператор извлечения для извлечения входных данных в переменную числового типа. Проверяя бит отказа (failbit), мы можем определить, ввел ли пользователь число или нет.

Давайте попробуем такой подход:

#include <iostream>
#include <limits>

int main()
{
    int age{};

    while (true)
    {
        std::cout << "Enter your age: ";
        std::cin >> age;

        if (std::cin.fail()) // извлечение не производилось
        {
            // сбрасываем биты состояния обратно в goodbit,
            // чтобы мы могли использовать ignore()
            std::cin.clear();
            // очищаем недопустимый ввод из потока
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); 
            // попробовать снова
            continue; 
        }

        if (age <= 0) // убедиться, что значение возраста положительное
            continue;

        break;
    }

    std::cout << "You entered: " << age << 'n';
}

Если пользователь вводит число, cin.fail() вернет false, и мы перейдем к инструкции break, выходя из цикла. Если пользователь вводит данные, начинающиеся с буквы, cin.fail() вернет true, и мы перейдем к условному выражению.

Однако есть еще один случай, который мы не проверили, и это когда пользователь вводит строку, которая начинается с цифр, но затем содержит буквы (например, «34abcd56«). В этом случае начальные числа (34) будут извлечены в переменную age, а остаток строки («abcd56«) останется во входном потоке, и бит отказа НЕ будет установлен. Это вызывает две потенциальные проблемы:

  1. если вы хотите, чтобы это был допустимый ввод, теперь в вашем потоке есть мусор;
  2. если вы не хотите, чтобы это был допустимый ввод, он не отклоняется (и в вашем потоке есть мусор);

Решим первую проблему. Это просто:

#include <iostream>
#include <limits>

int main()
{
    int age{};

    while (true)
    {
        std::cout << "Enter your age: ";
        std::cin >> age;

        if (std::cin.fail()) // извлечение не производилось
        {
            // сбрасываем биты состояния обратно в goodbit,
            // чтобы мы могли использовать ignore()
            std::cin.clear(); 
            // очищаем недопустимый ввод из потока
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); 
            // попробовать снова
            continue; 
        }

        // очищаем любой дополнительный ввод из потока
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); 

        if (age <= 0) // убедиться, что значение возраста положительное
            continue;

      break;
    }

    std::cout << "You entered: " << age << 'n';
}

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

#include <iostream>
#include <limits>

int main()
{
    int age{};

    while (true)
    {
        std::cout << "Enter your age: ";
        std::cin >> age;

        if (std::cin.fail()) // извлечение не производилось
        {
            // сбрасываем биты состояния обратно в goodbit,
            // чтобы мы могли использовать ignore()
            std::cin.clear();
            // очищаем недопустимый ввод из потока
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); 
            // попробовать снова
            continue; 
        }
 
        // очищаем любой дополнительный ввод из потока
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); 
        if (std::cin.gcount() > 1) // если мы удалили более одного дополнительного символа
        {
            continue; // будем считать этот ввод недопустимым
        }

        if (age <= 0) // убедиться, что значение возраста положительное
        {
            continue;
        }

        break;
    }

    std::cout << "You entered: " << age << 'n';
}

Проверка чисел в виде строки

В приведенном выше примере потребовалось довольно много работы, чтобы просто получить простое значение! Другой способ обработки числовых входных данных – прочитать их как строку, а затем попытаться преобразовать в переменную числового типа. Следующая программа использует этот метод:

#include <charconv> // std::from_chars
#include <iostream>
#include <optional>
#include <string>
#include <string_view>

std::optional<int> extractAge(std::string_view age)
{
  int result{};
  auto end{ age.data() + age.length() };

  // Пытаемся извлечь int из строки age
  if (std::from_chars(age.data(), end, result).ptr != end)
  {
    return {};
  }

  if (result <= 0) // убедиться, что значение возраста положительное
  {
    return {};
  }

  return result;
}

int main()
{
  int age{};

  while (true)
  {
    std::cout << "Enter your age: ";
    std::string strAge{};
    std::cin >> strAge;

    if (auto extracted{ extractAge(strAge) })
    {
      age = *extracted;
      break;
    }
  }

  std::cout << "You entered: " << age << 'n';
}

Будет ли этот подход более или менее трудоемким, чем извлечение чисел напрямую, зависит от ваших параметров проверки и ограничений.

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

Теги

C++ / CppiostreamLearnCppstd::cinstd::iosSTL / Standard Template Library / Стандартная библиотека шаблоновВалидацияВвод/выводДля начинающихОбучениеПрограммирование

Понравилась статья? Поделить с друзьями:
  • Ios ошибка учетной записи yandex
  • Ios network error
  • Ios in app purchase error
  • Ios error 4pda
  • Ionic zip badcrcexception crc error the file being extracted appears to be corrupted