C++学习-入门到精通【12】文件处理
C++学习-入门到精通【12】文件处理
目录
- C++学习-入门到精通【12】文件处理
- 一、文件和流
- 二、创建顺序文件
- 三、从顺序文件读取数据
- 文件定位指针
- 对之前的程序进行修改:贷款查询程序
- 四、更新顺序文件
- 五、随机存取文件
- 1.创建随机存取文件
- 2.修改程序:贷款处理程序
- 六、实例研究:事务处理程序
- 七、对象序列化
一、文件和流
在内存中数据的存储是临时的。文件是用使数据持久化的,即永久保存数据。计算机将文件存储在辅助存储设备中,比如,磁盘、CD等等。
C++将每个文件看成字符序列。每个文件都以一个文件结束符(end-of-file marker)或以存储在操作系统维护、管理的数据结构中的一个特定字节数作为结尾。
当打开一个文件时,一个对象被创建,并且将一个流关联到这个对象上。与这些对象关联的流提供了程序和特定文件或设备之间的通信通道。比如,cin对象允许程序从键盘或其他设备输入数据,cout对象允许程序将数据输出到屏幕或其他设备。
文件处理模板类
为了在C++中执行文件处理,必须包含头文件<iostream>
和<fstream>
。头文件<fstream>
包含了多种流类模板的定义:basic_ifstream
、basic_ofstream
和basic_fstream
。
它们的UML类图如下:
二、创建顺序文件
C++没有在文件上强加任何结构。因此,像“记录”这样的概念在C++文件中是不存在的。所以程序员必须自己设计文件结构来满足应用程序的需要。
下面我们会展示如何一个在文件上强加一个简单的记录结构。
#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
using namespace std;int main()
{// 创建一个名为 client.txt 的文件,并以输出的方式打开它(“出”相对于内存,所以是将数据写进文件)ofstream outClientFile("client.txt", ios::out);if (!outClientFile){cerr << "File could not be opened" << endl;exit(EXIT_FAILURE);}cout << "Enter the account, name and balance." << endl<< "Enter end-of-file to end input.\n? ";int account;string name;double balance;while (cin >> account >> name >> balance){outClientFile << account << ' ' << name << ' ' << balance << endl;cout << "? ";}outClientFile.close(); // 关闭文件
}
运行结果:
上面的程序的目的是将数据写入一个文件,所以需要通过创建ofstream
对象用来打开文件进行输出。该对象的构造函数有两个参数——文件名
和文件打开模式
。对于一个ofstream
对象,文件打开模式可以是ios::out
默认——向一个文件输出数据,或者是ios::app
——将数据输出到文件的结尾(不会改变文件原来的内容)。
使用ios::out
模式打开文件,会出现两种情况,文件本来就存在,此时会将原文件截顶(即所有存在于文件中的内容都将被丢弃)。当成这个文件原本就不存在,像是创建了一个新文件;
如果文件原来不存在,那么就创建一个新文件。
下面给出文件的打开模式:
模式 | 描述 |
---|---|
ios::app | 将所有输出数据添加到文件的结尾(只能将数据写到结尾) |
ios::ate | 将一个文件打开作为输出文件,并移动到文件尾。可以在文件的任何位置写数据。(只是在打开时,将文件指针移到文件尾,之后可以改变其位置) |
ios::in | 打开一个文件作为输入文件 |
ios::out | 打开一个文件作为输出文件 |
ios::trunc | 丢弃文件的内容 |
ios::binary | 打开一个文件进行二进制(非文本方式),不包含输入或输出权限 |
上面程序中使用语句ofstream outClientFile("client.txt", ios::out);
创建了一个ofstream对象,与打开用来输出的文件client.txt
相关联。这建立了一个到文件的“通信通道”。
通过成员函数open打开一个文件
一个ofstream对象可以在没有打开特定文件的情况下被创建,文件可以在之后关联到这个对象。例如,下面的代码,先是创建了一个ofstream的对象,再使用open成员函数打开一个文件并将它关联到一个已存在的ofstream对象上。
ofstream outClientFile;
outClientFile.open("client.txt", ios::out);
使用该成员函数时,文件的打开模式作用相同。
测试一个文件是否被成功打开
在创建了一个ostream对象后尝试打开它时,程序会测试打开操作是否成功。使用重载的ios操纵符成员函数operator!
来判定打开操作是否成功。如果在打开操作中,failbit
或badbit
位中的任何一个被设置了,则该条件返回true。
导致错误发生的原因可能是:尝试打开并读取一个不存在的文件,在没有权限的情况下对文件进行读写操作,或是在打开文件并写入时没有磁盘空间。
如果文件打开失败,输出一条错误信息,并调用函数exit
来结束程序。该函数的参数是返回给程序调用环境的。有两种EXIT_SUCCESS
和EXIT_FAILURE
。分别表示正常退出和因错误退出。
将istream对象的引用作为判断条件时,会隐式地调用重载的类型转换函数operator void*
将流对象转换成一个指针。输入失败时,会产生一个空指针,之后C++会将这个空指针转换成bool值false,将一个非空指针转换成bool值true。
关闭文件
一旦用户输入文件结束指示符,此时显式的调用成员函数close来关闭ofstream对象。在main函数结束时,也会隐式的调用ofstream对象的析构函数,以此来关闭文件。
但是建议在不使用文件的地方,立刻关闭文件。
三、从顺序文件读取数据
本节将讨论如何顺序地从文件读取数据。直接看下面的代码。
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <iomanip>
using namespace std;void outputLine(int, const string&, double);int main()
{// 创建一个ifstream对象用来打开一个文件进行输入(进行内存叫做“入”)ifstream inClientFile("client.txt", ios::in);// 文件打开失败,if (!inClientFile){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE); // 因失败退出程序}int account;string name;double balance;cout << left << setw(10) << "Account" << setw(13) << "Name"<< "Balance" << endl << fixed << showpoint;// 从文件中顺序读取数据while (inClientFile >> account >> name >> balance){// 输出从文件中读取的一行数据outputLine(account, name, balance);}// 显式的关闭文件inClientFile.close();
}void outputLine(int account, const string& name, double balance)
{cout << left << setw(10) << account << setw(13) << name<< setw(7) << setprecision(2) << right << balance << endl;
}
运行结果:
打开一个文件用于输入
ifstream
类对象的默认打开模式为输入模式。所以可以使用下面的语句打开文件进行输入
ifstream("client.txt");
注意,如果文件的内容不应该被修改,则应该只用输入模式(ios::in)打开文件,这样可以避免意外的修改文件内容,同样这也遵循了最小特权原则。
文件定位指针
为了顺序地从文件中取得数据,程序一般从文件的起始位置开始连续地读取所有数据,直到找到需要的数据为止。istream
和ostream
都提供了成员函数来重定位文件定位指针
(文件下一个被读取或写入的字节号)。在istream
中,使用的成员函数为seekg
(“seek get”);在ostream
中,使用的成员函数为seekp
(“seek put”)。每个istream
对象都有一个"get"指针来指出文件中下一个输入的字节号,每个ostream
对象都有一个"put"指针来指出文件中下一个输出的字节号。
inClientFile.seekg(0);
将与inClientFile关联的文件定位指针重定位于起始位置(位置0)。
注意上面的第二个版本的必须使用作用域分辨运算符指定它的作用域,例如:ios::cur
。
seekp的用法相同:
如果想要知道当前的指针位置,可以使用成员函数tellg
和tellp
。例如:
long location = fileObject.tellg()
。这条语句将"get"文件定位指针的值赋给了变量location。
对之前的程序进行修改:贷款查询程序
下面的程序可以显示相关贷款信息,包括余额为0的客户(不欠公司任何钱的客户)、贷款余额为负的客户(向公司提供贷款的客户)和贷款余额为正的客户(使用公司提供的服务或货物而有欠款的客户)。
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include <iomanip>
using namespace std;// 创建一个枚举类型用于表示不同的账户类型
enum RequestType {END, ZERO_BALANCE, CREDIT_BALANCE, DEBIT_BALANCE
};// 让用户选择输出的需求
int getRequest();bool shouldDisplay(int , double);void outputLine(int, const string&, double);int main()
{ifstream inClientFile("client.txt", ios::in);if (!inClientFile){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE);}int account;string name;double balance;int request = 0;do {request = getRequest();switch (request){case ZERO_BALANCE:cout << "\nAccounts with zero balances:\n";break;case CREDIT_BALANCE:cout << "\nAccounts with credit balances:\n";break;case DEBIT_BALANCE:cout << "\nAccounts with debit balances:\n";break;case END:break;default: // 在getRequest函数中处理了异常情况,所以这里不会存在默认情况break;}if (request == END)break;// 直到读到文件末尾,读完整个文件while (inClientFile >> account >> name >> balance){// 判断当前读取的数据是否是需要显示的数据if (shouldDisplay(request, balance))outputLine(account, name, balance);}cout << "\n";// 重置该流的错误状态,为下一次使用作准备inClientFile.clear();// 将文件定位指针重置到起始位置inClientFile.seekg(0);} while(1);cout << "\nEnd of run.";
}int getRequest()
{int request;cout << "Enter request" << endl<< " 1 - List accounts with zero balances." << endl<< " 2 - List accounts with credit balances." << endl<< " 3 - List accounts with debit balances." << endl<< " 0 - End of run." << fixed << showpoint;do {cout << "\n?";cin >> request;}while(request < END || request > DEBIT_BALANCE);return request;
}bool shouldDisplay(int type, double balance)
{if (type == ZERO_BALANCE && balance == 0){return true;}if (type == CREDIT_BALANCE && balance < 0){return true;}if (type == DEBIT_BALANCE && balance > 0){return true;}return false;
}void outputLine(int account, const string& name, double balance)
{cout << left << setw(10) << account << setw(13) << name<< setw(7) << right << setprecision(2) << balance << "\n";
}
运行结果:
四、更新顺序文件
对格式化并写入顺序文件的数据进行修改,可能会有破坏其他数据的风险。例如,根据名字Lois改成Quagmire,则原有的名字在没有破坏文件的情况下是不可能被重写的。
Lois的记录被写到文件中,格式如下:
3 Lois 345.67
,现在要将这条记录的名字改成Quagmire,就需要在文件相同的位置用更长的名字重写该记录,则新记录会变成:3 Quagmire 345.67
。新的记录比原记录多了4个字符,此时这条新记录就会覆盖掉下一条记录的一部分。
问题在于,在使用流插入运算符<<和流提取运算符>>的格式化输入/输出模式时,字段和记录在大小上可以变化的。这些值在内存内部存储空间大小相同,但是在格式化输出为文本时,却变成了大小不同的字段。因此,格式化的输入/输出模式通常不会用来原地更新记录
。
像这样的更新操作,通常是使用复制的方法完成的。比如,为了实现前述的名字更改,会将在3 Lois 345.67
之前的记录复制到一个新文件中,然后将更新的记录也复制到这个新文件中,最后将该记录之后的记录也复制过来,这种方法要求在更新一条记录时,也要对文件中的每条记录进行处理。
如果文件中的多条记录都需要进行更新,那么这种方法是可接受的,否则,额外开销过大。
五、随机存取文件
顺序文件不适合即时存取应用程序,这些应用程序要求必须立即定位某个特定的记录。即时存取可以通过随机存取文件
实现。
C++没有将结构强加到文件中,所以应用程序想要使用随机存取文件,就必须自己创建。
可以采用多种技术来创建随机存取文件。而最容易的方法就是要求文件中的所有记录的长度相同
。通过运用相同大小的定长记录,程序只需要简单的计算,就可以找出任何一条记录到文件起始位置的精确位置。
使用这样的定长的结构,可以在不破坏文件中其他数据的情况下将数据插入到一个随机存取文件中。之前存在的数据也可以在不重写整个文件的情况下被更新或删除。
1.创建随机存取文件
ostream
的成员函数write从内存中的一个指定位置输出固定数目的字节到指定的流。当流被关联到文件时,函数write在文件中从“put”文件定位指针指定的位置开始写入数据。
istream
的成员函数read则将固定数目的字节从一个指定的流输入到内存中指定地址开始的一部分空间。如果流被关联到一个文件,那么函数read在文件中从由“get”文件定位指针所指定的位置读取字节数据。
利用ostream的成员函数write写入字节数据
还记得我们上一章中提到的ostream类的成员函数write吗,我们在将一个数据写入文件中时,除了使用流插入运算符之外,还可以使用这个成员函数来实现。例如:将一个整数写入到文件中
outFile.write(reinterpret_cast<const char*>(&number), sizeof(number));
注意成员函数write的第一个参数必须是一个声明为const的char指针,&number是一个int类型的指针,这里使用reinterpret_cast
直接将强制转换成char类型的指针。
reinterpret_cast
这个运算符,可以强制的将一个指针类型转换成另一个没有任何关联的指针类型。相当于是重新解释
,在编译过程中进行。
注意,使用 reinterpret_cast 这个运算符是很容易导致严重的执行时错误的,因为进行指针类型转换时,编译器不会进行任何检查。
且reinterpret_cast
这个运算符与编译器相关,所以可移植性较差。
2.修改程序:贷款处理程序
需求如下:
ClientData.h
#pragma once
#include <string>class ClientData
{
public:ClientData(int = 0, const std::string& = "", const std::string& = "", double = 0.0);void setAccountNumber(int);int getAccountNumber() const;void setLastName(const std::string&);std::string getLastName() const;void setFirstName(const std::string&);std::string getFirstName() const;void setBalance(double);double getBalance() const;
private:int accountNumber;char lastName[15];char firstName[10];double balance;
};
ClientData.cpp
#include "ClientData.h"
using namespace std;ClientData::ClientData(int accountNumberValue, const string& lastname,const string& firstname, double balanceValue): accountNumber(accountNumberValue), balance(balanceValue)
{setLastName(lastname);setFirstName(firstname);
}void ClientData::setAccountNumber(int accountNumberValue)
{accountNumber = accountNumberValue;
}
int ClientData::getAccountNumber() const
{return accountNumber;
}void ClientData::setLastName(const string& lastname)
{int length = lastname.length();// 一个C风格的字符串,最后数组最后一个元素必须放置\0length = (length < 15 ? length : 14);lastname.copy(lastName, length);lastName[length] = '\0'; // 在字符串最后添加一个\0作为字符串结束标志
}
string ClientData::getLastName() const
{return lastName;
}void ClientData::setFirstName(const string& firstname)
{int length = firstname.length();// 一个C风格的字符串,最后数组最后一个元素必须放置\0length = (length < 10 ? length : 9);firstname.copy(firstName, length);firstName[length] = '\0'; // 在字符串最后添加一个\0作为字符串结束标志
}
string ClientData::getFirstName() const
{return firstName;
}void ClientData::setBalance(double balanceValue)
{balance = balanceValue;
}
double ClientData::getBalance() const
{return balance;
}
类string的对象是没有统一长度的,因为它们的内存动态分配的以适应不同长度的字符串。这个程序维护的是固定长度的记录,所以类ClientData
中客户的姓和名使用的是定长的char数组存储的。
在这个程序执行过程中我们使用二进制的格式ios::binary
打开文件。注意必须使二进制模式打开文件,这是因为文件中是没有结构的,我们是通过在内存中的设计结构,然后将存储为这种结构的数据存储到文件中,下次读取时,这些数据仍能还原成这种结构。所以这里的定长
是在内存中定长,而内存中的数据是以二进制形式存储的,所以我们必须使用二进制模式打开文件。
测试代码,test.cpp
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include "ClientData.h"
using namespace std;int main()
{ofstream outCredit("credit.dat", ios::binary);if (!outCredit){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE);}ClientData blankClient; // 使用默认构造函数创建ClientData对象,每个数据成员都被初始化成0// 在文件中保存100条空的记录for (int i = 0; i < 100; i++){outCredit.write(reinterpret_cast<const char*>(&blankClient), sizeof(ClientData));}
}
向随机存取文件写入数据
下面我们向文件credit.dat
写入数据,并使用fstream的函数seekp
和write
的组合来将数据存储到文件的精确位置。
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "ClientData.h"
using namespace std;int main()
{int accountNumber;string lastName;string firstName;double balance;// fstream的对象即可以输入也可输出fstream outCredit("credit.dat", ios::in|ios::out|ios::binary);if (!outCredit){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE);}cout << "Enter account number (1 to 100, 0 to end input)\n? ";ClientData client;cin >> accountNumber;while (accountNumber > 0 && accountNumber <= 100){cout << "Enter lastname, firstname, balance\n? ";cin >> lastName >> firstName >> balance;client.setAccountNumber(accountNumber);client.setLastName(lastName);client.setFirstName(firstName);client.setBalance(balance);// 将put文件定位指针设置到文件中的第 帐号 - 1 个记录的位置outCredit.seekp((client.getAccountNumber() - 1) * sizeof(ClientData));// 将数据写入文件outCredit.write(reinterpret_cast<const char*>(&client), sizeof(ClientData));cout << "Enter account number\n? ";cin >> accountNumber;}
}
运行结果:
此时如果你在图形界面直接打开credit.dat
这个文件,里面会全部都是乱码,因为它们是以二进制格式存储的。
只能通过二进制模式读取这个文件,才能得到其他存储的内容(当然如果你能直接将二进制序列以对应的编码方式直接转换成对应的文本信息,那么你也能得到文件中存储的内容)。
上面代码中,我们使用了一个fstream对象来打开文件credit.dat。通过文件打开模式 ios::in、ios::out、ios::binary 的组合,可以按照二进制模式打开,从而进行输入和输出。多种文件打开模式可以通过使用按位或运算符(|)将单独的打开模式组合起来。
提示
使用ios::in|ios::out
表示以读写模式打开文件,这种方式打开不会擦除文件的原内容。
擦除文件内容的真正原因是使用了ios::trunc
,在打开文件时,单独使用ios::out
会隐式的触发ios::trunc
模式。
从随机存取文件顺序读取数据
现在我们来试试,如何从上面创建的随机存取文件中读取数据。
测试程序,test.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <iomanip>
#include "ClientData.h"
using namespace std;// 输出文件中的一条记录
void outputLine(ostream&, const ClientData&);int main()
{ifstream inCredit("credit.dat", ios::in|ios::binary);if (!inCredit){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE);}// 输出表头cout << left << setw(10) << "Account" << setw(16) << "LastName"<< setw(11) << "FirstName" << setw(10) << right << "Balance" << endl;ClientData client;do {// 注意read函数会修改数据,所以将ClientData的指针转换成char*,而不是const char*inCredit.read(reinterpret_cast<char*>(&client), sizeof(ClientData));if(client.getAccountNumber() != 0)outputLine(cout, client);}while (inCredit && !inCredit.eof());// 当读取到文件末尾或文件读取失败时结束循环
}void outputLine(ostream& output, const ClientData& record)
{output << left << setw(10) << record.getAccountNumber()<< setw(16) << record.getLastName()<< setw(11) << record.getFirstName()<< setw(10) << setprecision(2) << right << fixed<< showpoint << record.getBalance() << endl;
}
运行结果:
六、实例研究:事务处理程序
现在给出一个通过随机存取文件来实现“即时”存取处理的事务处理程序。这个程序维护了一个银行的账户信息。程序可以更新现有的账户、加入新的账户、删除账户,并在文本文件中存储一个格式化的所有当前账户列表。
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include "ClientData.h"
using namespace std;int enterChoice();
void createTextFile(fstream&);
void updateRecord(fstream&);
void newRecord(fstream&);
void deleteRecord(fstream&);
void outputLine(ostream&, const ClientData&);
int getAccount(const char* const);enum Choices {END, PRINT, UPDATE, NEW, DELETE
};int main()
{fstream inOutCredit("credit.dat", ios::in|ios::out|ios::binary);if (!inOutCredit){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE);}int choice;while ((choice = enterChoice()) != END){switch (choice){case PRINT:createTextFile(inOutCredit);break;case UPDATE:updateRecord(inOutCredit);break;case NEW:newRecord(inOutCredit);break;case DELETE:deleteRecord(inOutCredit);break;default:cerr << "Incorrect choice." << endl;break;}inOutCredit.clear(); // 重置错误状态和文件定位指针的位置}inOutCredit.close();
}int enterChoice()
{cout << "\nEnter your choice" << endl<< "1 - store a formatted text file of accounts" << endl<< " called \"print.txt\" for printing" << endl<< "2 - update an account" << endl<< "3 - add a new account" << endl<< "4 - delete an account" << endl<< "0 - end program\n? ";int menuChoice;cin >> menuChoice;return menuChoice;
}// 从二进制文件中将数据写入格式化保存数据的文件
void createTextFile(fstream& readFromFile)
{ofstream outPrintFile("print.txt", ios::out);if (!outPrintFile){cerr << "File could not be opened." << endl;exit(EXIT_FAILURE);}outPrintFile << left << setw(10) << "Account" << setw(16)<< "LastName" << setw(11) << "FirstName"<< right << setw(10) << "Balance" << endl;readFromFile.seekg(0);ClientData client;readFromFile.read(reinterpret_cast<char*>(&client), sizeof(ClientData));while (!readFromFile.eof()){if (client.getAccountNumber() != 0) // 跳过空记录{outputLine(outPrintFile, client);}readFromFile.read(reinterpret_cast<char*>(&client), sizeof(ClientData));}
}void updateRecord(fstream& updateFile)
{// 获取要更新的记录的账号int accountNumber = getAccount("Enter account to update");updateFile.seekg((accountNumber - 1) * sizeof(ClientData));ClientData client;updateFile.read(reinterpret_cast<char*>(&client), sizeof(ClientData));if (client.getAccountNumber() != 0){outputLine(cout, client); // 显示要更新的信息cout << "\nEnter charge(+) or payment(-): ";double transaction; // 存款或取款cin >> transaction;double oldBalance = client.getBalance();client.setBalance(oldBalance + transaction);outputLine(cout, client); // 显示更新之后的记录updateFile.seekp((accountNumber - 1) * sizeof(ClientData));// 将更新的内容写入文本文件updateFile.write(reinterpret_cast<const char*>(&client), sizeof(ClientData));}else{cerr << "Account #" << accountNumber << " has no information." << endl;}
}void newRecord(fstream& insertInFile)
{int accountNumber = getAccount("Enter new account number");// 找到对应的记录insertInFile.seekg((accountNumber - 1) * sizeof(ClientData));ClientData client;// 将数据提取到内存,保存在变量client中insertInFile.read(reinterpret_cast<char*>(&client), sizeof(ClientData));// 插入的位置,没有记录if (client.getAccountNumber() == 0){string lastName;string firstName;double balance;cout << "Enter lastname, firstname and balance\n? ";// 注意在输入操作中,使用setw操纵符设置提取的字符数,// 这里面会包含一个终止符\0的位置,如果设置提取 n 个字符,实际上最多提取 n - 1 个字符cin >> setw(15) >> lastName; cin >> setw(10) >> firstName;cin >> balance;client.setAccountNumber(accountNumber);client.setLastName(lastName);client.setFirstName(firstName);client.setBalance(balance);// 将数据写入文本文件insertInFile.seekp((accountNumber - 1) * sizeof(ClientData));insertInFile.write(reinterpret_cast<const char*>(&client), sizeof(ClientData));}else{cerr << "Account #" << accountNumber << " already contains information." << endl;}
}void deleteRecord(fstream& deleteFromFile)
{int accountNumber = getAccount("Enter account to delete");// 找到待删除的记录deleteFromFile.seekg((accountNumber - 1) * sizeof(ClientData));ClientData client;deleteFromFile.read(reinterpret_cast<char*>(&client), sizeof(ClientData));// 要删除记录不是空记录if (client.getAccountNumber() != 0){ClientData blankClient; // 创建一个空记录deleteFromFile.seekp((accountNumber - 1) * sizeof(ClientData));deleteFromFile.write(reinterpret_cast<const char*>(&blankClient), sizeof(ClientData));cout << "Account #" << accountNumber << " deleted.\n";}else{cerr << "Account #" << accountNumber << " is empty.\n";}
}void outputLine(ostream& output, const ClientData& record)
{output << left << setw(10) << record.getAccountNumber()<< setw(16) << record.getLastName()<< setw(11) << record.getFirstName()<< setw(10) << fixed << setprecision(2) << right<< showpoint << record.getBalance() << endl;
}int getAccount(const char* const prompt)
{int accountNumber;do{cout << prompt << "(1 - 100): ";cin >> accountNumber;} while(accountNumber < 1 || accountNumber > 100);return accountNumber;
}
运行结果:
选项1:
调用createTextFile函数,
将credit.dat中的数据读取出来,并将它们写入到一个格式化的文本文件中。
print.txt文件的内容如下:
选项2:
调用updateRecord函数来更新账户,该函数只能更新已存在的记录,所以该函数首先判断指定的记录是否为空。不为空继续进行下一步,显示原来的记录,之后进行余额的修改操作。
选项3:
调用newRecod函数,创建一个新账户,只能在一个空记录上创建一个账户,所以需要先判断指定的记录是否为空,为空才可以继续下一步。
选项4:
调用deleteRecord函数来删除一个账户,只有账户存在才能进行删除操作。
经过上述一系列操作之后,文件(print.txt)内容如下:
七、对象序列化
大家经过了这一章的学习,应该都知道了文件中只存储字节数据,并没有其他的信息(比如,类型信息)。如果将不同对象的数据保存到同一个文件中,要将它们再次读入内存中时,程序如何区分它们谁是谁(它们的类型是什么)。而对象是一般是没有类型域的(在对象中标记该对象属于哪一类的信息,因为多态性(都使用基类指针/引用来操作,由编译器运行时决定使用什么函数)所以一般没有)。
一些编程语言使用的方法是对象序列化
。所谓序列化的对象,是指一个字节序列表示的对象,这个序列不仅包含这个对象的数据,它也包含有关对象类型和对象中存储的数据的类型信息。当序列化的对象被写入文件后,它可以从文件中读出并反序列化,即类型信息可以被用来在内存中重新创建这个对象。
相关文章:

C++学习-入门到精通【12】文件处理
C学习-入门到精通【12】文件处理 目录 C学习-入门到精通【12】文件处理一、文件和流二、创建顺序文件三、从顺序文件读取数据文件定位指针对之前的程序进行修改:贷款查询程序 四、更新顺序文件五、随机存取文件1.创建随机存取文件2.修改程序:贷款处理程序…...
第十一篇:MySQL 在分布式系统中的一致性保障与中间件实践
随着微服务和分布式架构的发展,单点数据库早已无法满足系统的横向扩展需求。本篇聚焦 MySQL 在分布式系统中的一致性保障机制,以及相关中间件的使用策略与实战经验。 一、一致性问题的由来 在 单机 MySQL 环境 中,事务具有原子性、隔离性&am…...
Java中如何枚举正则表达式捕获组的名字
在使用正则表达式在匹配文本时,除了可以通过表达式捕获命中的文本串外,还可以对捕获的文本串进行命名。尤其是在解析日志的场景中,经常会被用到。表达式如下: \<(?<pri>\d)\>(?<time>.*) (?<host>\S)…...
matlab实现图像压缩编码
一、基于DCT的JPEG压缩(有损) 1. 核心步骤 图像分块:将图像划分为88的小块。离散余弦变换(DCT):对每个块进行DCT变换。量化:对DCT系数进行量化以减少高频信息。熵编码:使用哈夫曼或…...
如何排查Redis单个Key命中率骤降?
问题现象 Redis整体命中率98%,但监控发现特定Key(如user:1000:profile)的命中率从99%骤降至40%,引发服务延迟上升。 排查步骤 1. 确认现象与定位Key // 通过Redis监控工具获取Key指标 public void monitorKey(String key) {Je…...

记一次 Starrocks be 内存异常宕机
突发性 be 内存飙高,直至被系统 kill 掉,be 内存如下:其中 starrocks_be_update_mem_bytes 指标打满,重启也是如此 [rootlocalhost bin]# curl -XGET -s http://192.168.1.49:8040/metrics | grep "^starrocks_be_.*_mem_b…...
Spring Boot 读取.env文件获取配置
Spring Boot 读取.env文件获取配置 在Resouce 目录下创建.env文件 # DEEP SEEK TOKEN DEEP_SEEK_TOKENyour_deep_seek_key # 阿里云百炼 TOKEN ALI_BAILIAN_TOKENyour_ali_bailian_keyyml引入.env文件 spring:config:import: optional:classpath:.env[.properties]使用.env文…...

LangChain-结合GLM+SQL+函数调用实现数据库查询(一)
业务流程 实现步骤 1. 加载数据库配置 在项目的根目录下创建.env 文件,设置文件内容: DB_HOSTxxx DB_PORT3306 DB_USERxxx DB_PASSWORDxxx DB_NAMExxx DB_CHARSETutf8mb4 加载环境变量,从 .env 文件中读取数据库配置信息 使用 os.getenv…...
python训练营打卡第41天
简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化:调整一个批次的分布,常用与图像数据特征图:只有卷积操作输出的才叫特征图调度器:直接修改基础学习率 卷积操作常见流程如下: 1. 输入 → 卷积层 → Batch…...
1.3HarmonyOS NEXT统一开发范式与跨端适配:开启高效跨设备应用开发新时代
HarmonyOS NEXT统一开发范式与跨端适配:开启高效跨设备应用开发新时代 在HarmonyOS NEXT的技术体系中,统一开发范式与跨端适配是两大关键特性,它们为开发者打破了设备边界,极大地提升了开发效率与应用体验。本章节将深入探讨方舟…...
麒麟v10,arm64架构,编译安装Qt5.12.8
Window和麒麟x86_64架构,官网提供安装包,麒麟arm64架构的,只能自己用编码编译安装。 注意,“桌面”路径是中文,所以不要把源码放在桌面上编译。 1. 下载源码 从官网下载源码:https://download.qt.io/arc…...
ArcGIS Pro 3.4 二次开发 - 布局
环境:ArcGIS Pro SDK 3.4 + .NET 8 文章目录 布局1 布局工程项1.1 引用布局工程项及其关联的布局1.2 在新视图中打开布局工程项1.3 激活已打开的布局视图1.4 引用活动布局视图1.5 将 pagx 导入工程1.6 移除布局工程项1.7 创建并打开一个新的基本布局1.8 使用修改后的CIM创建新…...
基于随机函数链接神经网络(RVFL)的锂电池健康状态(SOH)预测
基于随机函数链接神经网络(RVFL)的锂电池健康状态(SOH)预测 一、RVFL网络的基本原理与结构 随机向量功能链接(Random Vector Functional Link, RVFL)网络是一种单隐藏层前馈神经网络的随机化版本,其核心特征在于输入层到隐藏层的权重随机生成且固定,输出层权重通过最…...
爱其实很简单
初春时,元元买来两只芙蓉鸟。一只白色的,是雄鸟;另一只黄色的,是雌鸟。 每天清晨日出之前,雄鸟便开始“啁啾——啁啾”地啼鸣,鸣声清脆婉转,充满喜悦,仿佛在迎接日出,又…...

2025年渗透测试面试题总结-匿名[校招]安全工程师(甲方)(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 匿名[校招]安全工程师(甲方) 1. 介绍自己熟悉的渗透领域 2. 编程语言与开发能力 3. 实习工作内容与流程 …...

PySide6 GUI 学习笔记——常用类及控件使用方法(地址类QUrl)
文章目录 地址类QUrl主要功能URL 格式介绍常见 scheme(协议)类型QUrl 类常用方法常用方法示例典型应用场景 地址类QUrl QUrl 是 PySide6.QtCore 模块中的一个类,用于处理和操作 URL(统一资源定位符)。它可以解析、构建…...

任务23:创建天气信息大屏Django项目
任务描述 知识点: Django 重 点: Django创建项目Django视图函数Django路由Django静态文件Django渲染模板 内 容: 使用PyCharm创建大屏项目渲染大屏主页 任务指导 1. 使用PyCharm创建大屏项目。 创建weather项目配置虚拟环境创建ch…...
数学分析——一致性(均匀性)和收敛
目录 1. 连续函数 1.1 连续函数的定义 1.2 连续函数的性质 1.2.1 性质一 1.2.2 性质二 1.2.3 性质三 1.2.4 性质四 2. 一致连续函数 2.1 一致连续函数的定义 2.2 一致连续性定理(小间距定理)(一致连续函数的另一种定义) 2.3 一致连续性判定法 2.4 连…...

Flutter GridView网格组件
目录 常用属性 GridView使用配置 GridView.count使用 GridView.extent使用 GridView.count Container 实现列表 GridView.extent Container 实现列表 GridView.builder使用 GridView网格布局在实际项目中用的也是非常多的,当我们想让可以滚动的元素使用矩阵…...

【深度学习】18. 生成模型:Variational Auto-Encoder(VAE)详解
Variational Auto-Encoder(VAE)详解 本节内容完整介绍 VAE 的模型结构、优化目标、重参数化技巧及其生成机制。 回顾:Autoencoder(自编码器) Autoencoder 是一种无监督学习模型,旨在从未标注的数据中学习压…...
NodeJS全栈开发面试题讲解——P6安全与鉴权
✅ 6.1 如何防止 SQL 注入 / XSS / CSRF? 面试官您好,Web 安全三大经典问题分别从不同层面入手: 🔸 SQL 注入(Server端) 原理:恶意用户将 SQL 注入查询语句拼接,导致数据泄露或破坏…...
C# 密封类和密封方法
密封(sealed)是C#中用于限制继承和多态行为的关键字,它可以应用于类和方法,提供了一种控制继承层次的方式。 密封类 特点 使用 sealed 关键字修饰的类密封类不能被其他类继承,但可以继承其他类或接口主要用于防止派生所有结构(struct)都是…...
为什么badmin reconfig以后始终不能提交任务
最近遇到的怪事:修改了openlava配置以后运行badmin reconfig激活配置变更,但是长时间始终不能提交任务。 首先查看进程,发现openlava管理节点上的所有服务进程都在运行状态;查看mbd日志没有发现错误信息;再看mbd进程的…...

解决Window10上IP映射重启失效的问题
问题 在实际网络搭建过程中,大家有可能会遇到在局域网范围内,在自己本机上搭建一个网站或者应用时,其他设备通过本机的IP地址无法访问的问题,这个问题可以通过设置IP映射来解决,但是通过netsh interface命令设置的IP映射…...
力扣刷题(第四十四天)
灵感来源 - 保持更新,努力学习 - python脚本学习 删除重复的电子邮箱 解题思路 这个问题要求我们删除表中所有重复的电子邮箱,只保留每个唯一电子邮箱对应的最小id记录。解决这个问题的关键在于识别出哪些记录是重复的,并确定需要删除的…...
MyBatis-Plus高级用法:最优化持久层开发
MyBatis-Plus 是 MyBatis 的增强工具,旨在简化开发、提高效率并保持 MyBatis 的灵活性。本文将详细介绍 MyBatis-Plus 的高级用法,帮助开发者最优化持久层开发。 一、MyBatis-Plus 简介 MyBatis-Plus 是一个 ORM 框架,提供了 CRUD 接口、条…...
c++之循环
目录 C循环结构完全解析:从基础到实战应用 一、for循环结构 二、while循环结构 三、do-while循环结构 四、范围for循环(C11) 五、循环控制语句 C循环结构完全解析:从基础到实战应用 循环结构是编程语言的核心控制结构之一&a…...

python h5py 读取mat文件的<HDF5 object reference> 问题
我用python加载matlab的mat文件 mat文件: 加载方式: mat_file h5py.File(base_dir str(N) _nodes_dataset_snr- str(snr) _M_ str(M) .mat, r) Signals mat_file["Signals"][()] Tp mat_file["Tp"][()] Tp_list mat_fil…...

linux命令 systemctl 和 supervisord 区别及用法解读
目录 基础与背景服务管理范围配置文件和管理方式监控与日志依赖管理适用场景常用命令对照表实际应用场景举例优缺点对比小结参考链接 1. 基础与背景 systemctl 和 supervisord 都是用于管理和控制服务(进程)的工具,但它们在设计、使用场景和…...

Spring Boot + MyBatis 实现的简单用户管理项目的完整目录结构示例
📁 示例项目结构(基于 Maven) user-management/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/usermanagement/ │ │ │ ├── controller/ │ │ │ │ └── UserC…...