在 C++ 中使用随机文件结构
实现随机文件访问是一个挑战,但可以通过使用 C++ 文件处理函数来完成。 正如我们所知,C++ 不会对文件强加结构; 如果我们想对文件使用随机访问,我们必须使用文件和流库提供的工具以编程方式创建它们。 本文展示了我们如何通过使用 C++ 支持的简单文件结构并使用适当的代码示例来随机而一致地操作。
随机文件访问
可以创建应用程序以随机方式访问文件信息。 在顺序文件中,信息按时间顺序访问,而随机访问提供记录的即时获取,并且也可以按任何顺序或根本没有顺序。 在随机访问中,我们必须实现立即定位记录的能力。 这是任何交易系统的典型特征,无论是银行业务、机票预订等。
但是,请注意,C++ 不会对文件强加任何结构。 因此,想要实现随机访问文件的应用程序必须从头开始。 有很多技术可以实现这一点,最简单的一种是拥有固定长度的记录。 从字段长度结构计算记录的大小很容易。 我们可以通过从文件开头计算对象的大小来准确定位文件中的记录(见图 1)。
图 1 :顺序与随机的访问顺序。 [ sizeof(PhoneBook) = 36 字节 ]
通过实现随机访问机制,我们可以插入、更新和删除以前存储在文件中的数据。 我们不需要像数据库那样重写整个文件并对特定记录进行操作。 我们将实现以下操作:
- 通过 ID 查找存储在文件中的记录
- 添加新记录
- 修改现有记录
- 删除现有记录
- 以表格格式打印所有记录
一个简单的例子
该 电话簿 类表示存储在一个文件中的记录。 它是一个简单的类,没有任何关于文件操作的信息。 这意味着这个类的对象不知道它在二进制文件中的持久性。
CRUD 操作与 一起实现 main 函数 ,代表了应用程序所代表的随机访问机制的关键。 下面的程序代表了这个想法的最小实现,并且没有经过优化和精心设计,以使代码大部分是不言自明的。
#ifndef PHONEBOOK_H
#define PHONEBOOK_H
#include <字符串>
班级电话簿
{
民众:
PhoneBook(int=0,const std::string & = "", const
std::string & = "", const std::string & = "");
void setId(int);
int getId() const;
void setFirstName(const std::string &);
std::string getFirstName() const;
void setLastName(const std::string &);
std::string getLastName() const;
void setPhone(const std::string &);
std::string getPhone() const;
std::string toString() const;
private:
int id;
char firstName[10];
char lastName[10];
char phone[10];
};
#endif // PHONEBOOK_H
#include "phonebook.h"
using namespace std;
PhoneBook::PhoneBook(int _id,
const std::string & _firstName,
const std::string & _lastName,
const std::string & _phone)
:id(_id){
setFirstName(_firstName);
setLastName(_lastName);
setPhone(_phone);
}
void PhoneBook::setId(int _id){
id = _id;
}
int PhoneBook::getId() const{
返回标识;
}
void PhoneBook::setFirstName(const string & _firstName){
size_t len = _firstName.size();
len = (len <10? len: 9);
_firstName.copy(firstName, len);
名字 [len] = '\0';
}
字符串 PhoneBook::getFirstName() const
{
返回名字;
}
void PhoneBook::setLastName(const string & _lastName)
{
size_t len = _lastName.size();
len = (len <10? len: 9);
_lastName.copy (lastName, len);
姓 [len] = '\0';
}
字符串电话簿::getLastName() const {
返回姓氏;
}
void PhoneBook::setPhone(const string & _phone)
{
size_t len = _phone.size();
len = (len <10? len: 9);
_phone.copy( 电话, len );
电话 [len] = '\ 0';
}
字符串电话簿::getPhone() const {
回电话;
}
字符串 PhoneBook::toString() const
{
返回"\nID:"+to_string(id)+
" [ "+字符串(名字)+
" , "+字符串(姓氏)+
" , "+字符串(电话)+" ]";
}
复制代码
以下代码实现了随机访问机制。 它实现了类似CRUD的操作,比如新增一条 PhoneBook 记录,更新已有记录的电话号码,删除一条记录。
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include "电话簿.h"
使用命名空间标准;
枚举选项{ADDNEW = 1,更新,删除,显示,停止};
const string fileName = "phonebook.dat";
PhoneBook findById(int id){
ifstream file(fileName, ios::binary);
if(!file){
cerr<<"Error. Cannot open file."<<endl;
exit(EXIT_FAILURE);
}
PhoneBook pb;
while(file.read(reinterpret_cast<char *>(&pb),
大小(电话簿))){
if(pb.getId()==id)
休息;
pb.setId(0);
}
文件。关闭();
返回铅;
}
无效_创建(){
ofstream 文件(文件名,ios::out|ios::app|ios::binary);
如果(!文件){
cerr<<"错误,无法打开文件。"<<endl;
退出(EXIT_FAILURE);
}
整数 ID = 0;
字符串 fn,ln,ph;
而(1){
cout<<"请输入 ID(1-100)。要停止的任何其他数字:";
cin>>id;
if(id<1 || id>100) 中断;
电话簿铅;
pb =findById(id);
if(pb.getId()==id){
cout<<"记录存在:"<<pb.toString()<<endl;
继续;
}
cout<<"请输入名字、姓氏、电话\n?";
cin>>setw(10)>>fn>>setw(10)>>ln>>setw(10)>>ph;
pb.setId(id);
pb.setFirstName(fn);
pb.setLastName(ln);
pb.setPhone(ph);
file.write(reinterpret_cast<const char *>(&pb),
大小(电话簿));
}
文件。关闭();
}
无效_更新(){
内部标识;
cout<<"输入要修改的ID:";
cin>>id;
fstream 文件(文件名,ios::in|ios::out|ios::binary);
如果(!文件){
cerr<<"错误,无法打开文件。"<<endl;
退出(EXIT_FAILURE);
}
电话簿铅;
字符串 phno;
布尔标志 = 假;
while(file.read(reinterpret_cast<char *>(&pb),
大小(电话簿))){
if(pb.getId()==id){
cout<<pb.toString()<<endl;
cout<<"请输入新的电话号码:";
cin>>phno;
pb.setPhone(phno);
long pos = -1*sizeof (PhoneBook);
file.seekp(pos, ios::cur);
file.write(reinterpret_cast<const char *>(&pb),
大小(电话簿));
标志 = 真;
休息;
}
}
如果(标志==假)
cout<<"没有找到修改记录。"<<endl;
别的
cout<<"记录修改成功。"<<endl;
文件。关闭();
}
无效_删除(){
内部标识;
cout<<"请输入现有ID:";
cin>>id;
PhoneBook pb;
fstream file(fileName, ios::in|ios::out|ios::binary);
if(!file){
cerr<<"Error. Cannot open file."<<endl;
exit(EXIT_FAILURE);
}
bool flag=false;
while(file.read(reinterpret_cast<char *>(&pb),
sizeof (PhoneBook))){
if(pb.getId()==id){
pb.setId(0);
pb.setFirstName("");
pb.setLastName("");
pb.setPhone("");
long pos = -1*sizeof (PhoneBook);
file.seekp(pos, ios::cur);
file.write(reinterpret_cast<const char *>(&pb),
sizeof (PhoneBook));
flag=true;
break;
}
}
if(flag==true)
cout<<"Record successfully deleted."<<endl;
else
cout<<"没有找到记录。"<<endl;
文件。关闭();
}
无效_打印(){
整数计数=0;
ifstream 文件(文件名,ios::binary);
如果(!文件){
cerr<<"错误,无法打开文件。"<<endl;
退出(EXIT_FAILURE);
}
cout<<"--------------------------------"<<endl;
cout<<left<<setw(3)<<"ID"
<<setw(10)<<"FName"
<<setw(10)<<"LName"
<<setw(10)<<"电话"<<endl;
cout<<"--------------------------------"<<endl;
电话簿铅;
while(file.read(reinterpret_cast<char *>(&pb),sizeof
(电话簿))){
if(pb.getId()!=0){
计数++;
cout<<left<<setw(3)<<pb.getId()
<<setw(10)<<pb.getFirstName()
<<setw(10)<<pb.getLastName()
<<setw(10)<<pb.getPhone()<<endl;
}
}
cout<<"--------------------------------"<<endl;
cout<<"总计:"<<count<<" 条记录"<<endl;
cout<<"--------------------------------"<<endl;
文件。关闭();
}
int main()
{
而(1){
cout << "\n请输入您的选择" << endl
<< "1 - 添加新记录" << endl
<< "2 - 更新现有记录" << endl
<< "3 - 删除一条记录" << endl
<< "4 - 打印所有记录" << endl
<< "5 - 退出\n?";
国际选择;
cin >> 选择;
开关(选择){
案例添加:_create(); 休息;
案例更新:_update(); 休息;
案例删除:_delete(); 休息;
案例展示:_print(); 休息;
案例停止:退出(EXIT_SUCCESS);
默认值:cerr << "无效选项!再试一次。"
<<结束; 休息;
}
}
返回0;
}
复制代码
文件处理函数
的 ostream的写 功能用于写入的字节数固定为指定的流。 在这里,流与文件相关联。 放置文件位置指针决定写入数据的位置。 类似地, istream read 函数用于从指定的流中读取固定数量的字节,在这种情况下是一个文件。 获取文件位置指针确定要读取的文件中的位置。
以二进制模式打开文件的想法是它非常适合写入固定长度的记录。
请注意,不是使用插入运算符来存储 的整数 id, PhoneBook 如下所示:
文件<<id;
复制代码
我们已将数字转换为字符,因为 4 字节整数可能包含多达 11 位数字(10 位数字 + 一个符号字节)。 但是,正如我们所写:
file.write(reinterpret_cast<const char *>(&id),
大小(id));
复制代码
它存储整数的二进制版本,即 4 个字节(在将 4 个字节表示为整数的机器上)。 内存中的对象被当作*const char **; 换句话说,指向一个字节的指针。 结果,write 函数输出许多字节。 以同样的方式和同样的原因,我们将 的对象转换为 PhoneBook 类 具有读取和写入操作的字节流。
电话簿铅;
...
file.read(reinterpret_cast<char *>(&pb), sizeof
(电话簿));
file.write(reinterpret_cast<const char *>(&pb), sizeof
(电话簿));
复制代码
要输出对象,我们必须将它们转换为 const char * 类型; 否则,compile 将不会编译对 调用 write 函数的 。 在 C++ 中, reinterpret_cast 运算符用于将一种类型的 转换为 不相关的 指针强制 指针类型。 此运算符在编译时执行并请求编译器将操作数重新解释为尖括号中声明的目标类型。 在我们的示例中,我们使用 reinterpret_cast 将指向 的指针转换 PhoneBook 为 *const char **。 将对象转换为要在文件中输出的字节集合。
请注意,在处理随机访问文件的程序中,读/写操作不会将单个字段读/写到文件中; 相反,它们将类的对象作为进出文件的字节序列读/写。
文件操作函数,例如 seekg 和 seekp ,分别用于定位获取文件指针并将文件指针放置在文件内的战略位置。 例如, seekg 设置要从输入流中提取的下一个字符的位置,而 seekp 设置要插入到输出流中的下一个字符的位置。
请参阅 C++ 文档 有关许多此类文件操作函数的详细信息, 。
结论
我们可以通过多种方式处理文件。 这是操作持久数据的技术之一。 通过随机访问文件,我们可以立即检索和操作固定长度的记录。 我们可以像对待数据库一样对待平面文件模型,尽管它不是一个高效的模型。