Форматирование. Флаги форматирования




Пока << применялась только для неформатированного вывода, и в реальных программах она именно для этого главным образом и применяется. В то же время << легко справляется с тремя стандартными типами данных: char, int(long), float(double). В результате переопределения операция помещения в поток определяет тип посланных данных и сама выбирает подходящий формат. То же самое происходит и с операцией извлечения из потока >>, которая вводит данные. Сравним пример на Си с его эквивалентом на Си++:

scanf("%d%f%c", &int_data, &float_data, &char_data); /* C */cin >> int_data >> float_data >> char_data; // C++

В Си++ не нужно заботиться о том, чтобы ставить перед переменными знак адреса &. Операция извлечения из потока сама вычисляет адреса переменных, определяет их типы и формат ввода.

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

char* oct(long, int=0); // восьмеричное представлениеchar* dec(long, int=0); // десятичное представлениеchar* hex(long, int=0); // шестнадцатеричное представлениеchar* chr(int, int=0); // символchar* str(char*, int=0); // строка

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

cout << "dec(" << x << ") = oct(" << oct(x,6) << ") = hex(" << hex(x,4) << ")";

Если x==15, то в результате получится:

dec(15) = oct(17) = hex(f)

Можно также использовать строку в общем формате:

char* form(char* format,...);

cout<<form() эквивалентно применению стандартной функции вывода языка Cи printf(). Функция form() возвращает строку, получаемую в результате преобразования и форматирования ее параметров, которые стоят после первого управляющего параметра - строки формата format. Строка формата состоит из объектов двух типов: обычных символов, которые просто копируются в поток вывода, и спецификаций преобразования, каждая из которых влечет преобразование и печать следующего из параметров. Каждая спецификация преобразования начинается с символа %. Например:

cout<<form("there were %d members present",no_of_members);

Здесь %d указывает, что no_of_members должно рассматриваться как int и печататься в виде соответствующей последовательности десятичных цифр. Если no_of_members==127, вывод будет такой:

there were 127 members present

Множество спецификаций преобразования довольно велико и обеспечивает высокую степень гибкости. После % может стоять [-][[0]ddd][.dd][h|l]c:

  • необязательный знак минус, который задает выравнивание преобразованного значения влево в указанном поле;
  • ddd необязательная строка цифр, задающая ширину поля. Если преобразованное значение имеет меньше цифр, чем ширина поля, оно будет дополняться пробелами слева (или справа, если был задан индикатор выравнивания влево) до заполнения всей ширины поля; если ширина поля начинается с нуля, то вместо дополнения пробелами будет делаться дополнение нулями;
  • необязательная точка, для отделения ширины поля от следующей строки цифр;
  • dd необязательная строка цифр, специфицирующая точность, которая задает число цифр после десятичной точки для преобразований e и f или печатаемых символов для строки;
  • * в ширине поля или точности вместо строки цифр может стоять *. В этом случае ширина поля и точность задается целым параметром;
  • h необязательный символ h; указывает на то, что идущие за ним d, o, x или u соответствуют параметру короткое целое;
  • l необязательный символ l; указывает на то, что идущие за ним d, o, x или u соответствуют параметру длинное целое;
  • % указывает, что должен быть напечатан символ %, никакие параметры при этом не затрагиваются;
  • c символ, указывающий, какой тип преобразования должен применяться. Символы преобразования и их значения таковы:
    • d целый параметр преобразуется в десятичную запись;
    • o целый параметр преобразуется в восьмеричную запись;
    • x целый параметр преобразуется в шестнадцатиричную запись;
    • f параметр float или double преобразуется в десятичную запись вида [-]ddd.ddd, где число, задаваемое цифрами d после десятичной точки, эквивалентно спецификации точности для параметра. Если точность опущена, дается шесть цифр;
    • если точность явно задана как 0, то не печатается десятичная точка и не печатается ни одной цифры;
    • e параметр float или double преобразуется в десятичную запись вида [-]d.ddde+dd, где перед десятичной точкой стоит одна цифра, а число, задаваемое цифрами после десятичной точки, эквивалентно спецификации точности для параметра;
    • когда точность опущена, выдается шесть цифр;
    • g параметр float или double печатается в том из видов d,f или e, который обеспечивает полную точность при минимальной затрате места;
    • c печатается символьный параметр, пустые символы игнорируются;
    • s параметр воспринимается как строка (указатель на символ), и печатаются символы из строки до пустого символа или до тех пор, пока не будет достигнуто число символов, указанное спецификацией точности; но если точность равна нулю, печатаются все символы до пустого;
    • u беззнаковый целый параметр преобразуется в десятичную запись.

Несуществующая или недостаточная ширина поля никогда не приводит к обрезанию поля; дополнение поля записи имеет место только в том случае, если указанная ширина поля превышает фактическую ширину.

Вот более сложный пример:

char* src_file_name = "С++/main.c"; // __FILE__int line = 13; // __LINE__char* line_format = "\n#line %d \"%s\"\n";//...cout << "int a;\n";cout << form(line_format,line,src_file_name);cout << "int b;\n";

который печатает

int a; #line 13 "С++/main.c"int b;

Применение form() небезопасно в смысле того, что не выполняется проверка типа. Вот, например, хорошо хорошо известный способ получить непредсказуемый вывод и/или дамп (core dump):

char x;//...cout<<form("bad input char: %s",x);

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

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

