Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2vcTnguyvU

.pdf
Скачиваний:
6
Добавлен:
15.04.2023
Размер:
955.27 Кб
Скачать

С помощью стандартного манипулятора можно более элегантно задавать размера поля вывода.

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

В классе ios содержится состояние формата, которое управляется функциями flags() и setf(). По сути эти функции нужны, чтобы установить или отменить следующие флаги:

class ios { public:

// управляющие форматом флаги: enum {

skipws=01,

// пропуск обобщенных пробелов для input

 

// поле выравнивания:

left=02,

// добавление перед значением

right=04,

// добавление после значения

internal=010, // добавление между знаком и значением

 

// основание целого:

dec=020,

// восьмеричное

oct=040,

// десятичное

hex=0100,

// шестнадцатеричное

showbase=0200,// показать основание целого showpoint=0400,// выдать нули в конце uppercase=01000,// 'E', 'X' , а не 'e', 'x' showpos=02000,// '+' для положительных чисел // запись числа типа float:

scientific=04000, // .dddddd Edd fixed=010000, // dddd.dd

// сброс в выходной поток: unitbuf=020000,// после каждой операции stdio=040000 // после каждого символа }; //...

}; Конкретные значения флагов зависят от реализации и даны здесь

только для того, чтобы избежать синтаксически неверных конструкций. Определение интерфейса как набора флагов и операций для их

установки или отмены - это несколько устаревший прием. Основное его достоинство в том, что пользователь может собрать воедино набор флагов, например, так:

const int my_io_options =ios::left|ios::oct|ios::showpoint|ios::fixed;

80

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

cout.flags(my_io_options);

void your_function(int ios_options); void my_function()

{

// ...

your_function(my_io_options); // ...

}

Множество флагов можно установить с помощью функции flags(), например: void your_function(int ios_options)

{

int old_options = cout.flags(ios_options); // ...

cout.flags(old_options); // reset options

}

Функция flags() возвращает старое значение множества флагов. Это позволяет переустановить значения всех флагов, как показано выше, а также задать значение отдельному флагу. Например, вызов

myostream.flags(myostream.flags()|ios::showpos);

заставляет класс myostream выдавать положительные числа со знаком + и, в то же время, не меняет значения других флагов. Получается старое значение множества флагов, к которому добавляется с помощью операции | флаг showpos. Функция setf() делает то же самое, поэтому эквивалентная запись имеет вид: myostream.setf(ios::showpos);

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

Вывод целых

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

Решение, принятое в <iostream>, сводится к использованию версии функции setf(), работающей со вторым "псевдопараметром", который показывает, какой именно флаг нужно добавить к новому значению. Поэтому обращения

cout.setf(ios::oct,ios::basefield); // восьмеричное

81

cout.setf(ios::dec,ios::basefield); // десятичное cout.setf(ios::hex,ios::basefield); // шестнадцатеричное

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

cout << 1234 << ' ';

// десятичное по умолчанию

cout << 1234 << ' ';

 

 

cout.setf(ios::oct,ios::basefield);// восьмеричное

cout << 1234 << ' ';

 

 

cout << 1234 << ' ';

 

 

cout.setf(ios::hex,ios::basefield);

// шестнадцатеричное

cout << 1234 << ' ';

 

 

cout << 1234 << ' ';

 

 

напечатает

 

 

1234 1234 2322 2322 4d2 4d2

 

Если появится необходимость

указывать систему счисления для

каждого выдаваемого числа, следует установить флаг showbase. Поэтому, добавив перед приведенными выше обращениями

cout.setf(ios::showbase); мы получим

1234 1234 02322 02322 0x4d2 0x4d2

Стандартные манипуляторы предлагают улучшенный способ определения системы счисления при выводе целых.

Выравнивание полей

С помощью обращений к setf() можно управлять расположением

символов в пределах поля:

 

cout.setf(ios::left,ios::adjustfield);

// влево

cout.setf(ios::right,ios::adjustfield);

// вправо

cout.setf(ios::internal,ios::adjustfield); // внутреннее

Будет установлено выравнивание в поле вывода, определяемом функцией ios::width(), причем не затрагивая других компонентов состояния потока.

Выравнивание можно задать следующим образом: cout.width(4);

cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::left,ios::adjustfield); cout << '(' << -12 << ")\n"; cout.width(4); cout.setf(ios::internal,ios::adjustfield);

82

cout << '(' << -12 << "\n"; что выдаст ( -12)

(-12 ) (- 12)

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

Вывод плавающих чисел.

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

cout.setf(ios::scientific,ios::floatfield);

cout.setf(ios::fixed,ios::floatfield); cout.setf(0,ios::floatfield); // вернуться к стандартному

установят вид печати вещественных чисел без изменения других компонентов состояния потока. Например:

cout << 1234.56789 << '\n'; cout.setf(ios::scientific,ios::floatfield); cout << 1234.56789 << '\n'; cout.setf(ios::fixed,ios::floatfield); cout << 1234.56789 << '\n'; напечатает 1234.57

1.234568e+03

1234.567890

После точки печатается n цифр, как задается в обращении cout.precision(n). По умолчанию n равно 6. Вызов функции precision влияет на все операции ввода-вывода с вещественными величинами до следующего обращения к precision, поэтому

cout.precision(8);

cout << 1234.56789 << '\n'; cout << 1234.56789 << '\n'; cout.precision(4);

cout << 1234.56789 << '\n'; cout << 1234.56789 << '\n'; выдаст 1234.5679 1234.5679 1235 1235

Заметьте, что происходит округление, а не отбрасывание дробной части.

83

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

К ним относятся разнообразные операции, которые приходится применять сразу перед или сразу после операции ввода-вывода. Например:

cout << x; cout.flush(); cout << y;

cin.eatwhite(); cin >> x;

Идея манипуляторов позволяет такие операции, как flush() или

eatwhite() прямо вставлять в список

операций ввода-вывода. В файле

<iostream.h> функция flush() описана как

 

ostream& flush(ostream&);

также в классе есть операция operator<<, которая использует указатель на функцию, как указано выше:

class ostream : public virtual ios { // ...

public:

ostream& operator<<(ostream& ostream& (*)(ostream&)); // ...

};

В приведенной ниже строке буфер выталкивается в поток cout дважды в подходящее время:

cout << x << flush << y << flush;

Похожие определения существуют и для класса istream: istream& ws(istream& is ) { return is.eatwhite(); }

class istream : public virtual ios { // ...

public:

istream& operator>>(istream&, istream& (*) (istream&)); // ...

};

поэтому в строке cin >> ws >> x; действительно обобщенные пробелы будут убраны до попытки чтения в x. Однако, поскольку по умолчанию для операции >> пробелы "съедаются" и так, данное применение ws() избыточно. Находят применение и манипуляторы с параметрами. Например, может появиться желание с помощью

cout << setprecision(4) << angle;

напечатать значение вещественной переменной angle с точностью до четырех знаков после точки. Для этого нужно уметь вызывать функцию, которая установит значение переменной, управляющей в потоке точностью вещественных величин. Это достигается так, нужно определить setprecision(4) как объект, который можно "выводить" с помощью operator<<():

class Omanip_int { int i;

ostream& (*f) (ostream&,int);

84

public:

Omanip_int(ostream& (*ff) (ostream&,int), int ii) : f(ff), i(ii) { }

friend ostream& operator<<(ostream& os, Omanip& m) { return m.f(os,m.i); }

};

Конструктор Omanip_int хранит свои аргументы в i и f, а с помощью operator<< вызывается f() с параметром i. Часто объекты таких классов называют объект-функция. Чтобы результат строки

cout << setprecision(4) << angle

был таким как требуется, необходимо, чтобы обращение setprecision(4) создавало безымянный объект класса Omanip_int, содержащий значение 4 и указатель на функцию, которая устанавливает в потоке ostream значение переменной, задающей точность вещественных:

ostream& _set_precision(ostream&,int); Omanip_int setprecision(int i)

{

return Omanip_int(&_set_precision,i);

}

Учитывая сделанные определения, operator<<() приведет к вызову precision(i).

Утомительно определять классы наподобие Omanip_int для всех типов аргументов, поэтому определим шаблон типа:

template<class T> class OMANIP { T i;

ostream& (*f) (ostream&,T); public:

OMANIP(ostream (*ff) (ostream&,T), T ii) : f(ff), i(ii) { }

friend ostream& operator<<(ostream& os, OMANIP& m) { return m.f(os,m.i) }

};

С помощью OMANIP пример с установкой точности можно сократить так: ostream& precision(ostream& os,int) {

os.precision(i); return os; }

OMANIP<int> setprecision(int i) { return OMANIP<int>(&precision,i); }

В файле <iomanip.h> можно найти шаблон типа OMANIP, его двойник для istream - шаблон типа SMANIP, а SMANIP - двойник для ioss.

85

Некоторые из стандартных

манипуляторов,

предлагаемых поточной

библиотекой, описаны ниже.

 

 

Стандартные манипуляторы ввода-вывода

 

Это следующие манипуляторы:

 

// Simple manipulators:

 

 

ios& oct(ios&); // в восьмеричной записи

 

ios& dec(ios&);

// в десятичной записи

 

ios& hex(ios&); // в шестнадцатеричной записи

ostream& endl(ostream&);

// добавить '\n' и вывести

ostream& ends(ostream&);

// добавить '\0' и вывести

ostream& flush(ostream&);

// выдать поток

 

istream& ws(istream&); // удалить обобщенные пробелы

// Манипуляторы имеют параметры:

 

SMANIP<int> setbase(int b);

 

