НА ГЛАВНУЮ
Меню сайта
Категория
Ghost++ [1]
С++ [55]
Развлечение
ON - LINE
Опрос
Оцените мой сайт
Всего ответов: 199
Оbserver Ward

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Друзья сайта
Заведи себе Бота
Hаша кнопка
Для обмена банерами , наша кнопка для размещения у вас на сайте

Клансайт USSR


Главная » Статьи » Программирование » С++

20. Библиотека iostream (3)
20.7. Состояния потока

Пользователей библиотеки iostream, разумеется, интересует, находится ли поток в ошибочном состоянии. Например, если мы пишем

int ival;
cin >>ival;

и вводим слово "Borges", то cin переводится в состояние ошибки после неудачной попытки присвоить строковый литерал целому числу. Если бы мы ввели число 1024, то чтение прошло бы успешно и поток остался бы в нормальном состоянии.

Чтобы выяснить, в каком состоянии находится поток, достаточно проверить его значение на истину:

if ( !cin )
   // операция чтения не прошла или встретился конец файла

Для чтения заранее неизвестного количества элементов мы обычно пишем цикл while:

while ( cin >> word )
      // операция чтения завершилась успешно ...

Условие в цикле while будет равно false, если достигнут конец файла или произошла ошибка при чтении. В большинстве случаев такой проверки потокового объекта достаточно. Однако при реализации оператора ввода для класса WordCount из раздела 20.5 нам понадобился более точный анализ состояния.

У любого потока есть набор флагов, с помощью которых можно следить за состоянием потока. Имеются четыре предикатные функции-члена:

    * eof() возвращает true, если достигнут конец файла:

      if ( inOut.eof() )
         // отлично: все прочитано ...

    * bad() возвращает true при попытке выполнения некорректной операции, например при установке позиции за концом файла. Обычно это свидетельствует о том, что поток находится в состоянии ошибки;
    * fail() возвращает true, если операция завершилась неудачно, например не удалось открыть файл или передан некорректный формат ввода:

      ifstream iFile( filename, ios_base::in );
      if ( iFile.fail() )    // не удалось открыть
         error_message( ... );

    * good() возвращает true, если все вышеперечисленные условия ложны:

if ( inOut.good() )

Существует два способа явно изменить состояние потока iostream. С помощью функции-члена clear() ему явно присваивается указанное значение. Функция setstate() не сбрасывает состояние, а устанавливает один из флагов, не меняя значения остальных. Например, в коде оператора ввода для класса WordCount при обнаружении неверного формата мы используем setstate() для установки флага fail в состоянии объекта istream:

if ((ch = is.get()) != '<' )
{
   is.setstate( ios_base::failbit );
   return is;
}

Имеются следующие значения флагов состояния:

ios_base::badbit
ios_base::eofbit
ios_base::failbit
ios_base::goodbit

Для установки сразу нескольких флагов используется побитовый оператор ИЛИ:

is.setstate( ios_base::badbit | ios_base::failbit );

При тестировании оператора ввода в классе WordCount (см. раздел 20.5) мы писали:

if ( !cin ) {
   cerr << "Ошибка ввода WordCount" << endl;
   return -1;
}

Возможно, вместо этого мы предпочли бы продолжить выполнение программы, предупредив пользователя об ошибке и попросив повторить ввод. Но перед чтением нового значения из потока cin необходимо перевести его в нормальное состояние. Это можно сделать с помощью функции-члена clear():

cin.clear(); // сброс ошибок

В более общем случае clear() используется для сброса текущего состояния и установки одного или нескольких флагов нового. Например:

cin.clear( ios_base::goodbit );

восстанавливает нормальное состояние потока. (Оба вызова эквивалентны, поскольку goodbit является для clear() аргументом по умолчанию.)

Функция-член rdstate() позволяет получить текущее состояние объекта:

ios_base::iostate old_state = cin.rdstate();

cin.clear();
process_input();

// перевести поток cin в прежнее состояние
cin.clear( old_state );

Упражнение 20.15

Измените один (или оба) оператор ввода для класса Date из упражнения 20.7 и/или класса CheckoutRecord из упражнения 20.8 (см. раздел 20.4) так, чтобы они устанавливали состояние объекта istream. Модифицируйте программы, которыми вы пользовались для тестирования этих операторов, для проверки явно установленного состояния, вывода его на печать и сброса в нормальное. Протестируйте программы, подав на вход правильные и неправильные данные.
20.8. Строковые потоки

