一、概述
本文主要讲解如何使用C++程序来创建、更新和处理数据文件,主要考虑顺序存储和随机存储文件两种方式。
二、文件和流
C++将每个文件看成是字节序列,每个文件都以一个文件结束符或者是存储在系统维护、管理的数据结构中的一个特点字节数作为结尾,而C++使用流对象(一种特殊的类模板的对象,也即流类模板对象)提供程序和文件之间的通信,如标准输入流对象cin,标准输出流对象cout等。
为了在C++中进行对文件的读写操作,必须包含头文件<iostream>和<fstream>,头文件<fstream>包含了多种流类模板的定义:ifstream(用于从文件输入数据)、ofstream(用于向文件输出数据)、fsream(用于从文件输入或向文件输出数据)
三、创建顺序文件
C++没有在文件上强加任何结构,因此必须自己设计文件结构满足应用程序的需要,在下面的例子中创建了简单的记录结构。
的
// #include <iostream>
// using std::cerr;
// using std::cin;
// using std::cout;
// using std::endl;
// using std::ios;
//
// #include <fstream>
// using std::ofstream;
//
// #include <cstdlib>
// using std::exit;
//
// int main()
// {
// ofstream outClientFile;
// outClientFile.open( "clients.dat" , ios::app); //打开文件,换成txt也可以
//
// if( !outClientFile)
// {
// cerr<<"File could not be opened"<<endl;
// exit(1);
// }
//
// cout<<"Enter the account,name,and balance."<<endl
// <<"Enter end-of-file to end input,\n";
//
// int account;
// char name[30];
// double balance;
//
// while(cin>>account>>name>>balance)
// {
// outClientFile<<account<<' '<<name<<' '<<balance<<endl;
// }
//
// return 0;
// }
//testOfstream
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
using std::ios;
#include <fstream>
using std::ofstream;
#include <cstdlib>
using std::exit;
int main()
{
ofstream outClientFile; //创建了ofstream流类模板对象,用于向文件输出数据
outClientFile.open( "clients.txt" , ios::out); //第一个参数为将打开文件的文件名,
//第二个参数为文流打开模式,该对象使用了open函数打开文件
//使用ios::out,假如文件有内容,则会被清除
//ofstream outClientFile( "clients.txt" , ios::out) 也可以直接用这种方式打开文件
if( !outClientFile) //判断是否可正常打开文件
{
cout<<"File could not be opened"<<endl;
exit(1);
}
cout<<"Enter the account,name,and balance."<<endl
<<"Enter end-of-file to end input,\n";
int account;
char name[30];
double balance;
while(cin>>account>>name>>balance) //括号中使用cin键入account、name、balance的值,直到键入文件结束符Ctrl+z
{
outClientFile<<account<<' '<<name<<' '<<balance<<endl;//ofstream流类模板对象按此格式输出数据至文件中
}
return 0;
}
//当main函数终止时,程序将隐式地调用outClientFile的析构函数关闭文件clients.txt,也可以在程序中显示地调用
//成员函数close()函数显示地关闭文件如:outClientFile.close();
运行结果
由键盘输入account、name、balance的值,ofstream流类模板对象outClientFile使用“<<”符号将数据向文件输出,也即写至文件clients.txt中,当记录输入完毕后,需输入文件结束符(Ctrl+z)。以下为clients.txt中的内容
需要注意的是语句outClientFile.open("clients.txt" , ios::out);中ios::out为文件的打开模式,此外还有ios::app、ios::ate、ios::trunc等,功能详细介绍如下:
四、从顺序文件读取数据
前面已经创建了顺序存储数据记录的文本文件clients.txt,这里介绍使用ifstream流类模板对象打开文件clients.txt,并从文件读取数据
//testIfstream
#include <iostream>
#include <fstream> //包含头文件<fstream>
using std::ifstream; //使用ifstream流类模板
using std::ios;
using std::cerr;
using std::cout;
using std::endl;
#include <iomanip>
using std::setw;
int main()
{
ifstream inClientFile( "clients.txt" ,ios::in); //创建ifstream流类模板对象inClientFile并关联
//至文件clients.txt,打开模式为ios::in,即从文件输入
if(!inClientFile) //若不能打开则报错
{
cerr<<"File could not be opened"<<endl;
exit(1);
}
int account;
char name[30];
double balance;
cout<<"Account"<<setw(10)<<"Name"
<<setw(15)<<"Balance"<<endl;
while(inClientFile >>account>>name>>balance) //ifstream对象inClientFile使用符号“>>”将文件中数据
{ //分别读入至相应的变量中,直至遇到文件结束符结束输入
cout<<account<<setw(15)<<name
<<setw(15)<<balance<<endl;
}
return 0;
}
运行结果
该程序从文件起始位置开始连续地读取所有数据(空格不读取),直到找到所需的数据。此外,istream和ofstream均提供成员函数来重新定位指针(文件中下一个被读取或写入的字节号)。ifstream成员函数为seekg(seek get),ofstream为seekp(seek put),用法如下
inFile.seekg(n) //n表示距离文件起始位置n个字节取0时即为定位至文件开始位置
inFile.seekg(n, ios::cur) // 第二个参数ios::cur表示定位至距离当前流位置n个字节的位置
inFile.seekg(n, ios::end) // ios::end表示定位至距离流结尾位置的n个字节的位置
同理ofstrem有:
ofstream outFile;
outFile.seekp(n);
outFile.seekp(n, ios::cur);
outFile.seekp(n, ios::end);
另外我们还需了解数据在文件中的存储格式,使用操纵符“<<”,“>>”是格式化输入/输出模式,将数据存储至文件时,会变成大小不同的字段(字符序列)
例如:
1 Lili 22.2
以上数据使用操纵符“<<”写入文件前1为4字节int型,Lili为字符数组,22.2为float型,而写入文件后变为3个长度不同的字符序列。使用“>>”从文件读取数据时,会将字符序列转换为“>>”后的数据的类型进行存储,例如在testIfsteam.cpp中return 0; 前加入
cout<<"\n\n";
inClientFile.clear();
inClientFile.seekg(7);//定位至距离文件起始位置7个字节后
double s1; //使用double型读入数据
char s2; //使用char型变量可读入一个字符
inClientFile >>s1>>s2; //按顺序分别读入
cout<<s1<<' '<<s2; //输出结果
运行结果为:22.2 2
因此要想不破坏其他数据的情况下,改变某个数据的值是行不通的。
五、随机存储文件
前面所述均为顺序存储文件,在这介绍另一种存储文件的方法,即随机存储文件。由于C++没有强加文件存储结构,所以必须自己构建,而通过seekg或seekp,可以快速地定位记录到文件开头的精确位置。这种方法是先在文件中创建多个具有固定内存(固定字节数)的文件,该文件的结构如下所示:
如上图所示,方框表示占用了固定字节数的一块内存,这些内存可为空也可为非空,而通过定位指针可以快速定位至任何一个位置,故可以实现随机存储。
下面通过创建某班末次考试成绩单示例,首先在StudentScoreData.dat文件(换成txt文件也可以)中,创建含有40个固定内存的文件,每个内存块占用大小均为sizeof(StudentData),StudentData类中构建了存放学生学号、姓名、分数等数据的变量。该程序还演示了建立一个可供打印的文本文件、更新记录、增加一个记录、删除一个记录。
代码如下:
//StudentData.h
#ifndef STUDNETDATA_H
#define STUDNETDATA_H
#include <string>
using std::string;
class StudentData
{
public:
StudentData(int = 0,string ="" ,float = 0.0);
void setStudentID( int );
int getStudentID() const;
void setName( string );
string getName()const;
void setScore( float);
float getScore()const;
private:
int studentID; //学号
char name[9]; //姓名
float score; //分数
};
#endif
//StudentData.cpp
#include <string>
using std::string;
#include <iostream>
using std::cout;
using std::cin;
#include "StudentData.h"
StudentData::StudentData(int id ,string nameValue,
float scoreValue)
{
setStudentID(id);
setName(nameValue);
setScore(scoreValue);
}
void StudentData::setStudentID(int id)
{
studentID = id;
}
int StudentData::getStudentID()const
{
return studentID;
}
void StudentData::setName( string nameValue)
{
const char *namePtr = nameValue.data();
int length = nameValue.size();
length = (length <9? length:8);
strncpy(name ,namePtr, length);
name[length] = '\0';
}
string StudentData:: getName()const
{
return name;
}
void StudentData::setScore(float scoreValue)
{
while(scoreValue<0 && scoreValue>100)
{
cout<<"\n输入分数不在范围1-100,请重新输入: ";
cin>>scoreValue;
}
score = scoreValue;
}
float StudentData::getScore()const
{
return score;
}
//StudentScoreAccount.cpp
#include <iostream>
using std::cerr;
using std::endl;
using std::ios;
using std::cin;
using std::cout;
using std::fixed;
using std::left;
using std::right;
using std::showpoint;
#include <fstream>
using std::ofstream;
using std::ostream;
using std::fstream;
#include <iomanip>
using std::setw;
using std::setprecision;
#include <cstdlib>
using std::exit;
#include "StudentData.h"
int enterChoice();
void createTextFile(fstream &);
void updateRecord(fstream &);
void newRecord(fstream &);
void deleteRecord(fstream &);
void outPutLine( ostream & ,const StudentData & );
int getAccount(const char* const);
enum Choices{PRINT = 1,UPDATE,NEW ,DELETE ,END }; //枚举类型,与宏替换功能相似
int main()
{
ofstream createNewFile( "StudentScoreData.dat" ,ios::binary); //使用ios::binary将数据以二进制形式保存
if (!createNewFile)
{
cerr<<"文件打开有误."<<endl;
exit(1);
}
StudentData blankData;
for(int i = 0;i < 40 ;i++) //建立具有40个存储单元的文件
createNewFile.write(reinterpret_cast<const char*>(&blankData) , //使用write成员函数写入数据
sizeof(StudentData)); //每个存储内存块大小为StudentData类对象所占内存大小
createNewFile.close(); //创建完毕关闭文件
fstream inOutFile("StudentScoreData.dat",ios::in| ios::out);//可读或写文件
if (!inOutFile)
{
cerr<<"文件打开有误."<<endl;
exit(1);
}
int choice;
while((choice = enterChoice()) != END)
{
switch(choice)
{
case PRINT:
createTextFile(inOutFile);//将StudentScoreData.dat另存为print.txt
break;
case UPDATE:
updateRecord(inOutFile);//更新分数
break;
case NEW:
newRecord(inOutFile); //增加一个新的记录
break;
case DELETE:
deleteRecord(inOutFile);//删除一个记录
break;
default:
cerr << "输入有误"<<endl;
break;
}
}
return 0;
}
int enterChoice()
{
cout<<"********************************************"
<<"\n请输入你的选择:"<<endl
<<"1-创建按顺序存储的print.txt文本文件,用于打印"<<endl
<<"2-更新一个分数记录"<<endl
<<"3-增加一个分数记录"<<endl
<<"4-删除一个分数记录"<<endl
<<"5-结束\n"
<<"********************************************"<<endl;
int menuChoice;
cin>>menuChoice;
return menuChoice;
}
void createTextFile(fstream &readFromFile)
{
ofstream outPrintFile("print.txt" ,ios::out);//打开print.txt,会覆盖原先的内容,不存在则新建
if (!outPrintFile)
{
cerr<<"文件创建失败"<<endl;
exit(1);
}
outPrintFile<<left<<setw(10)<<"学号"<<setw(10)
<<"姓名"<<right<<setw(8)<<"成绩" <<endl;
readFromFile.seekg(0); //指定读取的位置
StudentData studentData;
readFromFile.read(reinterpret_cast<char *>(&studentData),
sizeof(studentData)); //将内存中数据读入StudentData类的对象studentData中
while(!readFromFile.eof())
{
if(studentData.getStudentID() != 0)
outPutLine(outPrintFile ,studentData); //将对象studentData的数据输出至print.txt文件中
readFromFile.read(reinterpret_cast<char *>(&studentData),
sizeof(studentData)); //将写一个内存中数据读入StudentData类的对象studentData中
}//循环结束,完成数据的输出
}
void updateRecord(fstream &updateFile)
{
int studentIDValue = getAccount("输入需要更新分数的学号"); //输入需要更新分数的学号
updateFile.seekg((studentIDValue -1)*sizeof(StudentData));//由于指针位置从0开始,故(studentIDValue -1)*sizeof(StudentData)
//表示定位至要修改数据的那个内存块
StudentData scoreData;
updateFile.read(reinterpret_cast<char *>(&scoreData) ,
sizeof(StudentData)); //读取数据
if (scoreData.getStudentID() != 0)
{
outPutLine(cout ,scoreData);
cout<<"\n请输入更新的分数:";
float scoreValue;
cin>>scoreValue;
scoreData.setScore(scoreValue); //使用成员函数改变对象scoreData中的数值
outPutLine(cout ,scoreData);
cout<<"\n";
updateFile.seekp((studentIDValue -1)*sizeof(StudentData));//找到要写入的位置
updateFile.write(reinterpret_cast<char *>(&scoreData) ,
sizeof(StudentData)); //使用write成员函数将对象scoreData中的数值写至文件中
}
else
cerr<<"学号 "<<studentIDValue
<<" 不存在"<<endl;
}
void newRecord(fstream &insertInFile)
{
int studentIDValue = getAccount("输入新增加的学生信息的学号");
insertInFile.seekg((studentIDValue -1)*sizeof(StudentData));
StudentData scoreData;
insertInFile.read(reinterpret_cast<char *>(&scoreData) ,
sizeof(StudentData));
if(scoreData.getStudentID() == 0)//新增加的过程,则是讲一个设置好数据的对象写入一个空白内存块中
{
string name;
float score;
cout<<"请输入姓名、成绩:\n";
cin>>name;
cin>>score;
scoreData.setName(name);
scoreData.setScore(score);
scoreData.setStudentID(studentIDValue);
insertInFile.seekp((studentIDValue -1)*sizeof(StudentData));
insertInFile.write(reinterpret_cast<char *>(&scoreData) ,
sizeof(StudentData));
}
else
cerr<<"学号:"<<studentIDValue
<<" 已存在"<<endl;
}
void deleteRecord(fstream &deleteFromFile)
{
int studentIDValue = getAccount("输入需要删除的学生信息的学号");
deleteFromFile.seekg((studentIDValue -1)*sizeof(StudentData));
StudentData scoreData;
deleteFromFile.read(reinterpret_cast<char *>(&scoreData) ,
sizeof(StudentData));
if (scoreData.getStudentID() != 0) //删除即是将一个空白的对象BlankData写入原有数据的内存块中
{
StudentData BlankData;
deleteFromFile.seekp((studentIDValue -1)*sizeof(StudentData));
deleteFromFile.write(reinterpret_cast<char *>(&BlankData) ,
sizeof(StudentData));
cout<<"学号:"<<studentIDValue
<<" 已删除"<<endl;
}
else
cerr<<"学号:"<<studentIDValue
<<" 不存在"<<endl;
}
void outPutLine( ostream &output ,const StudentData &record )
{
output<<left<<setw(10)<<record.getStudentID()
<<setw(10)<<record.getName()
<<setw(9)<<setprecision(2)<<right<<fixed
<<showpoint<<record.getScore()<<"\n";
}
int getAccount(const char* const prompt)
{
int studentIDValue;
do
{
cout<<prompt<<" (1 - 40): ";
cin>>studentIDValue;
}while(studentIDValue <1 ||studentIDValue>40);
return studentIDValue;
}
运行结果
文件print.txt如下