SMANIP<int>

setfill(int f);

 

 

SMANIP<int>

setprecision(int p);

 

SMANIP<int> setw(int w); SMANIP<long> resetiosflags(long b); SMANIP<long> setiosflags(long b); Например,

cout << 1234 << ' ' << hex << 1234 << ' ' << oct << 1234 << endl; напечатает

1234 4d2 2322

cout << setw(4) << setfill('#') << '(' << 12 << ")\n"; cout << '(' << 12 << ")\n";

напечатает

(##12)

(12)

Не забудьте включить файл <iomanip.h>, если используете манипуляторы с параметрами.

Члены ostream

В классе ostream есть лишь несколько функций для управления выводом, большая часть таких функций находится в классе ios.

class ostream : public virtual ios { //...

public:

ostream& flush();

ostream& seekp(streampos); ostream& seekp(streamoff, seek_dir);

86

// от начала файла // от текущей позиции в файле
// от конца файла

streampos tellp(); //...

};

Как уже отмечалось ранее, функция flush() опустошает буфер в выходной поток. Остальные функции используются для позиционирования в ostream при записи. Окончание на букву p указывает, что именно позиция используется при выдаче символов в заданный поток. Конечно эти функции имеют смысл, только если поток присоединен к чему-либо, что допускает позиционирование, например файл. Тип streampos представляет позицию символа в файле, а тип streamoff представляет смещение относительно позиции, заданной seek_dir. Все они определены в классе ios:

class ios { //...

enum seek_dir { beg=0, cur=1, end=2

}; //...

};

Позиции в потоке отсчитываются от 0, как если бы файл был массивом из n символов:

char file[n-1];

и если fout присоединено к file, то fout.seek(10);

fout<<'#';

поместит # в file[10].

Члены istream

Как и для ostream, большинство функций форматирования и управления вводом находится не в классе iostream, а в базовом классе ios.

class istream : public virtual ios { //...

public:

int peek()

istream& putback(char c); istream& seekg(streampos);

istream& seekg(streamoff, seek_dir); streampos tellg();

//...

};

87

Функции позиционирования работают так же, как и их двойники из ostream. Окончание на букву g показывает, что именно позиция используется при вводе символов из заданного потока. Буквы p и g нужны, поскольку мы можем создать производный класс iostreams из классов ostream и istream, и в нем необходимо следить за позициями ввода и вывода.

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

Файлы и потоки

Ниже приведена программа копирования одного файла в другой. Имена файлов берутся из командной строки программы:

#include <fstream.h>

 

#include <libc.h>

 

void error(char* s, char* s2 ="")

{

cerr << s << ' ' << s2 << '\n';

 

exit(1); }

 

int main(int argc, char* argv[])

{

if (argc != 3) error("wrong number of arguments"); ifstream from(argv[1]);

if (!from) error("cannot open input file",argv[1]); ostream to(argv[2]);

if (!to) error("cannot open output file",argv[2]); char ch;

while (from.get(ch)) to.put(ch); if (!from.eof() || to.bad())

error("something strange happened"); return 0; }

Для открытия выходного файла создается объект класса ofstream - выходной поток файла, использующий в качестве аргумента имя файла. Аналогично, для открытия входного файла создается объект класса ifstream - входной файловый поток, также использующий в качестве аргумента имя файла. В обоих случаях следует проверить состояние созданного объекта, чтобы убедиться в успешном открытии файла, а если это не так, операции завершатся не успешно, но корректно.

По умолчанию ifstream всегда открывается на чтение, а ofstream открывается на запись. В ostream и в istream можно использовать необязательный второй аргумент, указывающий иные режимы открытия:

class ios {

88

public: //...

enum open_mode {

in=1, // открыть на чтение

out=2,

// открыть как выходной

ate=4,// открыть и переместиться в конец файла

app=010,

// добавить

trunc=020,

// сократить файл до нулевой длины

nocreate=040,

// неудача, если файл не существует

noreplace=0100

// неудача, если файл существует

}; //...

};

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

void f() {

ofstream mystream(name,ios::out|ios::nocreate); if (ofstream.bad()) {

//...

}

//...

}

Также можно открыть файл сразу на чтение и запись: fstream dictionary("concordance", ios::in|ios::out);

Все операции, допустимые для ostream и ostream, можно применять к fstream. На самом деле, класс fstream является производным от iostream, который является, в свою очередь, производным от istream и ostream. Причина, по которой информация по буферизации и форматированию для ostream и istream находится в виртуальном базовом классе ios, в том, чтобы заставить действовать всю эту последовательность производных классов. По этой же причине операции позиционирования в istream и ostream имеют разные имена - seekp() и seekg(). В iostream есть отдельные позиции для чтения и записи.

Закрытие потоков

Файл может быть закрыт явно, если вызвать close() для его потока: mystream.close( );

89

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]