Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Перегрузка оператора ввода
Перегрузка оператора ввода (> > ) похожа на перегрузку оператора вывода, но, к сожалению, возможностей для ошибок гораздо больше. Вот, например, его реализация для класса WordCount:
} На этом примере показан целый ряд проблем, имеющих отношение к возможным ошибочным состояниям входного потока: · поток, чтение из которого невозможно из-за неправильного формата, переводится в состояние fail: is.setstate( ios_base:: failbit ); · операции вставки и извлечения из потока, находящегося в ошибочном состоянии, не работают: while (( ch = is.get() )! = lbrace) Инструкция зациклится, если объект istream будет находиться в ошибочном состоянии. Поэтому перед каждым обращением к get() проверяется отсутствие ошибки:
while ( is & & ( ch = is.get() )! = lbrace) Если объект istream не в “хорошем” состоянии, то его значение будет равно false. (О состояниях потока мы расскажем в разделе 20.7.) Данная программа считывает объект класса WordCount, сохраненный оператором вывода из предыдущего раздела:
} Выводится следующее:
< 10> rosebud < 11, 3> < 11, 8> < 14, 2> < 34, 6> < 49, 7> < 67, 5> < 81, 2> < 82, 3> < 91, 4> < 97, 8>
Упражнение 20.9 Оператор ввода класса WordCount сам читает объекты класса Location. Вынесите этот код в отдельный оператор ввода класса Location. Упражнение 20.10 Реализуйте оператор ввода для класса Date из упражнения 20.7 в разделе 20.4. Упражнение 20.11 Реализуйте оператор ввода для класса CheckoutRecord из упражнения 20.8 в разделе 20.4. Файловый ввод/вывод Если программе необходимо работать с файлом, то следует включить в нее заголовочный файл fstream (который в свою очередь включает iostream): #include < fstream> Если файл будет использоваться только для вывода, мы определяем объект класса ofstream. Например: ofstream outfile( " copy.out", ios:: base:: out ); Передаваемые конструктору аргументы задают имя открываемого файла и режим открытия. Файл типа ofstream может быть открыт либо – по умолчанию – в режиме вывода (ios_base:: out), либо в режиме дозаписи (ios_base:: app). Такое определение файла outfile2 эквивалентно приведенному выше:
ofstream outfile2( " copy.out" ); Если в режиме вывода открывается существующий файл, то все хранившиеся в нем данные пропадают. Если же мы хотим не заменить, а добавить данные, то следует открывать файл в режиме дозаписи: тогда новые данные помещаются в конец. Если указанный файл не существует, то он создается в любом режиме. Прежде чем пытаться прочитать из файла или записать в него, нужно проверить, что файл был успешно открыт:
} Класс ofstream является производным от ostream. Все определенные в ostream операции применимы и к ofstream. Например, инструкции
outFile < < " 1 + 1 = " < < (1 + 1) < < endl; выводят в файл outFile последовательность символов:
1) 1 + 1 = 2
Следующая программа читает из стандартного ввода символы и копирует их в стандартный вывод:
} К объекту класса ofstream можно применять и определенные пользователем экземпляры оператора вывода. Данная программа вызывает оператор вывода класса WordCount из предыдущего раздела:
} Чтобы открыть файл только для чтения, применяется объект класса ifstream, производного от istream. Следующая программа читает указанный пользователем файл и копирует его содержимое на стандартный вывод:
} Программа, показанная ниже, читает наш текстовый файл alice_emma, фильтрует его с помощью функции filter_string() (см. раздел 20.2.1, где приведены текст этой функции и содержимое файла), сортирует строки, удаляет дубликаты и записывает результат на стандартный вывод:
} После компиляции и запуска программа выводит следующее:
A Alice Daddy Emma Her I Shyly a alive almost asks at beautiful bird blows but creature fiery flight flowing hair has he her him in is it like long looks magical mean more no red same says she shush such tell tells the there through time to untamed wanting when wind
Объекты классов ofstream и ifstream разрешено определять и без указания имени файла. Позже к этому объекту можно присоединить файл с помощью функции-члена open():
//... Чтобы закрыть файл (отключить от программы), вызываем функцию-член close():
} Объект класса fstream (производного от iostream) может открывать файл для ввода или вывода. В следующем примере файл word.out сначала считывается, а затем записывается с помощью объекта типа fstream. Созданный ранее в этом разделе файл word.out содержит объект WordCount:
} Объект класса fstream может также открывать файл одновременно для ввода и вывода. Например, приведенная инструкция открывает файл word.out для ввода и дозаписи: fstream io( " word.out", ios_base:: in|ios_base:: app ); Для задания нескольких режимов используется оператор побитового ИЛИ. Объект класса fstream можно позиционировать с помощью функций-членов seekg() или seekp(). Здесь буква g обозначает позиционирование для чтения (getting) символов (используется с объектом класса ofstream), а p – для записи (putting) символов (используется с объектом класса ifstream). Эти функции делают текущим тот байт в файле, который имеет указанное абсолютное или относительное смещение. У них есть два варианта:
seekg( off_type offset_position, ios_base:: seekdir dir ); В первом варианте текущая позиция устанавливается в некоторое абсолютное значение, заданное аргументом current_position, причем значение 0 соответствует началу файла. Например, если файл содержит такую последовательность символов:
abc def ghi jkl
то вызов io.seekg( 6 ); позиционирует io на шестой символ, т.е. на f. Второй вариант устанавливает указатель рабочей позиции файла на заданное расстояние от текущей, от начала файла или от его конца в зависимости от аргумента dir, который может принимать следующие значения: · ios_base:: beg – от начала файла; · ios_base:: cur – от текущей позиции; · ios_base:: end – от конца файла. В следующем примере каждый вызов seekg() позиционирует файл на i-ую запись:
readFile.ssekg( i * sizeof(Record), ios_base:: beg ); С помощью первого аргумента можно задавать отрицательное значение. Переместимся на 10 байтов назад от текущей позиции: readFile.seekg( -10, ios_base:: cur ); Текущая позиция чтения в файле типа fstream возвращается любой из двух функций-членов tellg() или tellp(). Здесь 'p' означает запись (putting) и используется с объектом ofstream, а 'g' говорит о чтении (getting) и обслуживает объект ifstream:
writeFile.seekp( mark ); Если необходимо сместиться вперед от текущей позиции на одну запись типа Record, то можно воспользоваться любой из данных инструкций:
readFile.seekg( sizeof(Record), ios_base:: cur ); Разберем реальный пример. Дан текстовый файл, нужно вычислить его длину в байтах и сохранить ее в конце файла. Кроме того, каждый раз при встрече символа новой строки требуется сохранить текущее смещение в конце файла. Вот наш текстовый файл:
abcd efg hi j
Программа должна создать файл, модифицированный следующим образом:
abcd efg hi j 5 9 12 14 24
Так выглядит первая попытка реализации:
} inOut – это объект класса fstream, связанный с файлом copy.out, открытым для ввода и дозаписи. Если файл открыт в режиме дозаписи, то все новые данные записываются в конец. При чтении любого (включая пробельные) символа, кроме конца файла, мы увеличиваем переменную cnt на 1 и копируем прочитанный символ на терминал, чтобы вовремя заметить ошибки в работе программы. Встретив символ новой строки, мы записываем текущее значение cnt в inOut. Как только будет достигнут конец файла, цикл прекращается. Окончательное значение cnt выводится в файл и на экран. Программа компилируется без ошибок и кажется правильной. Но если подать на вход несколько фраз из романа “Моби Дик” Германа Мелвилла:
Call me Ishmael. Some years ago, never mind how long precisely, having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen, and regulating the circulation.
то получим такой результат:
[ 0 ]
Программа не вывела ни одного символа, видимо, полагая, что файл пуст. Проблема в том, что файл открыт для дозаписи и потому позиционирован на конец. При выполнении инструкции inOut.get( ch ); мы читаем конец файла, цикл while завершается и выводится значение 0. Хотя мы допустили серьезную ошибку, исправить ее совсем несложно, поскольку причина понятна. Надо лишь перед чтением переустановить файл на начало. Это делается с помощью обращения: inOut.seekg( 0 ); Запустим программу заново. На этот раз она печатает:
Call me Ishmael. Some years ago, never mind [ 45 ]
Как видим, выводится лишь первая строка текста и счетчик для нее, а оставшиеся шесть строк проигнорированы. Ну что ж, исправление ошибок – неотъемлемая часть профессии программиста. А проблема опять в том, что файл открыт в режиме дозаписи. Как только мы в первый раз вывели cnt, файл оказался позиционирован на конец. При следующем обращении к get() читается конец файла, и цикл while снова завершается преждевременно. Нам необходимо встать на ту позицию в файле, где мы были перед выводом cnt. Для этого понадобятся еще две инструкции:
inOut.seekg( mark ); // восстановить позицию После повторной компиляции программа выводит на экран ожидаемый результат. Но посмотрев на выходной файл, мы обнаружим, что она все еще не вполне правильна: окончательное значение счетчика есть на экране, но не в файле. Оператор вывода, следующий за циклом while, не был выполнен. Дело в том, что inOut находится в состоянии “конец файла”, в котором операции ввода и вывода не выполняются. Для решения проблемы необходимо сбросить это состояние с помощью функции-члена clear(): inOut.clear(); // обнулить флаги состояния Окончательный вариант программы выглядит так:
} Вот теперь – наконец-то! – все правильно. При реализации этой программы было необходимо явно сформулировать поведение, которое мы собирались поддержать. А каждое наше исправление было реакцией на выявившуюся ошибку вместо анализа проблемы в целом. Упражнение 20.12 Пользуясь операторами вывода для класса Date, которые вы определили в упражнении 20.7, или для класса CheckoutRecord из упражнения 20.8 (см. раздел 20.4), напишите программу, позволяющую создать файл и писать в него. Упражнение 20.13 Напишите программу для открытия и чтения файла, созданного в упражнении 20.12. Выведите содержимое файла на стандартный вывод. Упражнение 20.14 Напишите программу для открытия файла, созданного в упражнении 20.12, для чтения и дозаписи. Выведите экземпляр класса Date или CheckoutRecord: (a) в начало файла (b) после второго из существующих объектов (c) в конец файла Состояния потока Пользователей библиотеки iostream, разумеется, интересует, находится ли поток в ошибочном состоянии. Например, если мы пишем
cin > > ival; и вводим слово " Borges", то cin переводится в состояние ошибки после неудачной попытки присвоить строковый литерал целому числу. Если бы мы ввели число 1024, то чтение прошло бы успешно и поток остался бы в нормальном состоянии. Чтобы выяснить, в каком состоянии находится поток, достаточно проверить его значение на истину:
// операция чтения не прошла или встретился конец файла Для чтения заранее неизвестного количества элементов мы обычно пишем цикл while:
// операция чтения завершилась успешно... Условие в цикле while будет равно false, если достигнут конец файла или произошла ошибка при чтении. В большинстве случаев такой проверки потокового объекта достаточно. Однако при реализации оператора ввода для класса WordCount из раздела 20.5 нам понадобился более точный анализ состояния. У любого потока есть набор флагов, с помощью которых можно следить за состоянием потока. Имеются четыре предикатные функции-члена: · eof() возвращает true, если достигнут конец файла:
// отлично: все прочитано... · bad() возвращает true при попытке выполнения некорректной операции, например при установке позиции за концом файла. Обычно это свидетельствует о том, что поток находится в состоянии ошибки; · fail() возвращает true, если операция завершилась неудачно, например не удалось открыть файл или передан некорректный формат ввода:
error_message(... ); · good() возвращает true, если все вышеперечисленные условия ложны: if ( inOut.good() ) Существует два способа явно изменить состояние потока iostream. С помощью функции-члена clear() ему явно присваивается указанное значение. Функция setstate() не сбрасывает состояние, а устанавливает один из флагов, не меняя значения остальных. Например, в коде оператора ввода для класса WordCount при обнаружении неверного формата мы используем setstate() для установки флага fail в состоянии объекта istream:
} Имеются следующие значения флагов состояния:
ios_base:: goodbit Для установки сразу нескольких флагов используется побитовый оператор ИЛИ: is.setstate( ios_base:: badbit | ios_base:: failbit ); При тестировании оператора ввода в классе WordCount (см. раздел 20.5) мы писали:
} Возможно, вместо этого мы предпочли бы продолжить выполнение программы, предупредив пользователя об ошибке и попросив повторить ввод. Но перед чтением нового значения из потока cin необходимо перевести его в нормальное состояние. Это можно сделать с помощью функции-члена clear(): cin.clear(); // сброс ошибок В более общем случае clear() используется для сброса текущего состояния и установки одного или нескольких флагов нового. Например: cin.clear( ios_base:: goodbit ); восстанавливает нормальное состояние потока. (Оба вызова эквивалентны, поскольку goodbit является для clear() аргументом по умолчанию.) Функция-член rdstate() позволяет получить текущее состояние объекта:
cin.clear( old_state ); Упражнение 20.15 Измените один (или оба) оператор ввода для класса Date из упражнения 20.7 и/или класса CheckoutRecord из упражнения 20.8 (см. раздел 20.4) так, чтобы они устанавливали состояние объекта istream. Модифицируйте программы, которыми вы пользовались для тестирования этих операторов, для проверки явно установленного состояния, вывода его на печать и сброса в нормальное. Протестируйте программы, подав на вход правильные и неправильные данные. Строковые потоки Библиотека iostream поддерживает операции над строковыми объектами в памяти. Класс ostringstream вставляет символы в строку, istringstream читает символы из строкового объекта, а stringstream может использоваться как для чтения, так и для записи. Чтобы работать со строковым потоком, в программу необходимо включить заголовочный файл #include < sstream> Например, следующая функция читает весь файл alice_emma в объект buf класса ostringstream. Размер buf увеличивается по мере необходимости, чтобы вместить все символы:
} Функция-член str() возвращает строку – объект класса string, ассоциированный со строковым потоком ostringstream. Этой строкой можно манипулировать так же, как и “обычным” объектом класса string. Например, в следующей программе text почленно инициализируется строкой, ассоциированной с buf:
} Объект класса ostringstream можно использовать для автоматического форматирования составной строки, т.е. строки, составленной из данных разных типов. Так, следующий оператор вывода автоматически преобразует любой арифметический тип в соответствующее строковое представление, поэтому заботиться о выделении нужного количества памяти нет необходимости:
} Иногда лучше собрать все диагностические сообщения об ошибках, а не выводить их по мере возникновения. Это легко сделать с помощью перегруженного множества функций форматирования:
//... и так далее Приложение может сохранить такие строки для последующего отображения и даже рассортировать их по серьезности. Обобщить эту идею помогают классы Notify (извещение), Log (протокол) и Error (ошибка). Поток istringstream читает из объекта класса string, с помощью которого был сконструирован. В частности, он применяется для преобразования строкового представления числа в его арифметическое значение:
} Упражнение 20.16 В языке Си форматирование выходного сообщения производится с помощью функций семейства printf(). Например, следующий фрагмент
ival, dval, cval, sval ); печатает:
ival: 1024 dval: 3.14159 cval: a sval: the end
Первым аргументом printf() является форматная строка. Каждый символ % показывает, что вместо него должно быть подставлено значение аргумента, а следующий за ним символ определяет тип этого аргумента. Вот некоторые из поддерживаемых типов (полное описание см. в [KERNIGHAN88]):
%s C-строка Дополнительные аргументы printf() на позиционной основе сопоставляются со спецификаторами формата, начинающимися со знака %. Все остальные символы в форматной строке рассматриваются как литералы и выводятся буквально. Основные недостатки семейства функций printf() таковы: во-первых, форматная строка не обобщается на определенные пользователем типы, и, во-вторых, если типы или число аргументов не соответствуют форматной строке, компилятор не заметит ошибки, а вывод будет отформатирован неверно. Однако у функций printf() есть и достоинство – компактность записи. 1. Получите так же отформатированный результат с помощью объекта класса ostringstream. 2. Сформулируйте достоинства и недостатки обоих подходов. Состояние формата Каждый объект класса из библиотеки iostream поддерживает состояние формата, которое управляет выполнением операций форматирования, например основание системы счисления для целых значений или точность для значений с плавающей точкой. Для модификации состояния формата объекта в распоряжении программиста имеется предопределенный набор манипуляторов[O.A.6].1 Манипулятор применяется к потоковому объекту так же, как к данным. Однако вместо чтения или записи данных манипулятор модифицирует внутреннее состояние потока. Например, по умолчанию объект типа bool, имеющий значение true (а также литеральная константа true), выводится как целая ‘1’:
} Чтобы поток cout выводил переменную illustrate в виде слова true, мы применяем манипулятор boolalpha:
} Поскольку манипулятор возвращает потоковый объект, к которому он применялся, то допустимо прицеплять его к выводимым данным и другим манипуляторам. Вот как можно перемежать данные и манипуляторы в нашей программе:
} Вывод данных и манипуляторов вперемежку может сбить пользователя с толку. Применение манипулятора изменяет не только представление следующего за ним объекта, но и внутреннее состояние потока. В нашем примере все значения типа bool в оставшейся части программы также будут выводиться в виде строк. Чтобы отменить сделанную модификацию потока cout, необходимо использовать манипулятор noboolalpha:
< < noboolalpha // сбрасывает внутреннее состояние cout Как мы покажем, для многих манипуляторов имеются парные. По умолчанию значения арифметических типов читаются и записываются в десятичной системе счисления. Программист может изменить ее на восьмеричную или шестнадцатеричную, а затем вернуться к десятичной (это распространяется только на целые типы, но не на типы с плавающей точкой), пользуясь манипуляторами hex, oct и dec:
} Эта программа печатает следующее:
ival: 16 установлен oct: 20 dval: 16 установлен hex: 16 ival: 10 установлен dec: 16
Но, глядя на значение, мы не можем понять, в какой системе счисления оно записано. Например, 20 – это действительно 20 или восьмеричное представление 16? Манипулятор showbase выводит основание системы счисления вместе со значением с помощью следующих соглашений: · 0x в начале обозначает шестнадцатеричную систему (если мы хотим, чтобы вместо строчной буквы 'x' печаталась заглавная, то можем применить манипулятор uppercase, а для отмены – манипулятор nouppercase); · 0 в начале обозначает восьмеричную систему; · отсутствие того и другого обозначает десятичную систему. Вот та же программа, но и с использованием showbase:
} Результат:
ival: 16 установлен oct: 020 dval: 16 установлен hex: 16 ival: 0x10 установлен dec: 16
Манипулятор noshowbase восстанавливает состояние cout, при котором основание системы счисления не выводится. По умолчанию значения с плавающей точкой выводятся с точностью 6. Эту величину можно модифицировать с помощью функции-члена precision(int) или манипулятора setprecision(); для использования последнего необходимо включить заголовочный файл iomanip. precision() возвращает текущее значение точности. Например:
} После компиляции и запуска программа печатает следующее:
Точность: 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:
< < noshowpoint < < '\n'; Манипулятор noshowpoint восстанавливает поведение по умолчанию. По умолчанию значения с плавающей точкой выводятся в нотации с фиксированной точкой. Для перехода на научную нотацию используется идентификатор scientific, а для возврата к прежней нотации – модификатор fixed:
< < 10.0 < < '\n'; В результате печатается:
научная: 1.0e+01 с фиксированной точкой: 10
Если бы мы захотели вместо буквы 'e' выводить 'E', то следовало бы употребить манипулятор uppercase, а для возврата к 'e' – nouppercase. (Манипулятор uppercase не приводит к переводу букв в верхний регистр при печати.) По умолчанию перегруженные операторы ввода пропускают пустые символы (пробелы, знаки табуляции, новой строки и возврата каретки). Если дана последовательность:
a bc d
то цикл
//... читает все буквы от 'a' до 'd' за четыре итерации, а пробельные разделители оператором ввода игнорируются. Манипулятор noskipws отменяет такой пропуск пробельных символов:
cin > > skipws; Теперь цикл while будет выполняться семь раз. Чтобы восстановить поведение по умолчанию, к потоку cin применяется манипулятор skipws. Когда мы пишем: cout < < " пожалуйста, введите значение: "; то в буфере потока cout сохраняется литеральная строка. Есть ряд условий, при которых буфер сбрасывается (т.е. опустошается), – в нашем случае в стандартный вывод: · буфер может заполниться. Тогда перед чтением следующего значения его необходимо сбросить; · буфер можно сбросить явно с помощью любого из манипуляторов flush, ends или endl:
cout < < " hi! " < < endl; · при установлении внутренней переменной состояния потока unitbuf буфер сбрасывается после каждой операции вывода; · объект ostream может быть связан (tied) с объектом istream. Тогда буфер ostream сбрасывается каждый раз, когда istream читает из входного потока. cout всегда связан с cin: cin.tie( & cout ); Инструкция cin > > ival; приводит к сбросу буфера cout. В любой момент времени объект ostream разрешено связывать только с одним объектом istream. Чтобы разорвать существующую связь, мы передаем функции-члену tie() значение 0:
is.tie( old_tie ); // восстановить прежнюю связь Мы можем управлять шириной поля, отведенного для печати числового или строкового значения, с помощью манипулятора setw(). Например, программа
} печатает:
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. Манипуляторы
Последнее изменение этой страницы: 2019-04-09; Просмотров: 428; Нарушение авторского права страницы Главная | Случайная страница | Обратная связь |