C++学习笔记(六)文件处理

本文详细介绍了使用C++进行文件操作的方法,包括顺序文件和随机文件的创建、更新及处理。通过具体示例展示了文件的读写过程,以及如何利用流对象进行文件的定位和数据的格式化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

本文主要讲解如何使用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如下










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值