Библиотека iostream поддерживает операции над строковыми объектами в памяти. Класс ostringstream вставляет символы в строку, istringstream читает символы из строкового объекта, а stringstream может использоваться как для чтения, так и для записи. Чтобы работать со строковым потоком, в программу необходимо включить заголовочный файл

#include <sstream>

Например, следующая функция читает весь файл alice_emma в объект buf класса ostringstream. Размер buf увеличивается по мере необходимости, чтобы вместить все символы: #include <string> #include <fstream> #include <sstream> string read_file_into_string() { ifstream ifile( "alice_emma" ); ostringstream buf; char ch; while ( buf && ifile.get( ch )) buf.put( ch ); return buf.str(); }

Функция-член str() возвращает строку – объект класса string, ассоциированный со строковым потоком ostringstream. Этой строкой можно манипулировать так же, как и "обычным” объектом класса string. Например, в следующей программе text почленно инициализируется строкой, ассоциированной с buf:

int main()
{
    string text = read_file_into_string();

    // запомнить позиции каждого символа новой строки
    vector< string::size_type > lines_of_text;
    string::size_type pos = 0;

    while ( pos != string::npos )
    {
        pos = text.find( '\n' pos );
        lines_of_text.push_back( pos );
    }

    // ...
}

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

#include <iostream>
#include <sstream>

int main()
{
    int    ival  = 1024;     int   *pival = &ival;
    double  dval  = 3.14159; double *pdval = &dval;

    ostringstream format_message;

    // преобразование значений в строковое представление
    format_message << "ival: " << ival
                   <<" адрес ival: " << pival << 'n'
                   << "dval: " << dval
                   << " адрес dval: " << pdval << endl;

    string msg = format_message.str();
    cout << " размер строки сообщения: " << msg.size()
         <<" сообщение:"<<msg <<endl;
}

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

string
format( string msg, int expected, int received )
{
   ostringstream message;
   message << msg << " ожидалось: " << expected
           << " принято: " << received << "\n";
   return message.str();
}

string format( string msg, vector<int> *values );
// ... и так далее

Приложение может сохранить такие строки для последующего отображения и даже рассортировать их по серьезности. Обобщить эту идею помогают классы Notify (извещение), Log (протокол) и Error (ошибка).

Поток istringstream читает из объекта класса string, с помощью которого был сконструирован. В частности, он применяется для преобразования строкового представления числа в его арифметическое значение:

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    int    ival = 1024;    int *pival = &ival;
    double dval = 3.14159; double *pdval = &dval;

    // создает строку, в которой значения разделены пробелами
    ostringstream format_string;

    format_string << ival << " " << pival << " "
                   << dval <<" " << pdval << endl;

    // извлекает сохраненные значения в коде ASCII
    // и помещает их в четыре разных объекта
    istringstream input_istring( format_string.str() );

    input_istring >> ival >>pival
                   >> dval >> pdval;
}

Упражнение 20.16

В языке Си форматирование выходного сообщения производится с помощью функций семейства printf(). Например, следующий фрагмент

int    ival = 1024;
double dval = 3.14159;
char   cval = 'a';
char  *sval = "the end";

printf( "ival: %d\tdval% %g\tcval: %c\tsval: %s",
         ival, dval, cval, sval );
печатает:

ival: 1024   dval: 3.14159  cval: a   sval: the end

Первым аргументом printf() является форматная строка. Каждый символ % показывает, что вместо него должно быть подставлено значение аргумента, а следующий за ним символ определяет тип этого аргумента. Вот некоторые из поддерживаемых типов (полное описание см. в [KERNIGHAN88]):

%d        целое число
%g        число с плавающей точкой
%c        char
%s        C-строка

Дополнительные аргументы printf() на позиционной основе сопоставляются со спецификаторами формата, начинающимися со знака %. Все остальные символы в форматной строке рассматриваются как литералы и выводятся буквально.