class complex { float re,im; public: //... char* string(char* format) { return form(format,re,im); } }; //... cout << z.string("(%.3f,%.3f)");

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

Манипуляторы

Манипуляторы - функции потока, которые можно включать в операции помещения и извлечения в потоки (<<, >>). Имеются следующие манипуляторы:

endl // Помещение в выходной поток символа конца строки '\n' и // вызов функции flushends // Помещение в выходной поток символа '\0'flush // Вызов функции вывода буферизованных данных в выходной потокdec // Установка основания 10 системы счисленияhex // Установка основания 16 системы счисленияoct // Установка основания 8 системы счисленияws // Установка игнорирования при вводе пробеловsetbase(int) // Установка основания системы счисления (0 - 10 - по умолчанию, // также 8,10,16)resetiosflasg(long) // Сброс флагов форматирования по маскеsetiosflags(long) // Установка флагов форматирования по маскеsetfill(int) // Установка заполняющего символаsetprecision(int) // Установка точности вывода вещественных чиселsetw(int) // Установка ширины поля ввода-вывода

Пример вызова манипулятора:

cout << 15 << hex << 15 << setbase(8) << 15;

Ошибки потоков

Каждый поток (istream или ostream) имеет ассоциированное с ним состояние, и обработка ошибок и нестандартных условий осуществляется с помощью соответствующей установки и проверки этого состояния.

Поток может находиться в одном из следующих состояний:

enum stream_state { _good, _eof, _fail, _bad };

Если состояние _good или _eof, значит последняя операция ввода прошла успешно. Если состояние _good, то следующая операция ввода может пройти успешно, в противном случае она закончится неудачей. Другими словами, применение операции ввода к потоку, который не находится в состоянии _good, является пустой операцией. Если делается попытка читать в переменную v, и операция оканчивается неудачей, значение v должно остаться неизменным (оно будет неизменным, если v имеет один из тех типов, которые обрабатываются функциями членами istream или ostream). Отличие между состояниями _fail и _bad очень незначительно и представляет интерес только для разработчиков операций ввода. В состоянии _fail предполагается, что поток не испорчен и никакие символы не потеряны. В состоянии _bad может быть все что угодно.

Состояние потока можно проверять например так:

switch (cin.rdstate()) { case _good: // последняя операция над cin прошла успешно break; case _eof: // конец файла break; case _fail: // некоего рода ошибка форматирования, возможно, не слишком плохая break; case _bad: // возможно, символы cin потеряны break; }

Для любой переменной z типа, для которого определены операции << и >>, копирующий цикл можно написать так:

while (cin>>z) cout << z << "\n";

Например, если z - вектор символов, этот цикл будет брать стандартный ввод и помещать его в стандартный вывод по одному слову (то есть, последовательности символов без пробела) на строку.

Когда в качестве условия используется поток, происходит проверка состояния потока, и эта проверка проходит успешно (то есть, значение условия не ноль) только если состояние _good. В частности, в предыдущем цикле проверялось состояние istream, которое возвращает cin>>z. Чтобы обнаружить, почему цикл или проверка закончились неудачно, можно исследовать состояние.

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

Файловый ввод-вывод с применением потоков С++.
Конструкторы файловых потоков

Потоки обычно связаны с файлами. Библиотека потоков создает стандартный поток ввода cin, стандартный поток вывода cout и стандартный поток ошибок cerr. Программист может открывать другие файлы и создавать для них потоки.

Для инициализации потоков вывода ostream имеет конструкторы:

class ostream { //... ostream(streambuf* s); // связывает с буфером потока ostream(int fd); // связывание для файла ostream(int size, char* p); // связывает с вектором };

Главная работа этих конструкторов - связывать с потоком буфер. streambuf - класс, управляющий буферами, как и класс filebuf, управляющий streambuf для файла. Класс filebuf является производным от класса streambuf.

Естественно, тип istream, так же как и ostream, снабжен конструкторами:

class istream { //... istream(streambuf* s, int sk =1, ostream* t =0); istream(int size, char* p, int sk =1); istream(int fd, int sk =1, ostream* t =0); };

Параметр sk задает, должны пропускаться пропуски или нет. Параметр t (необязательный) задает указатель на ostream, к которому прикреплен istream. Например, cin прикреплен к cout; это значит, что перед тем, как попытаться читать символы из своего файла, cin выполняет cout.flush(); - пишет буфер вывода.

С помощью функции istream::tie() можно прикрепить (или открепить, с помощью tie(0)) любой ostream к любому istream.

Например:

01 int y_or_n(ostream& to, istream& from)02 /* "to", получает отклик из "from" */03 {04 ostream* old = from.tie(&to);05 for(;;)06 {07 cout << "наберите Y или N: ";08 char ch = 0;09 if (!cin.get(ch)) return 0;10 if (ch!= '\n')11 { // пропускает остаток строки12 char ch2 = 0;13 while (cin.get(ch2) && ch2!= '\n');14 }15 switch (ch)16 {17 case 'Y':18 case 'y':19 case '\n':20 from.tie(old); // восстанавливает старый tie21 return 1;22 case 'N':23 case 'n':24 from.tie(old); // восстанавливает старый tie25 return 0;26 default:27 cout << "извините, попробуйте еще раз: ";28 }29 }30 }

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

Символ можно вернуть в поток с помощью функции istream::putback(char). Это позволяет программе "заглядывать вперед" в поток ввода.

Деструктор для ostream сбрасывает буфер с помощью открытого члена функции ostream::flush():

ostream::~ostream(){ flush(); // сброс}

Сбросить буфер можно также и явно. Например:

cout.flush();



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2017-12-29 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: