C++通过例子理解封装、继承、多态(一)

要求

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();
}
相关推荐
唐诺33 分钟前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨2 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客2 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin2 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos3 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室4 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0014 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我584 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc4 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很4 小时前
C++ 集合 list 使用
c++