Стандартни потоци в C++

В този урок ще разгледаме основните потоци дефинирани от стандартната библиотека на езика. Това са cin, cout, cerr и clog. Те са достъпни веднага след като добавите следния ред към програмата си:

#iclude <iostream>

Какво означават тези странни имена?

  • cin – Character input; Cтандартен входен поток;
  • cout – Character output; Cтандартен изходен поток;
  • cerr – Character error (output); Стандартен поток за грешки (изходен);
  • clog – Character log (output); Стандартен поток за лог информация (изходен);

Всички те преобразуват това, което им се подава или това което четат от и към ASCII текст. Тоест спокойно можем да кажем, че са от тип char.

Понятие за поток (stream)

C++ библиотеката използва удобна абстракция наречена поток. За да чете и пише данни байт, по байт. Tехнически е така, защото се използва типа char. И от гледна точка на програмата няма значение към какво е свързан потока щом можем да четем и пишем байт по байт.

Потока е обект, в който програмата може да вмъква или да извлича символи. Няма нужда да знаем подробности за мястото с което е свързан потока. Или някоя от неговите вътрешни характеристики. Всичко, което трябва да знаем е, че потоците са източник / дестинация на символи и че тези символи се четат / пишат последователно (един след друг). Това накратко е концепцията за потоците.

Потоците могат да бъдат свързани към различни обекти: Обикновено пишат и четат в терминалния прозорец, където се изпълнява вашата програма. Но могат да бъдат свързани към фаилове или сокет (socket) за мрежова комуникация.

std::cin

Това е стандартния поток за четене от клавиатурата в терминалния прозорец. Използва се с оператора „>>“. Чете дума или число написано от потребителя. Входа е буфериран. Игнорира интервали, табове и ‘\n’ в началото. Чете до следващия срещнат интервал, таб или ‘\n’. Преобразува прочетеното в желания тип данни фундаментални данни.

int a {};
double b {};
std::string c {};

std::cin >> a;
std::cin >> b;
std::cin >> c;

Четене на цял ред:

std::string s;
std::getline(std::cin, s);

std::cout

Стандартен изход към терминала. Изхода е буфериран. Защото се смята, че изходните операции са тежки. И това е вярно донякъде но зависи от ОС. Ако искате бързина не използвайте постоянно endln оператора. А го извикайте след няколко операции. За нов ред добавете ‘\n’. Когато буфера се напълни сам ще изплюе текста към терминала.

std::cerr

Това е небуфериран поток за грешки. Отпечатва се веднага в терминала.

std::clog

Това е стандартният поток за логовaне на съобщения на програмата и диагностична информация. Използва се рядко. Изхода е буфериран.

Обобщение

КласБуфериранеДействие
std::cin✅вход на данни
std::cout✅извеждане на данни
std::cerr❌извеждане на грешки
std::clog✅логове и debug

Грешки в четенето

Изходния поток почти никога не ни дава грешка. Но при четене на входни данни входния поток може да изпадне в грешка. Нека обясним. Имаме:

int x {};
std::cin >> x;

Но, ако потребителя е въвел текст вместо число? Какво става? Входния поток пропада в безкраен цикъл, опитвайки се да прочете нещо и никога не успява. Тогава трябва да изчистим буфера и грешките преди да прочетем нов вход:

std::cin.clear();                // маха fail флага
std::cin.ignore(10'000, '\n');   // изхвърля грешния вход

Флагове за състоянието на потока

Потоците в C++ стандартната библиотека са конфигурирани да не хвърлят изключения по подразбиране. Те работят подобно на входно – изходните операции в езика C. Имат флагове за състоянието си. На C++ се пишат игри и драйвери. Дори части от ядрото на ОС. Помислете какво би станало, ако в програма на C++ се изпълняват няколко паралелни нишки и една от тях хвърли изключение?

Основни флагове за грешка

ФлагЗначение
goodbitНяма грешка
eofbitДостигнат е край на файла
failbitНевалиден вход / логическа грешка
badbitСериозна грешка в потока

Проверка на състоянието

if(std::cin.good())
    std::cout << "Всичко е наред" << std::endl;

if(std::cin.fail())
    std::cout << "Грешка при четене" << std::endl;

if(std::cin.eof()) 
    std::cout << "Край на файла" << std::endl;

if(std::cin.bad())
    std::cout << "Критична грешка" << std::endl;

Ето пример, който проверява състоянието на входния поток:

#include <iostream>

int ReadInt() {
    int x;
    while( not (std::cin >> x)) {
        std::cout << "Невалидно число!" << std::endl;
        std::cin.clear();
        std::cin.ignore(10'000, '\n');
    }
    return x;
}

int main() {
    int x = {};
    x = ReadInt();
    std::cout << "x = " << x << std::endl;
}

Член – функцията rdstate()

Този метод връща състоянието на потока като цяло число. И може да съдържа повече от една грешка. Всяка възникнала грешка в потока задава отделен бит в числото. И тогава резултата не е 0. Когато метода върне 0, тогава потока е в добро, работещо състояние.

Като използвате долния код въведете цяло число и после текст. Какъв ще е изхода?

#include <iostream>

int main() {
    int x = {};
    std::cin >> x;
    std::cout << "rdstate() = " << std::cin.rdstate() << std::endl;
}

Поведение на потоците