Программирование >>  Разработка устойчивых систем 

1 ... 33 34 35 [ 36 ] 37 38 39 ... 196


cout е.what о endl: return EXITJAILURE:

return EXITJUCCESS: } Файл автоматически закрывается деструктором /:-

Мы создаем объект FileClass и используем его в обычных вызовах функци! ввода-вывода С, вызывая функцию fp(). Завершив работу с файлом, о нем можно просто забыть; файл будет закрыт деструктором в конце области видимости.

Хотя указатель на FILE хранится в закрытой переменной, назвать его полностью защищенным нельзя - функция fp() возвращает этот указатель. Поскольку весь эффект от применения класса сводится к гарантированной инициализации и закрытию файла, почему бы не сделать указатель открытым или не воспользоваться структурой вместо класса? Обратите внимание: хотя вы можете получить копию f с помощью функции fp(), присвоить указателю новое значения нельзя - он находится под полным контролем класса. Но сохранение указателя, возвращаемого функцией fp(), позволит прикладному программисту присваивать значения элементам структуры и даже закрыть ее, так что защита скорее обеспечивает действительность указателя на FILE, нежели целостность содержимого структуры.

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

: C04:Fullwrap.h

Полная инкапсуляция файлового ввода-вывода

#ifndef FULLWRAP H

#define FULLWRAP H

#include <cstddef>

linclude <cstdio>

lundef getc

lundef putc

lundef ungetc

using std::size t:

using std::fpos t:

class File { Std::FILE* f:

std::FILE* FO: Возвращает проверенный указатель на f public:

FileO: Создает объект, но не открывает файл File(const char* path, const char* mode = r ): -FileO:

int open(const char* path, const char* mode = r ):

int reopen(const char* path, const char* mode):

int getcO:

int ungetcOnt c):

int putcCint c):

int puts(const char* s):

char* getsCchar* s. int n):

int printf(const char* format, ...):

size t readCvoid* ptr. size t size, size t n);

size t write(const void* ptr. size t size. size t n):

int eofO:



1nt closeO: int flushO:

int seek(long offset, int whence):

int getpos(fpos t* pos):

int setpos(const fpos t* pos):

long tellO:

void rewindO:

void setbuf(char* buf):

int setvbuf(char* buf. int type. size t sz):

int еггогО:

void clearErrO:

#endif FULLWRAPJ /:-

Класс содержит почти все функции файлового ввода-вывода из файла <cstdio> (отсутствует функция vprintf(), реализующая функцию printf()).

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

Конструктор по умолчанию обнуляет закрытый указатель на FILE f. Но теперь перед любыми ссылками на f необходимо проверить значение указателя и убедиться в том, что оно отлично от нуля. Задача решается с помощью функции F(), также объявленной закрытой, потому что она должна использоваться только функциями нашего класса (в этой версии класса мы не собираемся предоставлять пользователю прямо!! доступ К базовой структуре FILE).

В целом решение получилось вполне приличным. Оно достаточно функционально. Можно без труда представить аналогичные классы для стандартного (консольного) ввода-вывода и чтения-записи данных в памяти вместо файла или консоли.

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

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

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

Поскольку форматная строка обрабатывается лишь во время выполнения, проверка ошибок на стадии компиляции невозможна. Вероятно, вы уже стал-



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

Основная проблема заключается в том, что в С++ семейство функций printf() плохо поддается расширению. Эти функции проектировались для работы с базовыми типами данных С (char, int, float, double, wchar t, char*, wchar t* и void*) и их разновидностями. Конечно, можно попытаться добавлять в каждый новый класс перегруженные функции printf() и scanf() (и их разновидности для файлов и строк), но не забывайте: перегруженные функции должны различаться по типам аргументов, а семейство printf() скрывает информацию о типах в форматной строке и переменном списке аргументов. Одной из целей проектирования С++ была простота добавления новых типов, поэтому такое ограничение неприемлемо.

Потоки ввода-вывода

Все перечисленные проблемы очевидно показывают, что система ввода-вывода является одним из первоочередных кандидатов на включение в стандартную библиотеку классов С++. Едва ли не каждый программист начинает изучение нового языка с программы Не11о, world , а ввод-вывод требуется почти в любой программе, поэтому библиотека ввода-вывода С++ должна быть особенно удобной в использовании. Но существует другая, гораздо более серьезная проблема - библиотека ввода-вывода должна легко адаптироваться к введению новых классов. Значит, из этих ограничений следует, что архитектура библиотеки классов ввода-вывода должна быть действительно творческой. Помимо объяснения основных принципов ввода-вывода и форматирования в этой главе также представлены примеры использования этой действительно мощной библиотеки С++.

Операторы чтения и записи

Потоком данных (stream) называется объект, предназначенный для передачи и форматирования символов в фиксированном формате. Потоки данных делятся на потоки ввода (потомки класса istream), потоки вывода (потомки класса ostream) и потоки ввода-вывода (объекты, производные от iostream), поддерживающие оба класса операций. В библиотеку потоков ввода-вывода включены несколько специализаций этих классов: ifstream, ofstream и fstream для файлов и istringstream, ostringstream и stringstream для взаимодействия с стандартным классом string языка С++. Все перечисленные потоковые классы обладают почти одинаковым интерфейсом, поэтому операции с потоком данных практически не отличаются оттого, работаете ли вы с файлом, консолью, блоком памяти или строковым объектом. Единый интерфейс также хорошо подходит для добавления расширений, поддерживающих новые классы. Одни функции реализуют форматированный ввод-вывод, другие позволяют читать и записывать символы без форматирования.



1 ... 33 34 35 [ 36 ] 37 38 39 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика