要求
cpp
class CFileBase
{
public:
CFileBase() {};
virtual ~CFileBase() {};
public:
virtual void Close() = 0;
virtual long ReadEx(void* pBuf, long iSize) = 0;
virtual long WriteEx(const void* pBuf, long iSize) = 0;
virtual long Seek(long offset, int whence) = 0;
virtual long GetLength() = 0;
virtual BOOL Truncate(long nFileSize) = 0; //截断文件, 不支持加长
virtual long GetPosition() = 0;
//virtual BOOL IsEOF() = 0;
//virtual int OutString(const char* fmt, ...) = 0; // 返回写入的字节数
public:
long SeekToBegin() { return Seek(0, SEEK_SET); }
long SeekToEnd() { return Seek(0, SEEK_END); }
BOOL Read(void* pBuf, long iSize) { return ReadEx(pBuf, iSize) == iSize; }
BOOL Write(const void* pBuf, long iSize) { return WriteEx(pBuf, iSize) == iSize; } // 要么全部写进去,要么都不写进去
char* GetLine(char* pBuf, int iBufSize); // 返回NULL表示文件结束
BOOL TruncateEnd() { return Truncate(GetPosition()); } // 从当前读写位置截断文件
};
//要求,根据上述封装的文件基类派生两种不同类型的文件操作:
//磁盘文件类和内存文件类,实现不同的接口
//内存文件由链表构成,长度可动态增加
要求使用该文件基类派生出两种文件操作类:磁盘文件和内存文件,其中内存文件由链表构成,长度可动态增加。我们先从较为简单的磁盘文件类开始。
磁盘文件类的编写
核心部分的函数都是虚函数,在派生类中定义,通过调用父类的public函数来使用这些虚函数,在子类继承后,这些虚函数被重写,在不同的子类里有不同的定义。因此,派生的磁盘文件类如下:
cpp
class CDiskFile : public CFileBase
{
public:
CDiskFile();
virtual ~CDiskFile();
public:
virtual void Close();
virtual long ReadEx(void* pBuf, long iSize);
virtual long WriteEx(const void* pBuf, long iSize);
virtual long Seek(long offset, int whence);
virtual long GetLength();
virtual long GetPosition();
virtual BOOL Truncate(long nFileSize); //截断文件, 不支持加长
public:
BOOL Open(const char* filename);
protected:
FILE* m_pFile;
};
首先由于磁盘文件多了一个open的操作,因此在子类中定义一个open函数,其余函数都是虚函数。定义一个protected类型的文件指针,用于指向open的文件,同时不允许类外访问该指针。函数内对该指针的操作在类外是不可见的,这也是c++类的封装特性。
open与close函数
open函数的作用是打开指定文件,初始化文件指针。close则是关闭该文件,并反初始化文件指针。
打开文件函数原型如下:
cpp
FILE *fopen(const char *filename, const char *mode);
filename即为文件名 例如 "./test.txt"
mode为文件的打开模式,可选项如下:
mode | 功能 |
---|---|
r | 以只读方式打开返回FILE*指针,文件必须存在,不存在时返回NULL |
r+ | 以读写方式打开,返回文件指针,文件必须存在,否则返回NULL |
rb+ | 以 读写的方式 打开 二进制文件,返回文件指针,文件必须存在,否则返回NULL |
rw+ | 以读写方式打开文本文件,返回文件指针,文件必须存在,否则返回NULL 每次写入时,都将文件清空,从头写。 |
w | 以写的方式打开文件 ,文件不存在则创建文件 每次写入时,都将文件清空,从头写。 |
w+ | 以读写的方式打开文件 ,文件不存在则创建文件 每次写入时,都将文件清空,从头写。 |
a | 追加写入方式打开文件,文件不存在则创建文件。 内容会被追加至文件末尾,不清空文件 |
a+ | 追加读写方式打开文件,文件不存在则创建文件。 内容会被追加至文件末尾,不清空文件 |
另外,b选项可以与其他选项任意组合,例如:ab,wb等。
关闭文件函数原型如下:
cpp
int fclose(FILE *stream)
stream:文件指针
如果关闭成功,返回0 否则返回-1
用以上两个函数写出磁盘文件类的打开/关闭文件函数
打开:
cpp
/*
打开文件
返回值:
ture:成功
false:失败
*/
BOOL CDiskFile::Open(const char* filename)
{
m_pFile = fopen(filename, "a+");
if (m_pFile == NULL)
{
return false;
}
return true;
}
关闭:
cpp
/* 关闭文件 */
void CDiskFile::Close()
{
fclose(m_pFile);
m_pFile = NULL;
}
关闭文件函数内除了使用fclose函数,还需要将文件指针置NULL
构造函数和析构函数
磁盘文件类的构造函数只需要初始化类中的文件指针:
cpp
CDiskFile::CDiskFile()
{
m_pFile = NULL;
}
析构函数用于关闭文件:
cpp
CDiskFile::~CDiskFile()
{
Close();
}
读写函数
cpp
/*
读取文件内容
pBuf:缓冲区指针
iSize:读取大小,单位字节
返回值:
实际读取字节
*/
long CDiskFile::ReadEx(void* pBuf, long iSize)
{
size_t irSize = 0;
irSize = fread(pBuf, sizeof(char), iSize, m_pFile);
return (long)irSize;
}
/*
写入内容到文件
pBuf:缓冲区指针
iSize:写入大小,单位字节
返回值
实际写入字节
*/
long CDiskFile::WriteEx(const void* pBuf, long iSize)
{
size_t iwSize;
iwSize = fwrite(pBuf, sizeof(char), iSize, m_pFile);
return (long)iwSize;
}
用到了fread和fwrite函数原型分别如下:
cpp
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
- ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
- size -- 这是要读取的每个元素的大小,以字节为单位。
- nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
cpp
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
- ptr -- 这是指向要被写入的元素数组的指针。
- size -- 这是要被写入的每个元素的大小,以字节为单位。
- nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
返回值均为实际写入数据单元个数,数据单元大小由size指定,这里都指定为char,即一个字节大小。
Seek函数
cpp
/*
设置文件内部指针位置
offset :偏移量
whence:取值为 SEEK_SET SEEK_END SEEK_CUR
返回值:
1 成功
0 失败
*/
long CDiskFile::Seek(long offset, int whence)
{
int iflag;
iflag = fseek(m_pFile, offset, whence);
if (iflag != 0)
{
return 0;
}
return 1;
}
用到了fseek函数原型如下;
cpp
int fseek(FILE *stream, long int offset, int whence)
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset -- 这是相对 whence 的偏移量,以字节为单位。
- whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量 | 含义 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针当前位置 |
SEEK_END | 文件的结尾 |
注意,offset可以为负数,表示往前偏移
GetPosition函数
cpp
/*
获取当前文件指针的位置
返回值:
从文件头到当前指针位置的偏移 单位为字节
*/
long CDiskFile::GetPosition()
{
long iPos;
iPos = ftell(m_pFile);
return iPos;
}
使用ftell函数即可获取文件指针的当前位置
cpp
long int ftell(FILE *stream)
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流
该函数返回位置标识符的当前值即距离文件开头多少字节。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。
GetLength函数
cpp
/*
获取文件长度
返回值:
文件长度,单位字节
*/
long CDiskFile::GetLength()
{
long iPreSize;
iPreSize = GetPosition(); //记录当前指针位置 单位为字节
long iSize;
SeekToEnd();
iSize = ftell(m_pFile);
Seek(iPreSize, SEEK_SET);//恢复
return iSize;
}
也是利用ftell函数,先记录当前指针,然后把指针移到文件最后,使用ftell即可获取文件长度。最后把指针恢复到当前位置。
截断函数
cpp
/*
截断文件
返回值:
1 成功
0 失败
*/
BOOL CDiskFile::Truncate(long nFileSize)
{
if(!Seek(nFileSize, SEEK_SET))
{
return 0;
}
//const char* pszEof = EOF;
if (!Write((char*)EOF, 1))
{
return 0;
}
return 1;
}
截断函数非常简单,在指定位置写入一个EOF即可。
磁盘文件类的完整代码
CDiskFile.h:
cpp
#ifndef _CDISKFILE_H_
#define _CDISKFILE_H_
#include "CFileBase.h"
class CDiskFile : public CFileBase
{
public:
CDiskFile();
virtual ~CDiskFile();
public:
virtual void Close();
virtual long ReadEx(void* pBuf, long iSize);
virtual long WriteEx(const void* pBuf, long iSize);
virtual long Seek(long offset, int whence);
virtual long GetLength();
virtual long GetPosition();
virtual BOOL Truncate(long nFileSize); //截断文件, 不支持加长
public:
BOOL Open(const char* filename);
protected:
FILE* m_pFile;
};
#endif // _CDISKFILE_H_
CDiskFile.cpp:
cpp
/* CDiskFile.cpp : Operation of DiskFile */
#define _CRT_SECURE_NO_WARNINGS
#include "CDiskFile.h"
/*
打开文件
返回值:
ture:成功
false:失败
*/
BOOL CDiskFile::Open(const char* filename)
{
m_pFile = fopen(filename, "a+");
if (m_pFile == NULL)
{
return false;
}
return true;
}
/* 关闭文件 */
void CDiskFile::Close()
{
fclose(m_pFile);
m_pFile = NULL;
}
/*
设置文件内部指针位置
offset :偏移量
whence:取值为 SEEK_SET SEEK_END SEEK_CUR
返回值:
1 成功
0 失败
*/
long CDiskFile::Seek(long offset, int whence)
{
int iflag;
iflag = fseek(m_pFile, offset, whence);
if (iflag != 0)
{
return 0;
}
return 1;
}
/*
读取文件内容
pBuf:缓冲区指针
iSize:读取大小,单位字节
返回值:
实际读取字节
*/
long CDiskFile::ReadEx(void* pBuf, long iSize)
{
size_t irSize = 0;
irSize = fread(pBuf, sizeof(char), iSize, m_pFile);
return (long)irSize;
}
/*
写入内容到文件
pBuf:缓冲区指针
iSize:写入大小,单位字节
返回值
实际写入字节
*/
long CDiskFile::WriteEx(const void* pBuf, long iSize)
{
size_t iwSize;
iwSize = fwrite(pBuf, sizeof(char), iSize, m_pFile);
return (long)iwSize;
}
/*
获取当前文件指针的位置
返回值:
从文件头到当前指针位置的偏移 单位为字节
*/
long CDiskFile::GetPosition()
{
long iPos;
iPos = ftell(m_pFile);
return iPos;
}
/*
获取文件长度
返回值:
文件长度,单位字节
*/
long CDiskFile::GetLength()
{
long iPreSize;
iPreSize = GetPosition(); //记录当前指针位置 单位为字节
long iSize;
SeekToEnd();
iSize = ftell(m_pFile);
Seek(iPreSize, SEEK_SET);//恢复
return iSize;
}
/*
截断文件
返回值:
1 成功
0 失败
*/
BOOL CDiskFile::Truncate(long nFileSize)
{
if(!Seek(nFileSize, SEEK_SET))
{
return 0;
}
//const char* pszEof = EOF;
if (!Write((char*)EOF, 1))
{
return 0;
}
return 1;
}
CDiskFile::CDiskFile()
{
m_pFile = NULL;
}
CDiskFile::~CDiskFile()
{
Close();
}