- •Предисловие
- •Структура книги
- •Благодарности
- •1. Начинаем
- •1.1. Решение задачи
- •1.2. Программа на языке C++
- •1.2.1. Порядок выполнения инструкций
- •1.3. Директивы препроцессора
- •1.4. Немного о комментариях
- •1.5. Первый взгляд на ввод/вывод
- •1.5.1. Файловый ввод/вывод
- •2. Краткий обзор С++
- •2.1. Встроенный тип данных “массив”
- •2.2. Динамическое выделение памяти и указатели
- •2.3. Объектный подход
- •2.4. Объектно-ориентированный подход
- •2.5. Использование шаблонов
- •2.7. Использование пространства имен
- •2.8. Стандартный массив – это вектор
- •Часть II
- •3. Типы данных С++
- •3.1. Литералы
- •3.2. Переменные
- •3.2.1. Что такое переменная
- •3.2.2. Имя переменной
- •3.2.3. Определение объекта
- •3.3. Указатели
- •3.4. Строковые типы
- •3.4.1. Встроенный строковый тип
- •3.4.2. Класс string
- •3.5. Спецификатор const
- •3.6. Ссылочный тип
- •3.7. Тип bool
- •3.8. Перечисления
- •3.9. Тип “массив”
- •3.9.1. Многомерные массивы
- •3.9.2. Взаимосвязь массивов и указателей
- •3.10. Класс vector
- •3.11. Класс complex
- •3.12. Директива typedef
- •3.14. Класс pair
- •3.15. Типы классов
- •4. Выражения
- •4.2. Арифметические операции
- •4.3. Операции сравнения и логические операции
- •4.4. Операции присваивания
- •4.5. Операции инкремента и декремента
- •4.6. Операции с комплексными числами
- •4.7. Условное выражение
- •4.8. Оператор sizeof
- •4.9. Операторы new и delete
- •4.10. Оператор “запятая”
- •4.11. Побитовые операторы
- •4.12. Класс bitset
- •4.13. Приоритеты
- •4.14. Преобразования типов
- •4.1. Что такое выражение?
- •4.14.1. Неявное преобразование типов
- •4.14.2. Арифметические преобразования типов
- •4.14.3. Явное преобразование типов
- •4.14.4. Устаревшая форма явного преобразования
- •4.15. Пример: реализация класса Stack
- •5. Инструкции
- •5.1. Простые и составные инструкции
- •5.2. Инструкции объявления
- •5.3. Инструкция if
- •5.4. Инструкция switch
- •5.5. Инструкция цикла for
- •5.6. Инструкция while
- •5.8. Инструкция do while
- •5.8. Инструкция break
- •5.9. Инструкция continue
- •5.10. Инструкция goto
- •5.11. Пример связанного списка
- •5.11.1. Обобщенный список
- •6. Абстрактные контейнерные типы
- •6.1. Система текстового поиска
- •6.2. Вектор или список?
- •6.3. Как растет вектор?
- •6.4. Как определить последовательный контейнер?
- •6.5. Итераторы
- •6.6. Операции с последовательными контейнерами
- •6.6.1. Удаление
- •6.6.2. Присваивание и обмен
- •6.6.3. Обобщенные алгоритмы
- •6.7. Читаем текстовый файл
- •6.8. Выделяем слова в строке
- •6.9. Обрабатываем знаки препинания
- •6.10. Приводим слова к стандартной форме
- •6.11. Дополнительные операции со строками
- •6.12. Строим отображение позиций слов
- •6.12.2. Поиск и извлечение элемента отображения
- •6.12.3. Навигация по элементам отображения
- •6.12.4. Словарь
- •6.12.5. Удаление элементов map
- •6.13. Построение набора стоп-слов
- •6.13.2. Поиск элемента
- •6.13.3. Навигация по множеству
- •6.14. Окончательная программа
- •6.15. Контейнеры multimap и multiset
- •6.16. Стек
- •6.17. Очередь и очередь с приоритетами
- •6.18. Вернемся в классу iStack
- •Часть III
- •7. Функции
- •7.1. Введение
- •7.2. Прототип функции
- •7.2.1. Тип возвращаемого функцией значения
- •7.2.2. Список параметров функции
- •7.2.3. Проверка типов формальных параметров
- •7.3. Передача аргументов
- •7.3.1. Параметры-ссылки
- •7.3.2. Параметры-ссылки и параметры-указатели
- •7.3.3. Параметры-массивы
- •7.3.5. Значения параметров по умолчанию
- •7.3.6. Многоточие
- •7.4. Возврат значения
- •7.5. Рекурсия
- •7.6. Встроенные функции
- •7.7. Директива связывания extern "C" A
- •7.8.1. Класс для обработки параметров командной строки
- •7.9. Указатели на функции
- •7.9.1. Тип указателя на функцию
- •7.9.2. Инициализация и присваивание
- •7.9.3. Вызов
- •7.9.4. Массивы указателей на функции
- •7.9.5. Параметры и тип возврата
- •7.9.6. Указатели на функции, объявленные как extern "C"
- •8. Область видимости и время жизни
- •8.1. Область видимости
- •8.1.1. Локальная область видимости
- •8.2. Глобальные объекты и функции
- •8.2.1. Объявления и определения
- •8.2.2. Сопоставление объявлений в разных файлах
- •8.2.3. Несколько слов о заголовочных файлах
- •8.3. Локальные объекты
- •8.3.1. Автоматические объекты
- •8.3.2. Регистровые автоматические объекты
- •8.3.3. Статические локальные объекты
- •8.4. Динамически размещаемые объекты
- •8.4.2. Шаблон auto_ptr А
- •8.4.3. Динамическое создание и уничтожение массивов
- •8.4.5. Оператор размещения new А
- •8.5. Определения пространства имен А
- •8.5.1. Определения пространства имен
- •8.5.2. Оператор разрешения области видимости
- •8.5.3. Вложенные пространства имен
- •8.5.4. Определение члена пространства имен
- •8.5.5. ПОО и члены пространства имен
- •8.5.6. Безымянные пространства имен
- •8.6. Использование членов пространства имен А
- •8.6.1. Псевдонимы пространства имен
- •8.6.2. Using-объявления
- •8.6.3. Using-директивы
- •8.6.4. Стандартное пространство имен std
- •9. Перегруженные функции
- •9.1. Объявления перегруженных функций
- •9.1.1. Зачем нужно перегружать имя функции
- •9.1.2. Как перегрузить имя функции
- •9.1.3. Когда не надо перегружать имя функции
- •9.1.4. Перегрузка и область видимости A
- •9.1.5. Директива extern "C" и перегруженные функции A
- •9.1.6. Указатели на перегруженные функции A
- •9.1.7. Безопасное связывание A
- •9.2. Три шага разрешения перегрузки
- •9.3. Преобразования типов аргументов A
- •9.3.1. Подробнее о точном соответствии
- •9.3.3. Подробнее о стандартном преобразовании
- •9.3.4. Ссылки
- •9.4. Детали разрешения перегрузки функций
- •9.4.1. Функции-кандидаты
- •9.4.2. Устоявшие функции
- •9.4.3. Наилучшая из устоявших функция
- •9.4.4. Аргументы со значениями по умолчанию
- •10. Шаблоны функций
- •10.1. Определение шаблона функции
- •10.2. Конкретизация шаблона функции
- •10.3. Вывод аргументов шаблона А
- •10.4. Явное задание аргументов шаблона A
- •10.5. Модели компиляции шаблонов А
- •10.5.1. Модель компиляции с включением
- •10.5.2. Модель компиляции с разделением
- •10.5.3. Явные объявления конкретизации
- •10.6. Явная специализация шаблона А
- •10.7. Перегрузка шаблонов функций А
- •10.8. Разрешение перегрузки при конкретизации A
- •10.9. Разрешение имен в определениях шаблонов А
- •10.10. Пространства имен и шаблоны функций А
- •10.11. Пример шаблона функции
- •11. Обработка исключений
- •11.1. Возбуждение исключения
- •11.2. try-блок
- •11.3. Перехват исключений
- •11.3.1. Объекты-исключения
- •11.3.2. Раскрутка стека
- •11.3.3. Повторное возбуждение исключения
- •11.3.4. Перехват всех исключений
- •11.4. Спецификации исключений
- •11.4.1. Спецификации исключений и указатели на функции
- •11.5. Исключения и вопросы проектирования
- •12. Обобщенные алгоритмы
- •12.1. Краткий обзор
- •12.2. Использование обобщенных алгоритмов
- •12.3. Объекты-функции
- •12.3.1. Предопределенные объекты-функции
- •12.3.2. Арифметические объекты-функции
- •12.3.3. Сравнительные объекты-функции
- •12.3.4. Логические объекты-функции
- •12.3.5. Адаптеры функций для объектов-функций
- •12.3.6. Реализация объекта-функции
- •12.4. Еще раз об итераторах
- •12.4.1. Итераторы вставки
- •12.4.2. Обратные итераторы
- •12.4.3. Потоковые итераторы
- •12.4.4. Итератор istream_iterator
- •12.4.5. Итератор ostream_iterator
- •12.4.6. Пять категорий итераторов
- •12.5. Обобщенные алгоритмы
- •12.5.1. Алгоритмы поиска
- •12.5.2. Алгоритмы сортировки и упорядочения
- •12.5.3. Алгоритмы удаления и подстановки
- •12.5.4. Алгоритмы перестановки
- •12.5.5. Численные алгоритмы
- •12.5.6. Алгоритмы генерирования и модификации
- •12.5.7. Алгоритмы сравнения
- •12.5.8. Алгоритмы работы с множествами
- •12.5.9. Алгоритмы работы с хипом
- •12.6.1. Операция list_merge()
- •12.6.2. Операция list::remove()
- •12.6.3. Операция list::remove_if()
- •12.6.4. Операция list::reverse()
- •12.6.5. Операция list::sort()
- •12.6.6. Операция list::splice()
- •12.6.7. Операция list::unique()
- •Часть IV
- •13. Классы
- •13.1. Определение класса
- •13.1.1. Данные-члены
- •13.1.2. Функции-члены
- •13.1.3. Доступ к членам
- •13.1.4. Друзья
- •13.1.5. Объявление и определение класса
- •13.2. Объекты классов
- •13.3. Функции-члены класса
- •13.3.1. Когда использовать встроенные функции-члены
- •13.3.2. Доступ к членам класса
- •13.3.3. Закрытые и открытые функции-члены
- •13.3.4. Специальные функции-члены
- •13.3.5. Функции-члены со спецификаторами const и volatile
- •13.3.6. Объявление mutable
- •13.4. Неявный указатель this
- •13.4.1. Когда использовать указатель this
- •13.5. Статические члены класса
- •13.5.1. Статические функции-члены
- •13.6. Указатель на член класса
- •13.6.1. Тип члена класса
- •13.6.2. Работа с указателями на члены класса
- •13.6.3. Указатели на статические члены класса
- •13.7. Объединение – класс, экономящий память
- •13.8. Битовое поле – член, экономящий память
- •13.9. Область видимости класса A
- •13.9.1. Разрешение имен в области видимости класса
- •13.10. Вложенные классы A
- •13.11. Классы как члены пространства имен A
- •13.12. Локальные классы A
- •14.1. Инициализация класса
- •14.2. Конструктор класса
- •14.2.1. Конструктор по умолчанию
- •14.2.2. Ограничение прав на создание объекта
- •14.2.3. Копирующий конструктор
- •14.3. Деструктор класса
- •14.3.1. Явный вызов деструктора
- •14.3.2. Опасность увеличения размера программы
- •14.4. Массивы и векторы объектов
- •14.4.1. Инициализация массива, распределенного из хипа A
- •14.4.2. Вектор объектов
- •14.5. Список инициализации членов
- •14.6. Почленная инициализация A
- •14.6.1. Инициализация члена, являющегося объектом класса
- •14.7. Почленное присваивание A
- •14.8. Соображения эффективности A
- •15.1. Перегрузка операторов
- •15.1.1. Члены и не члены класса
- •15.1.2. Имена перегруженных операторов
- •15.1.3. Разработка перегруженных операторов
- •15.2. Друзья
- •15.3. Оператор =
- •15.4. Оператор взятия индекса
- •15.5. Оператор вызова функции
- •15.6. Оператор “стрелка”
- •15.7. Операторы инкремента и декремента
- •15.8. Операторы new и delete
- •15.8.1. Операторы new[ ] и delete [ ]
- •15.8.2. Оператор размещения new() и оператор delete()
- •15.9. Определенные пользователем преобразования
- •15.9.1. Конвертеры
- •15.9.2. Конструктор как конвертер
- •15.10. Выбор преобразования A
- •15.10.1. Еще раз о разрешении перегрузки функций
- •15.10.2. Функции-кандидаты
- •15.11. Разрешение перегрузки и функции-члены A
- •15.11.1. Объявления перегруженных функций-членов
- •15.11.2. Функции-кандидаты
- •15.11.3. Устоявшие функции
- •15.12. Разрешение перегрузки и операторы A
- •15.12.1. Операторные функции-кандидаты
- •15.12.2. Устоявшие функции
- •15.12.3. Неоднозначность
- •16. Шаблоны классов
- •16.1. Определение шаблона класса
- •16.1.1. Определения шаблонов классов Queue и QueueItem
- •16.2. Конкретизация шаблона класса
- •16.2.1. Аргументы шаблона для параметров-констант
- •16.3. Функции-члены шаблонов классов
- •16.3.1. Функции-члены шаблонов Queue и QueueItem
- •16.4. Объявления друзей в шаблонах классов
- •16.4.1. Объявления друзей в шаблонах Queue и QueueItem
- •16.5. Статические члены шаблонов класса
- •16.6. Вложенные типы шаблонов классов
- •16.7. Шаблоны-члены
- •16.8. Шаблоны классов и модель компиляции A
- •16.8.1. Модель компиляции с включением
- •16.8.2. Модель компиляции с разделением
- •16.8.3. Явные объявления конкретизации
- •16.9. Специализации шаблонов классов A
- •16.10. Частичные специализации шаблонов классов A
- •16.11. Разрешение имен в шаблонах классов A
- •16.12. Пространства имен и шаблоны классов
- •16.13. Шаблон класса Array
- •Часть V
- •17. Наследование и подтипизация классов
- •17.1. Определение иерархии классов
- •17.1.1. Объектно-ориентированное проектирование
- •17.2. Идентификация членов иерархии
- •17.2.1. Определение базового класса
- •17.2.2. Определение производных классов
- •17.2.3. Резюме
- •17.3. Доступ к членам базового класса
- •17.4. Конструирование базового и производного классов
- •17.4.1. Конструктор базового класса
- •17.4.2. Конструктор производного класса
- •17.4.3. Альтернативная иерархия классов
- •17.4.4. Отложенное обнаружение ошибок
- •17.4.5. Деструкторы
- •17.5.1. Виртуальный ввод/вывод
- •17.5.2. Чисто виртуальные функции
- •17.5.3. Статический вызов виртуальной функции
- •17.5.4. Виртуальные функции и аргументы по умолчанию
- •17.5.5. Виртуальные деструкторы
- •17.5.6. Виртуальная функция eval()
- •17.5.7. Почти виртуальный оператор new
- •17.5.8. Виртуальные функции, конструкторы и деструкторы
- •17.6. Почленная инициализация и присваивание A
- •17.7. Управляющий класс UserQuery
- •17.7.1. Определение класса UserQuery
- •17.8. Соберем все вместе
- •18.1. Готовим сцену
- •18.2. Множественное наследование
- •18.3. Открытое, закрытое и защищенное наследование
- •18.3.1. Наследование и композиция
- •18.3.2. Открытие отдельных членов
- •18.3.3. Защищенное наследование
- •18.3.4. Композиция объектов
- •18.4. Область видимости класса и наследование
- •18.5. Виртуальное наследование A
- •18.5.1. Объявление виртуального базового класса
- •18.5.2. Специальная семантика инициализации
- •18.5.3. Порядок вызова конструкторов и деструкторов
- •18.5.4. Видимость членов виртуального базового класса
- •18.6.2. Порождение класса отсортированного массива
- •18.6.3. Класс массива с множественным наследованием
- •19. Применение наследования в C++
- •19.1. Идентификация типов во время выполнения
- •19.1.1. Оператор dynamic_cast
- •19.1.2. Оператор typeid
- •19.1.3. Класс type_info
- •19.2. Исключения и наследование
- •19.2.1. Исключения, определенные как иерархии классов
- •19.2.2. Возбуждение исключения типа класса
- •19.2.3. Обработка исключения типа класса
- •19.2.4. Объекты-исключения и виртуальные функции
- •19.2.5. Раскрутка стека и вызов деструкторов
- •19.2.6. Спецификации исключений
- •19.2.7. Конструкторы и функциональные try-блоки
- •19.3. Разрешение перегрузки и наследование A
- •19.3.1. Функции-кандидаты
- •19.3.3. Наилучшая из устоявших функций
- •20. Библиотека iostream
- •20.1. Оператор вывода <<
- •20.2. Ввод
- •20.2.1. Строковый ввод
- •20.3. Дополнительные операторы ввода/вывода
- •20.4. Перегрузка оператора вывода
- •20.5. Перегрузка оператора ввода
- •20.6. Файловый ввод/вывод
- •20.7. Состояния потока
- •20.8. Строковые потоки
- •20.9. Состояние формата
- •20.10. Сильно типизированная библиотека
- •accumulate()
- •adjacent_difference()
- •adjacent_find()
- •binary_search()
- •copy()
- •copy_backward()
- •count_if()
- •equal()
- •equal_range()
- •fill()
- •find()
- •find_if()
- •find_end()
- •find_first_of()
- •generate()
- •generate_n()
- •includes()
- •inplace_merge()
- •iter_swap()
- •lexicographical_compare()
- •max_element()
- •merge()
- •next_permutation()
- •nth_element()
- •partial_sort()
- •partial_sort_copy()
- •partition()
- •prev_permutation()
- •random_shuffle()
- •remove()
- •remove_if()
- •remove_copy_if()
- •replace_copy()
- •replace_if()
- •replace_copy_if()
- •reverse_copy()
- •rotate()
- •search_n()
- •set_difference()
- •set_intersection()
- •set_union()
- •sort()
- •stable_partition()
- •swap()
- •swap_ranges()
- •transform()
- •unique_copy()
- •upper_bound()
- •Алгоритмы для работы с хипом
- •make_heap()
- •pop_heap()
- •push_heap()
- •sort_heap()
С++ для начинающих |
1051 |
||
|
|
class CheckoutRecord { |
// запись о выдаче |
|
|
||
|
|
public: |
|
|
|
// ... |
|
|
|
private: |
// идентификатор книги |
|
|
double book_id; |
|
|
|
string title; |
// название |
|
|
Date date_borrowed; |
// дата выдачи |
|
|
Date date_due; |
// дата возврата |
|
|
pair<string,string> borrower; // кому выдана |
|
|
|
vector pair<string,string> wait_list; // очередь на книгу |
|
|
|
}; |
|
|
|
||
|
|
|
|
20.5. Перегрузка оператора ввода
Перегрузка оператора ввода (>>) похожа на перегрузку оператора вывода, но, к сожалению, возможностей для ошибок гораздо больше. Вот, например, его реализация для класса WordCount:
С++ для начинающих |
1052 |
||
|
|
#include <iostream> |
|
|
|
|
|
|
|
#include "WordCount.h" |
|
|
|
/* необходимо модифицировать определение класса WordCount, чтобы |
|
|
|
оператор ввода был другом |
|
|
|
class WordCount { |
|
|
|
friend ostream& operator<<( ostream&, const WordCount& ); |
|
|
|
friend istream& operator>>( istream&, const WordCount& ); |
|
|
|
*/ |
|
|
|
istream& |
|
|
|
operator >>( istream &is, WordCount &wd ) |
|
|
|
{ |
|
|
|
/* формат хранения объекта WordCount: |
|
|
|
* <2> строка |
|
|
|
* <7,3> <12,36> |
|
|
|
*/ |
|
|
|
int ch; |
|
|
|
/* прочитать знак '<'. Если его нет, |
|
|
|
* перевести поток в ошибочное состояние и выйти |
|
|
|
*/ |
|
|
|
if ((ch = is.get()) != '<' ) |
|
|
|
{ |
|
|
|
// is.setstate( ios_base::badbit ); |
|
|
|
return is; |
|
|
|
} |
|
|
|
// прочитать длину |
|
|
|
int occurs; |
|
|
|
is >> occurs; |
|
|
|
// читать до обнаружения >; ошибки не контролируются |
|
|
|
while ( is && (ch = is.get()) != '>' ) ; |
|
|
|
is >> wd._word; |
|
|
|
// прочитать позиции вхождений; |
|
|
|
// каждая позиция имеет формат: < строка, колонка > |
|
|
|
for ( int ix = 0; ix < occurs; ++ix ) |
|
|
|
{ |
|
|
|
int line, col; |
|
|
|
// извлечь значения |
|
|
|
while (is && (ch = is.get())!= '<' ) ; |
|
|
|
is >> line; |
|
|
|
while (is && (ch = is.get())!= ',' ) ; |
|
|
|
is >> col; |
|
|
|
while (is && (ch = is.get())!= '>' ) ; |
|
|
|
wd._occurList.push_back( Location( line, col )); |
|
|
|
} |
|
|
|
return is; |
|
|
|
} |
|
|
|
||
|
|
|
|
На этом примере показан целый ряд проблем, имеющих отношение к возможным |
|
||
ошибочным состояниям входного потока: |
|
С++ для начинающих |
1053 |
||
|
∙ |
поток, чтение из которого невозможно из-за |
неправильного формата, |
|
|
переводится в состояние fail: |
|
|
is.setstate( ios_base::failbit ); |
|
|
|
∙ |
операции вставки и извлечения из потока, находящегося в ошибочном |
|
|
|||
|
|
состоянии, не работают: |
|
while (( ch = is.get() ) != lbrace)
Инструкция зациклится, если объект istream будет находиться в ошибочном состоянии. Поэтому перед каждым обращением к get() проверяется отсутствие
// проверить, находится ли поток "is" в "хорошем" состоянии
ошибки:
while ( is && ( ch = is.get() ) != lbrace)
Если объект istream не в “хорошем” состоянии, то его значение будет равно false. (О состояниях потока мы расскажем в разделе 20.7.)
Данная программа считывает объект класса WordCount, сохраненный оператором вывода
#include <iostream> #include "WordCount.h"
int main()
{
WordCount readIn;
// operator>>( cin, readIn ) cin >> readIn;
if ( !cin ) {
cerr << "Ошибка ввода WordCount" << endl; return -1;
}
// operator<<( cout, readIn ) cout << readIn << endl;
из предыдущего раздела:
}
Выводится следующее:
<10> rosebud |
<14,2> |
<34,6> |
<49,7> |
<67,5> |
|
<11,3> |
<11,8> |
||||
<81,2> |
<82,3> |
<91,4> |
<97,8> |
|
|
Упражнение 20.9
Оператор ввода класса WordCount сам читает объекты класса Location. Вынесите этот код в отдельный оператор ввода класса Location.
С++ для начинающих |
1054 |
Упражнение 20.10
Реализуйте оператор ввода для класса Date из упражнения 20.7 в разделе 20.4.
Упражнение 20.11
Реализуйте оператор ввода для класса CheckoutRecord из упражнения 20.8 в разделе
20.4.
20.6. Файловый ввод/вывод
Если программе необходимо работать с файлом, то следует включить в нее заголовочный файл fstream (который в свою очередь включает iostream):
#include <fstream>
Если файл будет использоваться только для вывода, мы определяем объект класса ofstream. Например:
ofstream outfile( "copy.out", ios::base::out );
Передаваемые конструктору аргументы задают имя открываемого файла и режим открытия. Файл типа ofstream может быть открыт либо – по умолчанию – в режиме вывода (ios_base::out), либо в режиме дозаписи (ios_base::app). Такое определение
// по умолчанию открывается в режиме вывода
файла outfile2 эквивалентно приведенному выше: ofstream outfile2( "copy.out" );
Если в режиме вывода открывается существующий файл, то все хранившиеся в нем данные пропадают. Если же мы хотим не заменить, а добавить данные, то следует открывать файл в режиме дозаписи: тогда новые данные помещаются в конец. Если указанный файл не существует, то он создается в любом режиме.
Прежде чем пытаться прочитать из файла или записать в него, нужно проверить, что
if ( ! outfile ) { // открыть файл не удалось
cerr << "не могу открыть "copy.out" для записи\n"; exit( -1 );
файл был успешно открыт:
}
Класс ofstream является производным от ostream. Все определенные в ostream
char ch = ' ';
outFile.put( '1' ).put( ')' ).put( ch );
операции применимы и к ofstream. Например, инструкции outFile << "1 + 1 = " << (1 + 1) << endl;
С++ для начинающих |
1055 |
выводят в файл outFile последовательность символов:
1) 1 + 1 = 2
Следующая программа читает из стандартного ввода символы и копирует их в
#include <fstream>
int main()
{
// открыть файл copy.out для вывода ofstream outFile( "copy.out" );
if ( ! outFile ) {
cerr << "Не могу открыть 'copy.out' для вывода\n"; return -1;
}
char ch;
while ( cin.get( ch ) ) outFile.put( ch );
стандартный вывод:
}
К объекту класса ofstream можно применять и определенные пользователем экземпляры оператора вывода. Данная программа вызывает оператор вывода класса WordCount из
#include <fstream> #include "WordCount.h"
int main()
{
//открыть файл word.out для вывода ofstream oFile( "word.out" );
//здесь проверка успешности открытия ...
//создать и вручную заполнить объект WordCount WordCount artist( "Renoir" );
artist.found( 7, 12 ); artist.found( 34, 18 );
//вызывается оператор <<(ostream&, const WordCount&); oFile << artist;
предыдущего раздела:
}
Чтобы открыть файл только для чтения, применяется объект класса ifstream, производного от istream. Следующая программа читает указанный пользователем файл и копирует его содержимое на стандартный вывод:
С++ для начинающих |
1056 |
#include <fstream> #include <string>
int main()
{
cout << "filename: "; string file_name;
cin >> file_name;
// открыть файл для ввода
ifstream inFile( file_name.c_str() );
if ( !inFile ) {
cerr << "не могу открыть входной файл: "
<< file_name << " -- аварийный останов!\n"; return -1;
}
char ch;
while ( inFile.get( ch )) cout.put( ch );
}
Программа, показанная ниже, читает наш текстовый файл alice_emma, фильтрует его с помощью функции filter_string() (см. раздел 20.2.1, где приведены текст этой функции и содержимое файла), сортирует строки, удаляет дубликаты и записывает результат на стандартный вывод:
С++ для начинающих |
1057 |
||
|
|
#include <fstream> |
|
|
|
|
|
|
|
#include <iterator> |
|
|
|
#include <vector> |
|
|
|
#include <algorithm> |
|
|
|
template <class InputIterator> |
|
|
|
void filter_string( InputIterator first, InputIterator last, |
|
|
|
string filt_elems = string("\",?.")) |
|
|
|
{ |
|
|
|
for ( ; first != last; first++ ) |
|
|
|
{ |
|
|
|
string::size_type pos = 0; |
|
|
|
while (( pos = (*first).find_first_of( filt_elems, pos )) |
|
|
|
!= string::npos ) |
|
|
|
(*first).erase( pos, 1 ); |
|
|
|
} |
|
|
|
} |
|
|
|
int main() |
|
|
|
{ |
|
|
|
ifstream infile( "alice_emma" ); |
|
|
|
istream_iterator<string> ifile( infile ); |
|
|
|
istream_iterator<string> eos; |
|
|
|
vector< string > text; |
|
|
|
copy( ifile, eos, inserter( text, text.begin() )); |
|
|
|
string filt_elems( "\",.?;:" ); |
|
|
|
filter_string( text.begin(), text.end(), filt_elems ); |
|
|
|
vector<string>::iterator iter; |
|
|
|
sort( text.begin(), text.end() ); |
|
|
|
iter = unique( text.begin(), text.end() ); |
|
|
|
text.erase( iter, text.end() ); |
|
|
|
ofstream outfile( "alice_emma_sort" ); |
|
|
|
iter = text.begin(); |
|
|
|
for ( int line_cnt = 1; iter != text.end(); |
|
|
|
++iter, ++line_cnt ) |
|
|
|
{ |
|
|
|
outfile << *iter << " "; |
|
|
|
if ( ! ( line_cnt % 8 )) |
|
|
|
outfile << '\n'; |
|
|
|
} |
|
|
|
outfile << endl; |
|
|
|
} |
|
|
|
||
|
|
|
|
После компиляции и запуска программа выводит следующее: |
|
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
С++ для начинающих |
1058 |
Объекты классов ofstream и ifstream разрешено определять и без указания имени файла. Позже к этому объекту можно присоединить файл с помощью функции-члена
ifstream curFile; // ...
curFile.open( filename.c_str() );
if ( ! curFile ) // открытие успешно?
open():
// ...
#include <fstream>
const int fileCnt = 5;
string fileTabl[ fileCnt ] = {
"Melville", "Joyce", "Musil", "Proust", "Kafka"
};
int main()
{
ifstream inFile; // не связан ни с каким файлом
for ( int ix = 0; ix < fileCnt; ++ix )
{
inFile.open( fileTabl[ix].c_str() );
//... проверить успешность открытия
//... обработать файл
inFile.close();
}
Чтобы закрыть файл (отключить от программы), вызываем функцию-член close():
}
Объект класса fstream (производного от iostream) может открывать файл для ввода или вывода. В следующем примере файл word.out сначала считывается, а затем записывается с помощью объекта типа fstream. Созданный ранее в этом разделе файл word.out содержит объект WordCount:
С++ для начинающих |
1059 |
#include <fstream> #include "WordCount.h"
int main()
{
WordCount wd; fstream file;
file.open( "word.out", ios::in ); file >> wd;
file.close();
cout << "Прочитано: " << wd << endl;
// операция ios_base::out стерла бы текущие данные file.open( "word.out", ios::app );
file << endl << wd << endl; file.close();
}
Объект класса fstream может также открывать файл одновременно для ввода и вывода. Например, приведенная инструкция открывает файл word.out для ввода и дозаписи:
fstream io( "word.out", ios_base::in|ios_base::app );
Для задания нескольких режимов используется оператор побитового ИЛИ. Объект класса fstream можно позиционировать с помощью функций-членов seekg() или seekp(). Здесь буква g обозначает позиционирование для чтения (getting) символов (используется с объектом класса ofstream), а p – для записи (putting) символов (используется с объектом класса ifstream). Эти функции делают текущим тот байт в файле, который
//установить абсолютное смещение в файле seekg( pos_type current_position )
//смещение от текущей позиции в том или ином направлении
имеет указанное абсолютное или относительное смещение. У них есть два варианта: seekg( off_type offset_position, ios_base::seekdir dir );
В первом варианте текущая позиция устанавливается в некоторое абсолютное значение, заданное аргументом current_position, причем значение 0 соответствует началу файла. Например, если файл содержит такую последовательность символов:
abc def ghi jkl
то вызов
io.seekg( 6 );
позиционирует io на шестой символ, т.е. на f. Второй вариант устанавливает указатель рабочей позиции файла на заданное расстояние от текущей, от начала файла или от его конца в зависимости от аргумента dir, который может принимать следующие значения:
С++ для начинающих |
1060 |
∙ios_base::beg – от начала файла;
∙ios_base::cur – от текущей позиции;
∙ios_base::end – от конца файла.
for ( int i = 0; i < recordCnt; ++i )
В следующем примере каждый вызов seekg() позиционирует файл на i-ую запись: readFile.ssekg( i * sizeof(Record), ios_base::beg );
С помощью первого аргумента можно задавать отрицательное значение. Переместимся на 10 байтов назад от текущей позиции:
readFile.seekg( -10, ios_base::cur );
Текущая позиция чтения в файле типа fstream возвращается любой из двух функций- членов tellg() или tellp(). Здесь 'p' означает запись (putting) и используется с
// сохранить текущую позицию ios_base::pos_type mark = writeFile.tellp();
// ...
if ( cancelEntry )
// вернуться к сохраненной позиции
объектом ofstream, а 'g' говорит о чтении (getting) и обслуживает объект ifstream: writeFile.seekp( mark );
Если необходимо сместиться вперед от текущей позиции на одну запись типа Record, то
// эквивалентные вызовы seekg
readFile.seekg( readFile.tellg() + sizeof(Record) );
// данный вызов считается более эффективным
можно воспользоваться любой из данных инструкций: readFile.seekg( sizeof(Record), ios_base::cur );
Разберем реальный пример. Дан текстовый файл, нужно вычислить его длину в байтах и сохранить ее в конце файла. Кроме того, каждый раз при встрече символа новой строки требуется сохранить текущее смещение в конце файла. Вот наш текстовый файл:
abcd efg hi j
Программа должна создать файл, модифицированный следующим образом:
С++ для начинающих |
1061 |
abcd efg hi j
5 9 12 14 24
#include <iostream> #include <fstream>
main() {
// открыть файл для ввода и дозаписи
fstream inOut( "copy.out", ios_base::in|ios_base::app ); int cnt = 0; // счетчик байтов
char ch;
while ( inOut.get( ch ))
{
cout.put( ch ); // скопировать на терминал
++cnt;
if ( ch == '\n' ) { inOut << cnt ;
inOut.put( ' ' ); // пробел
}
}
// вывести окончательное значение счетчика байтов inOut << cnt << endl;
cout << "[ " << cnt << " ]" << endl; return 0;
Так выглядит первая попытка реализации:
}
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.
то получим такой результат:
С++ для начинающих |
1062 |
[ 0 ]
Программа не вывела ни одного символа, видимо, полагая, что файл пуст. Проблема в том, что файл открыт для дозаписи и потому позиционирован на конец. При выполнении
инструкции
inOut.get( ch );
мы читаем конец файла, цикл while завершается и выводится значение 0.
Хотя мы допустили серьезную ошибку, исправить ее совсем несложно, поскольку причина понятна. Надо лишь перед чтением переустановить файл на начало. Это делается с помощью обращения:
inOut.seekg( 0 );
Запустим программу заново. На этот раз она печатает:
Call me Ishmael. Some years ago, never mind [ 45 ]
Как видим, выводится лишь первая строка текста и счетчик для нее, а оставшиеся шесть строк проигнорированы. Ну что ж, исправление ошибок – неотъемлемая часть профессии программиста. А проблема опять в том, что файл открыт в режиме дозаписи. Как только мы в первый раз вывели cnt, файл оказался позиционирован на конец. При следующем обращении к get() читается конец файла, и цикл while снова завершается преждевременно.
Нам необходимо встать на ту позицию в файле, где мы были перед выводом cnt. Для
// запомнить текущую позицию ios_base::pos_type mark = inOut.tellg(); inOut << cnt << sp;
этого понадобятся еще две инструкции:
inOut.seekg( mark ); // восстановить позицию
После повторной компиляции программа выводит на экран ожидаемый результат. Но посмотрев на выходной файл, мы обнаружим, что она все еще не вполне правильна: окончательное значение счетчика есть на экране, но не в файле. Оператор вывода, следующий за циклом while, не был выполнен.
Дело в том, что inOut находится в состоянии “конец файла”, в котором операции ввода и вывода не выполняются. Для решения проблемы необходимо сбросить это состояние с помощью функции-члена clear():
inOut.clear(); // обнулить флаги состояния
Окончательный вариант программы выглядит так:
С++ для начинающих |
1063 |
#include <iostream> #include <fstream>
int main()
{
fstream inOut( "copy.out", ios_base::in|ios_base::app ); int cnt=0;
char ch;
inOut.seekg(0);
while ( inOut.get( ch ))
{
cout.put( ch ); cnt++;
if ( ch == '\n' )
{
// запомнить текущую позицию ios_base::pos_type mark = inOut.tellg(); inOut << cnt << ' ';
inOut.seekg( mark ); // восстановить позицию
}
}
inOut.clear();
inOut << cnt << endl;
cout << "[ " << cnt << " ]\n";
return 0;
}
Вот теперь – наконец-то! – все правильно. При реализации этой программы было необходимо явно сформулировать поведение, которое мы собирались поддержать. А
каждое наше исправление было реакцией на выявившуюся ошибку вместо анализа проблемы в целом.
Упражнение 20.12
Пользуясь операторами вывода для класса Date, которые вы определили в упражнении 20.7, или для класса CheckoutRecord из упражнения 20.8 (см. раздел 20.4), напишите программу, позволяющую создать файл и писать в него.
Упражнение 20.13
Напишите программу для открытия и чтения файла, созданного в упражнении 20.12. Выведите содержимое файла на стандартный вывод.
Упражнение 20.14
Напишите программу для открытия файла, созданного в упражнении 20.12, для чтения и дозаписи. Выведите экземпляр класса Date или CheckoutRecord:
(a)в начало файла
(b)после второго из существующих объектов
(c)в конец файла