Основные недостатки семейства функций printf() таковы: во-первых, форматная строка не обобщается на определенные пользователем типы, и, во-вторых, если типы или число аргументов не соответствуют форматной строке, компилятор не заметит ошибки, а вывод будет отформатирован неверно. Однако у функций printf() есть и достоинство – компактность записи.

   1. Получите так же отформатированный результат с помощью объекта класса ostringstream.
   2. Сформулируйте достоинства и недостатки обоих подходов.

20.9. Состояние формата

Каждый объект класса из библиотеки iostream поддерживает состояние формата, которое управляет выполнением операций форматирования, например основание системы счисления для целых значений или точность для значений с плавающей точкой. Для модификации состояния формата объекта в распоряжении программиста имеется предопределенный набор манипуляторов.1 Манипулятор применяется к потоковому объекту так же, как к данным. Однако вместо чтения или записи данных манипулятор модифицирует внутреннее состояние потока. Например, по умолчанию объект типа bool, имеющий значение true (а также литеральная константа true), выводится как целая ‘1’:

#include <iostream.h>

int main()
{
    bool illustrate = true;

    cout << "объект illustrate типа bool установлен в true: "
         << illustrate << '\n';
}

Чтобы поток cout выводил переменную illustrate в виде слова true, мы применяем манипулятор boolalpha:

#include <iostream.h>

int main()
{
    bool illustrate = true;
    cout << "объект illustrate типа bool установлен в true: ";

    // изменяет состояние cout так, что булевские значения
    // печатаются в виде строк true и false
    cout << boolalpha;
    cout << illustrate <<'\n';
}

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

#include <iostream.h>

int main()
{
    bool illustrate = true;
    cout << "объект illustrate типа bool: "
         << illustrate
         << "\nс использованием boolalpha: "
         << boolalpha << illustrate <<'\n';

    // ...
}

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

Чтобы отменить сделанную модификацию потока cout, необходимо использовать манипулятор noboolalpha:

cout << boolalpha   // устанавливает внутреннее состояние cout
     << illustrate
     << noboolalpha  // сбрасывает внутреннее состояние cout

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

По умолчанию значения арифметических типов читаются и записываются в десятичной системе счисления. Программист может изменить ее на восьмеричную или шестнадцатеричную, а затем вернуться к десятичной (это распространяется только на целые типы, но не на типы с плавающей точкой), пользуясь манипуляторами hex, oct и dec:

#include <iostream>
int main()
{
    int ival = 16;
    double dval = 16.0;
    
    cout << "ival: " << ival
          << " установлен oct: " << oct << ival << "\n";

    cout << "dval: " << dval
          << " установлен hex: " << hex << dval << "\n";

    cout << "ival: " << ival
          << " установлен dec: " << dec << ival << "\n";
}

Эта программа печатает следующее:

ival: 16 установлен oct: 20
dval: 16 установлен hex: 16
ival: 10 установлен dec: 16

Но, глядя на значение, мы не можем понять, в какой системе счисления оно записано. Например, 20 – это действительно 20 или восьмеричное представление 16? Манипулятор showbase выводит основание системы счисления вместе со значением с помощью следующих соглашений:

    * 0x в начале обозначает шестнадцатеричную систему (если мы хотим, чтобы вместо строчной буквы 'x' печаталась заглавная, то можем применить манипулятор uppercase, а для отмены – манипулятор nouppercase);
    * 0 в начале обозначает восьмеричную систему;
    * отсутствие того и другого обозначает десятичную систему.

Вот та же программа, но и с использованием showbase:

#include <iostream>
int main()
{
    int ival = 16;
    double dval = 16.0;

     cout << showbase;
    
    cout << "ival: " << ival
          << " установлен oct: " << oct << ival << "\n";

    cout << "dval: " <<dval
          << " установлен hex: " << hex << dval << "\n";

    cout << "ival: " << ival
          << " установлен dec: " << dec << ival << "\n";

     cout << noshowbase;
}

Результат:

ival: 16 установлен oct: 020
dval: 16 установлен hex: 16
ival: 0x10 установлен dec: 16

Манипулятор noshowbase восстанавливает состояние cout, при котором основание системы счисления не выводится.

По умолчанию значения с плавающей точкой выводятся с точностью 6. Эту величину можно модифицировать с помощью функции-члена precision(int) или манипулятора setprecision(); для использования последнего необходимо включить заголовочный файл iomanip. precision() возвращает текущее значение точности. Например:

