Программирование на C++ - Kласс File

prografix's picture

Класс File.

Иногда нужно, чтобы функция одинаковым образом работала, как с файлом на диске, так и с выделенным фрагментом памяти или ещё с чем-нибудь. Т.е. файл должен быть не конкретным, а абстрактным понятием. В этом разделе описаны несколько классов для решения этой задачи. Это уже вторая версия решения, а началось всё с того, что у меня были исходники алгоритма BTPC, сжимающего изображение подобно JPEG, но вход и выход для него задавался в виде файла, а мне нужно было сделать преобразование из памяти в память. Я решил заменить операции ввода-вывода на вызов методов абстрактного класса. Так появился класс File. Позже у меня появились идеи, как развить этот подход и классов стало несколько на разные случаи жизни. Методы этих классов аналогичны соответствующим функциям из библиотеки stdio.

class ReadFile
{
public:
virtual int read(void * p, const int size, const int count) = 0;
virtual bool getc(void * p) = 0;
virtual ~ReadFile() {}
};

Класс ReadFile предназначен для чтения данных. Метод read считывает информацию блоками размером size и количеством count в память с адресом p. Возращаемое значение - это реальное количество прочитанных блоков. Метод getc пытается прочитать один байт и возращает true в случае успеха.

class WriteFile
{
public:
virtual int write(const void * p, const int size, const int count) = 0;
virtual bool putc(const void * p) = 0;
virtual void flush() = 0;
virtual ~WriteFile() {}
};

Класс WriteFile предназначен для записи данных. Метод write записывает информацию блоками размером size и количеством count из памяти с адресом p. Возращаемое значение - это реальное количество записанных блоков. Метод putc пытается записать один байт и возращает true в случае успеха. Метод flush сбрасывает буфер, если это было предусмотрено.

class SeekFile
{
public:
virtual bool seek_set(long offset) = 0;
virtual bool seek_end(long offset) = 0;
virtual bool seek_cur(long offset) = 0;
virtual void rewind() = 0;
virtual long tell() = 0;
virtual ~SeekFile() {}
};

Класс SeekFile осуществляет перемещение по файлу. Методы seek_set, seek_end и seek_cur пытаются сместиться соответственно относительно начала, конца или текущей позиции на offset байт и возращают true в случае успеха. Метод rewind осуществляет переход на начало файла. Метод tell возращает текущее положение ( 0 - это начало файла ) или -1 в случае ошибки.

class ReadSeekFile : public ReadFile, public virtual SeekFile {};

class WriteSeekFile : public WriteFile, public virtual SeekFile {};

class File : public ReadSeekFile, public WriteSeekFile {};

Не все файлы могут поддерживать навигацию по файлу. Те, которые это делают, должны быть производными от классов ReadSeekFile, WriteSeekFile или File. Первый работает только на чтение, второй - только на запись, третий ( самый общий ) - и на чтение, и на запись. Обратите внимание, что здесь есть и множественное наследование, и виртуальные классы. Во многих других языках программирования эти вещи отсутствуют. Лично мне доставляет большое удовольствие проектирование подобных иерархий классов, а кто-то из-за выбора языка такого удовольствия лишён.

Теперь рассмотрим конкретные реализации этих абстрактных классов.

Класс RealFile, производный от класса File, является "настоящим" файлом в том смысле, что он ведёт себя точно также, как и файл из библиотеки stdio, и все его методы вызывают соответствующие функции из этой библиотеки. Вначале ( версия 2 ) я полагал, что эти функции корректно обрабатывают ситуацию, когда указатель file равен нулю, но позже я выяснил, что это не так. Поэтому в версии 2.1 во всех методах есть проверка указателя на ноль. Кроме того в классе появился метод isValid для проверки работоспособности файла.

class RealFile : public File
{
struct _iobuf * file;
// Запрет конструктора копии и оператора присваивания:
RealFile ( RealFile & );
void operator = ( RealFile & );
public:
RealFile(const char * filename, const char * mode);
bool isValid() const { return file != 0; }
int read (void * p, const int size, const int count);
int write(const void * p, const int size, const int count);
bool getc(void * p);
bool putc(const void * p);
bool seek_set(long offset);
bool seek_end(long offset);
bool seek_cur(long offset);
long tell();
void rewind();
void flush();
~RealFile();
};

RealFile::RealFile(const char * filename, const char * mode)
{
file = fopen(filename, mode);
}

RealFile::~RealFile()
{
if ( file ) fclose(file);
}

int RealFile::read(void * ptr, const int size, const int count)
{
if ( !file ) return 0;
return fread(ptr, size, count, file);
}

int RealFile::write(const void * ptr, const int size, const int count)
{
if ( !file ) return 0;
return fwrite(ptr, size, count, file);
}

bool RealFile::getc(void * ptr)
{
if ( !file ) return false;
int i = ::getc(file);
if ( i < 0 ) return false;
*(char *) ptr = (char) i;
return true;
}

bool RealFile::putc(const void * ptr)
{
return file && fputc(*(char *) ptr, file) >= 0;
}

bool RealFile::seek_cur(long offset)
{
return file && fseek(file, offset, SEEK_CUR) == 0;
}