#include <iostream>
#include <iomanip>
#include <math.h>

int main()
{
    cout << "Точность: "
          << cout.precision() << endl
          << sqrt(2.0) << endl;

     cout.precision(12);
    cout << "\nТочность: "
         << cout.precision() << endl
          << sqrt(2.0) << endl;

    cout << "\nТочность:"  << setprecision(3)
         << cout.precision() << endl
         << sqrt(2.0) << endl;

    return 0;
}

После компиляции и запуска программа печатает следующее:

Точность: 6

1.41421

Точность: 12

1.41421356237

Точность: 3

1.41

Манипуляторы, принимающие аргумент, такие, как setprecision() и setw(), требуют включения заголовочного файла iomanip:

#include <iomanip>

Кроме описанных аспектов, setprecision() имеет еще два: на целые значения он не оказывает никакого влияния; значения с плавающей точкой округляются, а не обрезаются. Таким образом, при точности 4 значение 3.14159 печатается как 3.142, а при точности 3 – как 3.14.

По умолчанию десятичная точка не печатается, если дробная часть значения равна 0. Например:

cout << 10.00

выводит

10

Чтобы точка выводилась, воспользуйтесь манипулятором showpoint:

cout << showpoint
     << 10.0
     << noshowpoint << '\n';

Манипулятор noshowpoint восстанавливает поведение по умолчанию.

По умолчанию значения с плавающей точкой выводятся в нотации с фиксированной точкой. Для перехода на научную нотацию используется идентификатор scientific, а для возврата к прежней нотации – модификатор fixed:

cout << "научная: " << scientific
     << 10.0
     <<"с фиксированной точкой: " << fixed
     << 10.0 << '\n';

В результате печатается:

научная: 1.0e+01

с фиксированной точкой: 10

Если бы мы захотели вместо буквы 'e' выводить 'E', то следовало бы употребить манипулятор uppercase, а для возврата к 'e' – nouppercase. (Манипулятор uppercase не приводит к переводу букв в верхний регистр при печати.)

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

a bc
d

то цикл

char ch;
while ( cin >> ch )
     // ...

читает все буквы от 'a' до 'd' за четыре итерации, а пробельные разделители оператором ввода игнорируются. Манипулятор noskipws отменяет такой пропуск пробельных символов:

char ch;
cin >> noskipws;
while ( cin >> ch )
     // ...
cin >> skipws;

Теперь цикл while будет выполняться семь раз. Чтобы восстановить поведение по умолчанию, к потоку cin применяется манипулятор skipws.

Когда мы пишем:

cout << "пожалуйста, введите значение: ";

то в буфере потока cout сохраняется литеральная строка. Есть ряд условий, при которых буфер сбрасывается (т.е. опустошается), – в нашем случае в стандартный вывод:

    * буфер может заполниться. Тогда перед чтением следующего значения его необходимо сбросить;
    * буфер можно сбросить явно с помощью любого из манипуляторов flush, ends или endl:

// сбрасывает буфер
cout << "hi!" << flush;
// вставляет нулевой символ, затем сбрасывает буфер
char ch[2]; ch[0] = 'a'; ch[1] = 'b';
cout << ch << ends;
// вставляет символ новой строки, затем сбрасывает буфер
cout << "hi!" << endl;

    * при установлении внутренней переменной состояния потока unitbuf буфер сбрасывается после каждой операции вывода;
    * объект ostream может быть связан (tied) с объектом istream. Тогда буфер ostream сбрасывается каждый раз, когда istream читает из входного потока. cout всегда связан с cin:

cin.tie( &cout );

Инструкция

cin >>ival;

приводит к сбросу буфера cout.

В любой момент времени объект ostream разрешено связывать только с одним объектом istream. Чтобы разорвать существующую связь, мы передаем функции-члену tie() значение 0:

istream is;
ostream new_os;

// ...

// tie() возвращает существующую связь
ostream *old_tie = is.tie();

is.tie( 0 );   // разорвать существующую связь
is.tie( &new_os );  // установить новую связь

// ...

is.tie( 0 );   // разорвать существующую связь
is.tie( old_tie );  // восстановить прежнюю связь

Мы можем управлять шириной поля, отведенного для печати числового или строкового значения, с помощью манипулятора setw(). Например, программа

#include <iostream>
#include <iomanip>

int main()
{
    int ival = 16;
    double dval = 3.14159;
    
    cout << "ival: " << setw(12) <<ival << '\n'
         << "dval: " << setw(12) << dval << '\n';
}

печатает:

ival:           16
dval:      3.14159

Второй модификатор setw() необходим потому, что, в отличие от других манипуляторов, setw() не изменяет состояние формата объекта ostream.

Чтобы выровнять значение по левой границе, мы применяем манипулятор left (соответственно манипулятор right восстанавливает выравнивание по правой границе). Если мы хотим получить такой результат:

    16
-    3

то пользуемся манипулятором internal, который выравнивает знак по левой границе, а значение – по правой, заполняя пустое пространство пробелами. Если же нужен другой символ, то можно применить манипулятор setfill(). Так

cout << setw(6) << setfill('%') << 100 << endl;

печатает:

%%%100

В табл. 20.1 приведен полный перечень предопределенных манипуляторов.

Таблица 20.1. Манипуляторы
Манипулятор    Назначение

boolalpha

    

Представлять true и false в виде строк

*noboolalpha

    

Представлять true и false как 1 и 0

Showbase

    

Печатать префикс, обозначающий систему счисления

*noshowbase

    

Не печатать префикс система: счисления

showpoint

    

Всегда печатать десятичную точку

*noshowpoint

    

Печатать десятичную точку только в том случае, если дробная часть ненулевая

showpos

    

Печатать + для неотрицательных чисел

*noshowpos

    

Не печатать + для неотрицательных чисел

*skipws

    

Пропускать пробельные символы в операторах ввода

noskipws

    

Не пропускать пробельные символы в операторах ввода

uppercase

    

Печатать 0X при выводе в шестнадцатеричной системе счисления; E - при выводе в научной нотации

*nouppercase

    

Печатать 0x при выводе в шестнадцатеричной системе счисления; e - при выводе в научной нотации

*dec

    

Печатать в десятичной системе

hex

    

Печатать в шестнадцатеричной системе

oct

    

Печатать в восьмеричной системе

left

    

Добавлять символ заполнения справа от значения

right

    

Добавлять символ заполнения слева от значения

internal

    

Добавлять символ заполнения между знаком и значением

*fixed

    

Отображать число с плавающей точкой в десятичной нотации

scientific

    

Отображать число с плавающей точкой в научной нотации

flush

    

Сбросить буфер ostream

ends

    

Вставить нулевой символ, затем сбросить буфер ostream

endl

    

Вставить символ новой строки, затем сбросить буфер ostream

ws

    

Пропускать пробельные символы

// для этих манипуляторов требуется #include <ionamiip>

setfill( ch )

    

Заполнять пустое место символом ch

Setprecision( n )

    

Установить точность вывода числа с плавающей точкой равной n

setw( w )

    

Установить ширину ноля ввода или вывода равной w

setbase( b )

    

Выводить целые числа по основанию b

* обозначает состояние потока но умолчанию
20.10. Сильно типизированная библиотека

Библиотека iostream сильно типизирована. Например, попытка прочитать из объекта класса ostream или записать в объект класса istream помечается компилятором как

#include <iostream>
#include <fstream>
class Screen;
extern istream& operator >>( istream&, const Screen& );
extern void print( ostream& );

нарушение типизации. Так, если имеется набор объявлений:

ifstream inFile;

то следующие две инструкции приводят к нарушению типизации, обнаруживаемому во время компиляции:


int main()
{
    Screen myScreen;
    
    // ошибка: ожидается ostream&
    print( cin >> myScreen );
    
    // ошибка: ожидается оператор >>
    inFile <<  "ошибка: оператор вода";

    

Средства ввода/вывода включены в состав стандартной библиотеки C++. В главе 20 библиотека iostream описана не полностью, в частности вопрос о создании определенных пользователем манипуляторов и буферных классов остался за рамками введения в язык. Мы сосредоточили внимание лишь на той части библиотеки iostream, которая имеет основополагающее значение для программного ввода/вывода.
Категория: С++ | Добавил: r2d2 (29.09.2011)
Просмотров: 1509 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Born in Ussr
Залогиниться
Турниры

/j clan ussr /j clan cccp