bool RealFile::seek_end(long offset)
{
return file && fseek(file, offset, SEEK_END) == 0;
}

bool RealFile::seek_set(long offset)
{
return file && fseek(file, offset, SEEK_SET) == 0;
}

long RealFile::tell()
{
return file ? ftell(file) : -1;
}

void RealFile::rewind()
{
if(file) ::rewind(file);
}

void RealFile::flush()
{
if(file) fflush(file);
}

Класс PseudoReadSeekFile производный от класса ReadSeekFile называется псевдофайлом потому, что он читает данные не из файла, а из памяти, заданной размером n и указателем p.

class PseudoReadSeekFile : public ReadSeekFile
{
const char * buf;
int length, pos;
public:
PseudoReadSeekFile ( int n, const void * p ) : length(n), buf((const char *)p), pos(0) {}
int read(void * p, const int size, const int count);
bool getc(void * p);
bool seek_set(long offset);
bool seek_end(long offset);
bool seek_cur(long offset);
long tell() { return pos; }
void rewind () { pos = 0; }
};

int PseudoReadSeekFile::read(void * ptr, const int size, const int count)
{
if ( size <= 0 || count <= 0 ) return 0;
int m = ( length - pos ) / size;
if ( m <= 0 ) return 0;
if ( m > count ) m = count;
const int n = size * m;
char * p = (char *) ptr;
for ( int i = 0; i < n; ++i ) p[i] = buf[pos++];
return m;
}

bool PseudoReadSeekFile::getc(void * ptr)
{
if ( pos >= length ) return false;
*(char *) ptr = buf[pos++];
return true;
}

bool PseudoReadSeekFile::seek_cur(long offset)
{
offset += pos;
if ( offset < 0 || offset > length ) return false;
pos = offset;
return true;
}

bool PseudoReadSeekFile::seek_end(long offset)
{
offset += length;
if ( offset < 0 || offset > length ) return false;
pos = offset;
return true;
}

bool PseudoReadSeekFile::seek_set(long offset)
{
if ( offset < 0 || offset > length ) return false;
pos = offset;
return true;
}

Класс PseudoWriteFile производный от класса WriteFile осуществляет запись в контейнер Shev::Array< char > и лишён навигации.

class PseudoWriteFile : public WriteFile
{
Shev::Array< char > & buf;
int pos;
public:
PseudoWriteFile ( Shev::Array< char > & p );
int write(const void * p, const int size, const int count);
bool putc(const void * p);
void flush() {}
long tell() { return pos; }
};

PseudoWriteFile::PseudoWriteFile ( Shev::Array< char > & p ) : buf(p), pos(0)
{
if ( buf.size() < 1 ) buf.resize ( 512 );
}

int PseudoWriteFile::write(const void * ptr, const int size, const int count)
{
if ( size <= 0 || count <= 0 ) return 0;
const int n = size * count;
while ( pos + n > buf.size() ) buf.resizeAndCopy ( 2*buf.size() );
const char * p = (const char *) ptr;
for ( int i = 0; i < n; ++i ) buf[pos++] = p[i];
return count;
}

bool PseudoWriteFile::putc(const void * ptr)
{
if ( pos >= buf.size() ) buf.resizeAndCopy ( 2*buf.size() );
buf[pos++] = *(char *) ptr;
return true;
}

Класс PseudoFile может использоваться вместо двух предыдущих.

class PseudoFile : public File
{
Shev::Array< char > & buf;
int length, pos;
PseudoFile ( PseudoFile & );
void operator = ( PseudoFile & );
public:
PseudoFile ( int n, Shev::Array< char > & p );
int read(void * p, const int size, const int count);
bool getc(void * p);
int write(const void * p, const int size, const int count);
bool putc(const void * p);
void flush() {}
bool seek_set(long offset);
bool seek_end(long offset);
bool seek_cur(long offset);
void rewind() { pos = 0; }
long tell() { return pos; }
};

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

bool read ( ReadFile & file, char & c, int & i );
bool read ( ReadFile & file, char & c, double & d );

с - это последний прочитанный символ.
Следующие функции записывают в файл числа в текстовом виде. Функция writeInt записывает в файл целое число. Параметр dig задаёт минимальное к-во выводимых десятичных разрядов ( 1 <= dig <= 10 ). В случае необходимости число будет дополнено слева нулями. Функция writeFlt записывает в файл число с плавающей точкой. Функция writeExp записывает в файл число с плавающей точкой в экспотенциальном виде. Параметр prec задаёт к-во дробных десятичных разрядов ( 0 <= prec <= 310 ). Если параметр plus = true, то для неотрицательных чисел пишется знак +, иначе - нет. Функция writeStr записывает в файл строку, которая заканчивается нулём.

bool writeInt ( WriteFile & file, int i, int dig = 1, bool plus = false );
bool writeFlt ( WriteFile & file, double d, int prec, bool plus = false );
bool writeExp ( WriteFile & file, double d, int prec, bool plus = false );
bool writeStr ( WriteFile & file, const char * str );
 

Your rating: None Average: 2.5 (2 votes)