本文主要是介绍Linux下C++标准文件库编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录(?)[+]
19.1 Linux下C++文件的创建
C++语言将每个文件都看成一个有序的字节流,每一个文件通常都可以以文件的结束符EOF(end-of-filemarker)作为结束标记,当然也可以由系统维护和管理的数据结构中特定的字节号结束,如换行符等特殊字节号作为文件中流数据划分点。
在C++中进行文件处理,通常需要要包含两个头文件<iostream>和<fstream>。其中<fstream>头文件包含类ifstream(从文件输入)、ofstream(向文件输出)和fstream(从文件输入/输出)的定义。通常情况下通过创建这些类的对象来打开文件。这些文件定义类分别从istream,ostream和iostream类派生而来,即继承了它们的功能。其文件处理类的继成关系如图18.1所示。
图18.1 文件处理类继承关系
19.1.1 Linux下C++创建顺序读写文件
前面已经描述过,C++中文件是被视为无结构的字节流的,所以记录等概念在C++的文件中是不存在的。所有的文件处理都需要从文件的开始之处逐步向后进行读写的文件可以称为顺序读写文件,之所以称为顺序读写文件仅仅是为了区别随机读写文件而定的称呼。当实际应用中需要改变文件顺序读写的方式时,可以通过相应的定位处理,通过一定的记录形式来定位读取文件中的记录数据。
使用标准C++文件库,通过创建一个文件并将其连接到应用程序上,以便用来读写文件数据操作,通常应用程序中需要包含头文件#include<fstream>,通过该头文件提供的公开接口方法操作相应文件。
创建一个被用于输出的文件,需要定义一个ofstream(输出文件流)类对象,该对象实例定义如下。
ofstream outfile(“example.txt”,ios::out);
以上创建文件的目的是为了从应用程序处获取到的数据输出到文件中,所以创建了ofstream对象,文件名和文件打开的方式这两个参数传递给ofstream对象的构造函数用于构造输出流对象。对于输出文件对象,是以ios::out(数据输出到文件)的方式打开该文件的。ios::out文件打开方式表示,一旦文件被打开,如果指定打开的文件存在,则文件中所有的数据都会不再存在,文件读写将会从文件头部开始;而如果指定需要打开的文件不存在,那么就用传入的文件名来重新创建该文件。
对于ofstream类,我们可以用另外一种方式创建并打开文件,那就是先定义ofstream对象,而后再打开并关联文件与对象。实际两种方式作用相同,第一种通过构造函数提供类似open方法功能来打开关联文件,而第二种则通过调用输出流对象的open方法来创建打开传入文件名的文件,该方式如下。
ofstream outfile;
outfile.open(“example.txt”,ios::out);
创建ofstream对象,利用ofstream成员函数open打开文件并将其关联到现有的ofstream对象上。值得提醒一个文件编程好习惯,那就是当创建一个文件输出流对象并关联打开一个文件的时候,最好测试一下打开文件的流对象操作是否成功是必要的,可以通过如下两种方式代码进行文件打开操作判断。
if( !outfile )
{
cerr<<“Filecan not open!”<<endl;
exit(1);
}
或
if(!outfile.is_open())
{
cout<<”Filecan not open!”<<endl;
exit(1);
}
上述实例演示了两种判断文件打开是否成功的方法,第一种判断针对不显式使用open操作打开文件的方式,直接判断文件输出流对象是否为真来判断文件打开是否成功。而第二种方式直接调用is_open方法,根据该方法的返回值判断文件打开是否成功,以便后续作出相应处理。
从上述两种创建输出文件的方法中,ofstream类构造传入的两个参数,同样适用open方法也传入两个一样的参数。两个参数中第一个是需要创建文件的文件名,第二个是文件的打开方式。通常第一个参数文件名,我们可以根据自己定义的格式来设定,这部分在后面一个完整的文件处理例子中会详细讲述。C++文件处理库提供了一系列的打开方式处理,如表格19.1列出了文件打开方式以及相应的说明。
表格19.1 文件基本打开方式
文件打开方式 | 使用说明 |
ios::app | 打开文件后将输出以追加的方式写入文件尾 |
ios::ate | 打开输出文件后,定位到文件尾,数据可以写入文件任何地方 |
ios::in | 打开文件用来输入 |
ios::out | 打开文件用来输出 |
ios::trunc | 删除现有打开文件的内容,也是ios::out的默认操作 |
ios::binary | 打开文件以二进制的方式输入或输出 |
19.1.2 Linux下C++顺序写入文件
上一节讲述了顺序文件创建过程,对于实际的项目应用,需要对创建的文件进行顺序读写操作。根据上述讲述,当前打开的文件在指定目录中如果不存在,则会自动创建一个该文件名的文件,同时将文件与流对象关联起来,此时即可以通过流的输出操作向该文件写入数据了,完整写入顺序文件实例如下。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1901.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1901
* 源文件chapter1901.cpp
* 顺序文件写入操作实例
*/
#include <iostream>
#include <fstream>
using namespace std;
/*主程序入口*/
int main()
{
ofstreamoutfile; //定义输出文件类对象
outfile.open(“clientFile.txt”,ios::out); //以输出的方式打开文件
if(!outfile) //判断文件打开是否成功
{
cerr<<”Cannot open file!”<<endl; //如果文件创建或者打开不成功,则打印出错信息
exit(1); //退出程序
}
for(inti = 0; i < 6; i++) //通过ofstream方法重载的<<操作符,循环的向文件中输出记录
{
outfile<<i<<”“<<”hello fileJ”<<endl; //单条数据记录写入
cout<<”Successwrite record!”<<endl;//屏幕输出每条记录写入成功信息
}
return0;
}
本实例程序组成很简单,主要在主函数内部实现创建文件并向新文件中写入数据的操作。主流程都在主函数内部实现,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下使用makefile工程文件,该文件主要涉及编译源文件为fileWrite.cpp,文件编辑如下。
OBJECTS=chapter1901.o
CC=g++
chapter1901: $(OBJECTS)
$(CC)$(OBJECTS) -o chapter1901
clean:
rm -fchapter1901 core $(OBJECTS)
submit:
cp -f -rchapter1901 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,编译生成可执行程序后通过make submit命令提交可行程序到相应的实例bin目录,通过cd命令定位至bin目录后运行该程序结果如下所示。
[developer@localhost src]$make
g++ -c -ochapter1901.o chapter1901.cpp
g++ chapter1901.o -o chapter1901
[developer@localhost src]$make submit
cp -f -r chapter1901 ../bin
cp -f -r *.h ../include
[developer@localhost src]$./chapter1901
Success write record!
Success write record!
Success write record!
Success write record!
Success write record!
Success write record!
[developer@localhost src]$ll
总用量 20
-rwxr-xr-x 1 ocs linkage 14992 1月 5 03:29 chapter1901
-rw-r--r-- 1 ocs linkage 78 1月 5 03:30 clientFile.txt
本实例主要实现创建并打开顺序文件同时向该文件写入数据的应用,程序实例中首先定义文件输出流对象outfile,随后调用文件打开操作open方法打开指定参数的文件。判断完文件是否成功打开之后通过for循环结构内部通过流的<<操作循环的将6行数据顺序的写入文本文件clientFile.txt中。此时可以在当前shell下使用vi编辑器打开文本文件clientFile.txt来验证刚刚写入的内容。
顺序文件读写操作意味着文件是按照顺序的字节流读取的,仅仅在应用程序中为了分记录写入或者读取而人为添加了换行符号。通过本实例只是想简单的告诉初学者顺序文件创建以及写入数据的基本实现过程,了解通过流提供的文件操作的一般使用情况,至于更加复杂的实际项目应用操作,会在后面的章节中详细描述。
19.1.3 Linux下C++顺序读取文件
对于存在的文件顺序的读取操作,C++标准库提供了ifstream输入流类定义,通常指定相应目录下存在的文件名,通过定义文件输入流对象,关联并打开文件从文件中读取数据。
下面将会通过一个完整实例,从上小节创建并写入数据的文件中顺序的读取数据,代码编辑如下。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1902.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1902
* 源文件chapter1902.cpp
* 顺序文件读取实例
*/
#include <iostream>
#include <fstream>
using namespace std;
/*主程序入口*/
int main()
{
ifstreaminfile; //定义文件输入流对象
infile.open("clientFile.txt",ios::in); //定义输入流对象,以输入的方式打开文件
if(!infile) //判断文件是否正确打开
{
cout<<"Cannot open file!"<<endl; //如果文件创建或打开不成功,打印输出出错信息
exit(1); //退出程序
}
for(inti = 0; i < 6; i++) //ifstream输入流提供的重载>>操作符,从文件中读取相应数据
{ //循环读取文件
stringid,name,data; //定义临时字符串变量
infile>>id; //输入流对象读取数据写入临时变量id
infile>>name; //输入流对象读取数据写入临时变量name
infile>>data; //输入流对象读取数据写入临时变量data
cout<<"Filedate :"<<id<<” “<<name<<” “<<data<<endl; //打印输出读取记录数据信息
}
return0;
}
本实例程序组成很简单,主要在主函数内部实现针对已经存在的文件读取数据操作。主流程都在主函数内部实现,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下使用makefile工程文件,该工程文件中主要涉及源文件为fileRead.cpp,文件编辑如下。
OBJECTS=chapter1902.o
CC=g++
chapter1902: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1902
clean:
rm -fchapter1902 core $(OBJECTS)
submit:
cp -f -rchapter1902 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,编译生成可执行程序后,make submit命令提交可执行程序到该实例bin目录。另外为了演示读取实例chapter1901程序写入的文件,因此需要通过cd命令从当前位置定位至实例chapter1901目录中拷贝产生的txt文本文件至本实例当前bin目录。随后执行程序运行结果如下。
[developer@localhost src]$make
g++ -c -o chapter1902.o chapter1902.cpp
g++ chapter1902.o -g -o chapter1902
[developer@localhost src]$make submit
cp -f -r chapter1902 ../bin
cp -f -r *.h ../include
[developer@localhost src]$cd../../chapter1901/bin
[developer@localhost src]$ll
总用量 20
-rwxr-xr-x 1 ocs linkage 14992 1月 5 03:29chapter1901
-rw-r--r-- 1 ocs linkage 78 1月 5 03:30clientFile.txt
[developer@localhost src]$cpclientFile.txt ../../chapter1902/bin
[developer@localhost src]$cd../../chapter1902/bin
[developer@localhost src]$ls
chapter1902 clientFile.txt
[developer@localhost src]$./chapter1902
File date :0 hello file
File date :1 hello file
File date :2 hello file
File date :3 hello file
File date :4 hello file
File date :5 hello file
4.程序剖析
以上实例演示了如何从已经存在的文件中读取数据操作,首先创建一个输入文件流(ifstream)对象infile,以ios::in文件输入的方式打开文件,注意文件输入输出这种说法经常会扰乱了初学者,这里需要理解的是这种文件输入输出的说法是针对设备来讲的,对于设备本身,输出文件就是向文件输出数据,也就是向其中写文件。对于设备本身,输入文件就是从文件向设备本身输入数据,也就是读取文件的过程。
实例中输入文件流对象创建完成后,判断打开文件操作是否成功。接着采用for循环控制结构从文件顺序的读取数据并且将其输入到相关变量中便于打印输出。此时采用的是ifstream流类重载>>操作符实现数据写入到当前变量。
另外由于重载的>>操作符每次都只从文件中读取一个单字,而不是一整行数据,所以这里需要针对文件中数据内容,定义3个临时字符串变量,最后将其拼凑成一条数据显示出来。程序处理中不需要显式的检查文件中数据行是否到达结束位置,因为重载的>>操作符会自动处理。此外也不必显式的关闭文件,因为ifstream类的析构函数会在应用程序结束后自动处理关闭文件。
通过前面两个小节的简单介绍,大致了解了Linux下c++处理文件的基本操作情况。在此基础上,初学者可以采用举一反三的学习方法来练习文件的简单顺序读取、写入处理,比如采用不同的打开方式,通过修改以上的实例来验证相关文件处理的特性,从而达到触类旁通的效果。
19.1.4 Linux下C++创建记录结构读写文件
对于系统工程应用,创建顺序访问文件以及从文件中顺序查找遍历所需要的特定的数据信息显然不能实现应用程序快速的访问要求。一般实时性要求访问文件记录方面的应用很多,比如银行系统、电信支撑系统中某些对文件处理实时性要求高的接口处理系统。而针对快速访问文件记录需求的应用程序一般都是采用随机访问文件方式实现,即文件中数据可以通过固定记录的形式快速定位操作。
前面已经讲述过,C++中将文件看成有序的字节流,并不针对文件提供相应的结构处理。实现文件结构方式有多种,比如定长记录、变长记录等。此处针对较常用的定长记录使用方法作为典型加以讲述。
所谓随机文件处理,意味着在写入文件的时候很少一次写一个域,一般都是写入一个struct结构体或者是一个类的对象,数据通过自定义类型的记录来表现。下面将会分析定长记录形式的随机文件的创建方式,定长记录下文件中每条记录长度都要求是相同,所以一般可以通过灵活的方式计算每条记录相对于文件起点的位置,从而在指定位置访问或者写入相应的记录数据,加快文件操作的处理速度。
下面通过一个完整实例直观的了解定长型的随机文件创建过程,该完整实例代码编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1903.h、chapter1903.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1903
* chapter1903.h、chapter1903.cpp
* 记录结构文件读写实例
*/
//记录头文件chapter1903.h
#ifndef RECORDFILE_H_
#define RECORDFILE_H_
struct recordData //文件记录结构体
{
intid; //记录编号
stringname; //记录名
stringdata; //记录数据
};
#endif
//源文件chapter1903.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
#include "recordFile.h"
/*主程序入口*/
int main()
{
ofstreamoutfile("recordFile.txt",ios::binary); //定义输出文件流对象,以二进制的方式打开文件
if(!outfile) //定义并且判断文件输出对象
{
cerr<<"Cannot open file!"<<endl;
exit(1);
}
for(inti = 0; i < 2; i++) //循环生成记录写入随机文件
{
recordDatarecord; //定义结构体记录对象
record.id= i; //记录对象编号赋值
cout<<"Pleaseinput name:"<<endl;
cin>>record.name; //输入对应结构体记录的记录名
cout<<"pleaseinput data:"<<endl;
cin>>record.data; //输入对应结构体记录的数据
outfile.write(reinterpret_cast<constchar*>(&record),sizeof(recordData)); //写入记录到文件
}
return0;
}
本实例程序组成很简单,主要在主函数内部实现针对文件记录型结构数据读写操作。主流程都在主函数内部实现,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下需要编译的源文件为chapter1903.cpp,makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1903.o
CC=g++
chapter1903: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1903
clean:
rm -fchapter1903 core $(OBJECTS)
submit:
cp -f -rchapter1903 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
在Linux下执行make命令,生成可执行程序后,执行make submit命令提交程序文件至该实例bin目录,通过cd命令定位至该实例bin目录,运行该可执行程序,程序运行结果如下所示。
[developer@localhost src]$make
g++ -c -o chapter1903.o chapter1903.cpp
g++ chapter1903.o -g -o chapter1903
[developer@localhost src]$make submit
cp -f -r chapter1903 ../bin
cp -f -r *.h ../include
[developer@localhost src]$cd../bin
[developer@localhost src]$./chapter1903
Please input name:
jack
please input data:
hello!
Please input name:
marry
please input data:
hi!
[developer@localhost src]$ll
总用量 20
-rwxr-xr-x 1 ocs linkage 16108 1月 6 05:53 chapter1903
-rw-r--r-- 1 ocs linkage 24 1月 6 05:54 recordFile.txt
4.程序剖析
本实例主要演示随机文件创建的过程,并且在创建文件完毕后向文件写入相应的自定义记录数据。实例中头文件recordFile.h定义结构体recordData表示需要写入文件中的数据记录格式,该记录格式中主要包含三个成员字段,分别为记录的编号id、记录的名称name以及记录的数据字段data。
主程序中使用该记录机构体则需要包含相应的头文件,首先通过ofstream定义文件输出流对象outfile,直接采用构造函数的方式创建并打开文本文件recordFile.txt,以二进制方式打开文件。随后判断该文件输出流对象打开文件是否成功,如果文件成功打开后,则准备通过该流对象向文件输入数据。
文件创建后写入记录数据通过定义for循环控制结构来循环写入,首先定义记录结构体对象实例record,随后将循环体中i变量以递增的方式赋给该记录对象的id成员。之后通过输入数据提示使用cin对象>>操作符向记录对象中字段赋值,最后通过outfile对象调用其write方法将整个记录直接写入到文件中,按照for循环中设定的增幅上限,总计向文件中写入两条记录。
由于文件流对象write方法原型的第一个参数必须是const char*型的,所以这里需要做一个转换,将结构体记录转换成此类型,然后操作该指针写入记录数据到文件中,reinterpret_cast用于强制转换使用类型,将结构体对象转换为const char*类型,此时调用write方法就可以顺利的通过编译,不会产生语法的错误。
19.1.5 Linux下C++记录结构写入文件
通过上小节讲述,大致了解了创建随机访问文件的过程,并且介绍了向随机文件写入记录数据的操作方法。随机文件中数据依然是以字节流的方式存储,其中访问以及写入时通过计算记录固定大小在指定的位置操作即可。本小节将会通过一个完整实例来演示使用ofstream流类中文件定位方法seekp和文件写入方法write将记录数据在文件指定的位置写入操作的应用,该实例代码编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1904.h、chapter1904.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1904
* 源文件chapter1904.h、chapter1904.cpp
* 记录结构文件写入实例
*/
#ifndef RECORDFILE_H_
#define RECORDFILE_H_
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
struct recordData //订单文件记录结构体
{
intid; //订单标号
chargoodsName[1024]; //商品名
charfactoryName[1024]; //商品厂家名称
doubleprice; //商品价格
};
class RecordFile //记录文件类
{
public:
RecordFile(); //记录文件类构造方法声明
~RecordFile(); //记录文件类析构方法声明
public:
voidsetFileName(const string &path, const string &filename); //设置文件名方法声明
boolopenFile(); //打开文件方法声明
voidcloseFile(); //关闭文件方法声明
boolwriteFile(recordData &record); //根据传入记录结构,写入文件方法声明
private:
string m_fileName; //文件名数据成员定义
string m_path; //路径名数据成员定义
ofstream m_ofstream; //文件输出流对象定义
int m_oOpenFlag; //文件打开标记数据成员定义
};
#endif
//源文件chapter1904.cpp
#include "chapter1904.h"
RecordFile::RecordFile() //记录文件类构造函数
{
m_oOpenFlag = 0; //初始化文件打开标记,初始值为整数0表示未设置文件打开标记
}
RecordFile::~RecordFile() //记录文件类析构函数
{
closeFile(); //对象析构时,调用关闭文件方法
}
void RecordFile::setFileName(const string&path, const string &filename) //设置文件名方法定义
{
m_fileName = filename; //根据传入filename参数,将其值赋给记录类数据成员m_fileName
m_path = path; //根据传入path路径参数,将其值赋给记录类数据成员m_path
if(strlen(path.c_str()) > 0) //if控制结构的条件中通过strlen方法计算路径名长度结果进行判断
{
if(path.c_str()[path.size() - 1]!='/') //如果上述条件判断路径名有效不为空,则if控制结构中,判断条
//件里判断路径名尾部是否有
m_path+='/';
m_fileName = m_path + m_fileName;
}
else
{
m_fileName ="/" + m_fileName;
}
}
bool RecordFile::openFile()
{
if (m_oOpenFlag==1)
return true;
m_ofstream.open(m_fileName.c_str(),ios::binary);
if(!m_ofstream)
return false;
m_oOpenFlag = 1;
returntrue;
}
void RecordFile::closeFile()
{
if(m_oOpenFlag == 1)
{
m_ofstream.close();
m_ofstream.clear();
m_oOpenFlag = 0;
}
}
bool RecordFile::writeFile(recordData &record)
{
m_ofstream.seekp((record.id -1)*sizeof(recordData));
if(m_oOpenFlag == 0)
{
return false;
}
m_ofstream.write(reinterpret_cast<constchar*>(&record),sizeof(recordData));
return true;
}
/*主程序入口*/
int main()
{
recordDatarecord;
RecordFilerecordFile;
recordFile.setFileName("/billing/wangfeng/linux_c++/chapter19/chapter1904/bin","clientFile.txt");
if(!recordFile.openFile())
{
cout<<"Open file fail!"<<endl;
}
cout<<"Pleaseinput the record id(1~10):"<<endl;
cin>>record.id;
while(record.id> 0 && record.id <= 10)
{
cin>>record.goodsName>>record.factoryName>>record.price;
if(!recordFile.writeFile(record))
{
cout<<"Write Filefail!"<<endl;
}
cout<<"Please input the record id(1~10):"<<endl;
cin>>record.id;
}
recordFile.closeFile();
return0;
}
本实例程序组成很简单,主要在主函数内部实现针对文件记录型结构数据定位读写操作。主流程都在主函数内部实现,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下需要编译的源文件为chapter1904.cpp,makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1904.o
CC=g++
chapter1904: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1904
clean:
rm -fchapter1904 core $(OBJECTS)
submit:
cp -f -rchapter1904 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息
3.编译运行程序
当前shell下执行make命令之后,生成可执行程序文件,随后执行make submit命令将程序文件提交至该实例bin目录,通过cd命令定位至该实例bin目录,运行该实例程序结果如下所示。
[developer @localhost src]$ make
g++ -c -ochapter1904.o chapter1904.cpp
g++ chapter1904.o -g -o chapter1904
[developer @localhost src]$ make submit
cp -f -r chapter1904 ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ rm clientFile.txt
[developer @localhost bin]$ ./chapter1904
Please input the record id(1~10):
1
icecream
factory
2.5
Please input the record id(1~10):
2
apple
factory
1.3
Please input the record id(1~10):
3
orange
factory
1.4
Please input the record id(1~10):
11
[developer @localhost bin]$ ll
total 32
-rwxr-xr-x 1 developer oracle 14794 May 610:59 chapter1904
-rw-r--r-- 1 developer oracle 6180 May 6 11:00 clientFile.txt
4.程序剖析
本实例直接通过封装一个记录类的方式来实现记录结构写文件基本操作演示。从程序运行结果来看,通过创建或者打开文件后提示输入文件写入记录数据,限定范围为1~10之间编号商品记录信息。输入到第三条后,通过超出编号输入值的方式来终止程序。此时程序通过文件定位操作seekp方法定位各个记录文件写入的位置,将上述三条商品信息写入文件。
实例程序主要由两个文件组成,头文件chapter1904.h主要实现商品信息结构体定义以及记录文件类的声明定义;源文件chapter1904.cpp实现具体记录类各个成员方法定义,另外主函数执行也放在源文件中。头文件中根据商品基本信息,定义结构体recordData采用各个成员变量来表示商品信息各个不同字段,最后一个结构体对象表示一个商品记录信息。
记录文件类主要包含三个部分,公共接口声明定义的构造和析构函数;公共接口声明定义的setFileName(设置文件名方法成员)、openFile(打开或创建文件方法成员)、closeFile(关闭文件方法成员)、writeFile(写文件方法成员);私有数据成员,主要包含m_fileName(文件名)、m_path(文件路径名)、m_ofstream(输出文件流对象)和m_oOpenFlag(输出文件流标记)。
记录文件类构造函数RecordFile主要实现功能,用于初始化文件输出流对象标记的值。通常定义记录文件类对象实例时,调用该构造函数,初始化输出文件流对象标记m_oOpenFlag值为0。记录文件类析构函数~ RecordFile主要实现功能,析构时关闭打开的文件,尽管可能程序外部调用时会显式的调用关闭文件操作,但是可能类的使用者没有注意显式调用关闭文件,此时对象生命周期结束后会默认调用其析构函数,最后关闭文件。
记录文件类设置文件名方法setFileName中,根据传入的文件路径名和文件名参数,方法内部根据判断在文件名前添加”/”符号,避免给出路径没有该符号的情况。
记录文件类打开或创建文件方法中,首先判断输出文件流标记m_oOpenFlag是否为1,为1表示已经创建或打开过文件。此时直接返回true告知调用者,该文件已经创建或者打开。如果输出文件流标记m_oOpenFlag为0,则表明该输出文件流对象m_ofstream并未打开或创建文件,随后调用输出流文件打开或创建文件方法open。最后判断打开文件输出流是否成功,成功则设置输出文件流标记m_oOpenFlag为1,返回true。
记录文件类关闭文件方法中,通过判断输出流文件标记,如果该标记为1表明该输出流对象存在并且成功打开或创建文件。随后执行关闭输出文件方法,同时调用clear方法清空,设置输出流对象标记为0。
记录文件类写文件方法中,通过传入商品信息记录结构对象实现定位写入功能。首先输出文件流对象通过seekp方法,根据记录中的编号id-1的方式确定起始位置,定位大小为一个商品信息结构体的记录数据。判断输出文件流标记m_oOpenFlag,为0则表示输出文件流对象无效,直接返回false表明写文件失败,否则调用写文件write方法。通过reinterpret_cast方法将记录数据转换为const char*类型,写入大小为该商品结构体大小的数据sizeof(recordData)。
实例主程序中,定义商品信息结构对象record,定义记录文件类对象recordFile。通过记录文件类对象recordFile调用其设置文件名成员函数setFileName,传入路径名和相应的文件名,通常实际应用中该部分参数都可以以配置文件读取方式来设置。设置完文件名字后,根据该文件名调用记录文件类对象中成员方法openFile打开或创建该文件。
如果该目录下没有指定文件名的文件,则新创建传入文件名为clientFile.txt。提示用户输入记录信息,这里限定为10条记录信息。通过cin对象输入商品信息编号,通过while结构判断商品信息编号是否在限定的范围内,在则输入该商品信息编号随后的商品名、商品厂家和商品价格字段信息。通过记录类文件对象的写文件成员,传入记录结构对象,将记录信息根据定位写入文件。
记录写入指定文件后,继续提示输入下一条记录信息,随后输入商品信息编号。如果超出限定范围,则跳出循环体,通过记录类文件类对象调用其关闭文件方法,最终关闭文件。
19.1.6 Linux下C++记录结构读取文件
上小节的实例程序,了解了随机文件创建以及按照指定位置写入记录等操作的实现过程。本小节主要讲述随机文件的读取数据操作实现过程,同时也验证上小节实例中写入的记录数据正确性,并且验证记录数据能否被正确的读取出来。C++中读取文件可以采用ifstream中提供的read方法来实现,read方法从指定的流的当前位置向对象输入指定字节数。例如如下的实现语句。
infile.read(reinterpret_cast<constchar*>(&record),sizeof(recordData));
从与istream对象infile关联的文件中读取sizeof(recordData)指定的字节数,并且将数据保存在record记录结构中。一个完整的实例如下所示,读者可以通过它完整的理解下随机文件的读取数据操作步骤。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1905.h、chapter1905.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1905
* 源文件chapter1905.h、chapter1905.cpp
* 记录结构文件读取实例
*/
#ifndef RECORDFILE_H_
#define RECORDFILE_H_
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
struct recordData //订单文件记录结构体
{
intid; //订单标号
chargoodsName[1024]; //商品名
charfactoryName[1024]; //商品厂家名称
doubleprice; //商品价格
};
class RecordFile
{
public:
RecordFile();
~RecordFile();
public:
voidsetFileName(const string &path, const string &filename);
boolopenFile();
voidcloseFile();
boolreadFile(recordData &record);
boolisFileEof();
voidshowRecord(ostream& output, const recordData &record);
private:
string m_fileName;
string m_path;
ifstream m_ifstream;
int m_iOpenFlag;
};
#endif
//
#include "chapter1905.h" //引用的记录结构文件
RecordFile::RecordFile()
{
m_iOpenFlag= 0;
}
RecordFile::~RecordFile()
{
closeFile();
}
bool RecordFile::isFileEof()
{
if(!m_ifstream.eof())
{
returntrue;
}
else
returnfalse;
}
void RecordFile::setFileName(const string&path, const string &filename)
{
m_fileName= filename;
m_path = path;
if(strlen(path.c_str()) > 0)
{
if(path.c_str()[path.size() - 1]!='/')
m_path+='/';
m_fileName= m_path + m_fileName;
}
else
{
m_fileName="/" + m_fileName;
}
}
bool RecordFile::openFile()
{
if(m_iOpenFlag==1)
returntrue;
m_ifstream.open(m_fileName.c_str(), ios::in);
if (!m_ifstream)
returnfalse;
m_iOpenFlag = 1;
returntrue;
}
void RecordFile::closeFile()
{
if(m_iOpenFlag == 1)
{
m_ifstream.close();
m_ifstream.clear();
m_iOpenFlag= 0;
}
}
bool RecordFile::readFile(recordData &record)
{
if(m_iOpenFlag== 0)
{
returnfalse;
}
m_ifstream.read(reinterpret_cast<char*>(&record),sizeof(recordData));
returntrue;
}
void RecordFile::showRecord(ostream& output,const recordData &record)
{
output<<record.id<<""<<record.goodsName<<""<<record.factoryName<<""<<record.price<<'\n';
}
/*主程序入口*/
int main()
{
recordDatarecord;
RecordFilerecordFile;
recordFile.setFileName("/billing/wangfeng/linux_c++/chapter19/chapter1905/bin","clientFile.txt");
if(!recordFile.openFile())
{
cout<<"Openfile fail!"<<endl;
}
recordFile.readFile(record);
while(recordFile.isFileEof())
{
if(record.id!= 0)
{
recordFile.showRecord(cout,record);
}
recordFile.readFile(record);
}
recordFile.closeFile();
return0;
}
2.编辑makefile
Linux平台下需要编译源文件为chapter1905.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1905.o
CC=g++
chapter1905: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1905
clean:
rm-f chapter1905 core $(OBJECTS)
submit:
cp-f -r chapter1905 ../bin
cp-f -r *.h ../include
上述makefile文件使用了实例模板。之所以其中采用变量定义替换的方式,目的就是为了方便编译不同程序文件的替换。从makefile工程文件中可以看出,布局是相同的。不同的地方仅仅是代码的文件名、生成可执行程序的名称等,大大方便了每次都要重新编写一遍编译命令的编辑方式。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至实例bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$ make
g++ -c -ochapter1905.o chapter1905.cpp
g++ chapter1905.o -g -o chapter1905
[developer @localhost src]$ make submit
cp -f -r chapter1905 ../bin
cp -f -r *.h ../include
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter1905
1 icecream factory 2.5
2 apple factory 1.3
3 orange factory 1.4
4.剖析程序
本实例程序同样通过记录结构文件类,封装实现读取记录文件的相关方法演示记录文件读取操作。从上述程序执行结果来看,执行生成的可执行程序后,将前面记录写入后的文件的信息都读取出来,并且打印在屏幕上。
实例程序主要由两个文件组成,头文件chapter1905.h主要实现商品信息结构体定义以及记录文件类的声明定义;源文件chapter1905.cpp实现具体记录类各个成员方法定义,另外主函数执行也放在源文件中。头文件中根据商品基本信息,定义结构体recordData采用各个成员变量来表示商品信息各个不同字段,最后一个结构体对象表示一个商品记录信息。
记录文件类主要包含三个部分,公共接口声明定义的构造和析构函数;公共接口声明定义的setFileName(设置文件名方法成员)、openFile(打开或创建文件方法成员)、closeFile(关闭文件方法成员)、readFile(读取文件方法成员)、isFileEof(判断是否到文件尾)、showRecord(显示读取记录);私有数据成员,主要包含m_fileName(文件名)、m_path(文件路径名)、m_ifstream(输入文件流对象)和m_iOpenFlag(输入文件流标记)。
记录文件类构造函数RecordFile主要实现功能,用于初始化文件输出流对象标记的值。通常定义记录文件类对象实例时,调用该构造函数,初始化输出文件流对象标记m_iOpenFlag值为0。记录文件类析构函数~RecordFile主要实现功能,析构时关闭打开的文件,尽管可能程序外部调用时会显式的调用关闭文件操作,但是可能类的使用者没有注意显式调用关闭文件,此时对象生命周期结束后会默认调用其析构函数,最后关闭文件。
记录文件类设置文件名方法setFileName中,根据传入的文件路径名和文件名参数,方法内部根据判断在文件名前添加”/”符号,避免给出路径没有该符号的情况。
记录文件类打开或创建文件方法中,首先判断输入文件流标记m_iOpenFlag是否为1,为1表示已经打开过文件。此时直接返回true告知调用者,该文件已经打开。如果输入文件流标记m_iOpenFlag为0,则表明该输入文件流对象m_ifstream并未打开文件,随后调用输入流文件打开文件方法open。最后判断打开文件输入流是否成功,成功则设置输入文件流标记m_iOpenFlag为1,返回true。
记录文件类关闭文件方法中,通过判断输入流文件标记,如果该标记为1表明该输入流对象存在并且成功打开或创建文件。随后执行关闭输入文件方法,同时调用clear方法清空,设置输入流对象标记为0。
记录文件类读取文件方法中,判断输入流文件标记,如果为0则直接返回失败。正常情况下,通过输入流文件对象调用文件读取方法read,通过reinterpret_cast转换结构体为char*类型,读取大小为商品信息结构的记录数据。
记录文件类显示记录方法中,通过传入输出流对象和结构记录数据参数,内部按照一定格式打印输出从记录文件读取的记录信息。
通过整个的19.1章节大致的讲述了顺序文件以及随机文件的创建和读写操作,为Linux下的文件系统开发奠定了一个基本知识基础,通过几个完整的实例运行,相信会对文件部分操作有一个大致的理解。顺序文件和随机文件在日常的开发工作中都比较的常用,区别只是应用的场合不同而已。顺序文件主要应用于日常的读写文件操作,而随机文件则应用于对于文件快速存取以及在指定位置写入数据等要求的场合。两种文件的方式视实际的项目场景需要作出选择,但是记住一点,项目的开发应该是在符合要求的情况下做出简单实现的一方选择,不要一昧的追求复杂实现!
19.2 Linux下C++实现文件无格式操作
标准C++文件库提供文件处理流类类型,用于对外提供文件处理操作接口。其中除了提供基本的文件操作之外,针对文件数据处理中特殊需要的情况,提供了文件字符、数据行以及二进制数据块等多种与记录格式无关的文件操作。标准库通过自定义类型封装了内部复杂的处理操作,通过简洁的对外接口,使得用户开发应用中,针对文件操作处理变得更加方便快捷。
19.2.1 Linux下C++文件字符数据写入
对于标准的C++库提供的以字符数据方式操作文件的方法,提供两类接口方法用于文件流方式处理字符数据读写应用。下面首先介绍标准库针对文件处理中提供的字符数据写入处理方法,方法原型如下。
#include <fstream>
ostream& put(char ch);
字符处理文件方法接口由标准库C++文件流类提供,方法接口比起低级文件相对应的操作具有接口更加简洁易操作的优势。方法接口put用于将一个字符数据写入到一个打开的文件流中,其中参数ch是需要写入的字符。
标准C++库封装实现文件流处理类,提供了类似低级文件处理调用方法的功能。为了直观的理解标准库提供的文件字符操作接口,下面将会给出一个完整的操作应用实例,该实例首先实现按字符数据写入的方式写入指定单个字符数据到文件中,实例代码编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1906.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示
/**
* 实例chapter1906
* 源文件chapter1906.cpp
* 文件字符数据读写实例
*/
//实例源文件chapter1906.cpp
#include <iostream>
#include <fstream>
using namespace std;
/*主函数入口*/
int main()
{
ofstreamoutfile("recordStreamFile.txt",ios::out); //文件输出流对象定义,主要以输出方式打开文件
charch; //定义字符变量ch
cout<<"pleaseinput a char:"<<endl; //提示从屏幕输入字符变量
cin>>ch; //通过cin对象输入字符变量值
while(ch!= '#') //判断输入的字符是否为#
{
outfile.put(ch); //调用输出流对象put方法向文件写入输入的字符变量
cin>>ch; //继续输入字符变量
}
outfile.close(); //文件输出流对象调用close方法关闭文件
return0;
}
本实例程序组成很简单,主要在主函数内部实现针对文件字符数据写入操作。主流程都在主函数内部实现,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下需要编译的源文件为chapter1906.cpp,makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1906.o
CC=g++
chapter1906: $(OBJECTS)
$(CC)$(OBJECTS) -o chapter1906
clean:
rm -fchapter1906 core $(OBJECTS)
submit:
cp -f -rchapter1906 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
在当前的shell下,执行make命令生成可执行程序,随后执行make submit命令提交程序文件至该实例bin目录中,通过cd命令定位至该实例bin目录,执行程序文件运行结果如下所示。
[developer@localhost src]$make
g++ -c -o chapter1906.o chapter1906.cpp
g++ chapter1906.o -g -o chapter1906
[developer@localhost src]$make submit
cp -f -r chapter1906 ../bin
cp -f -r *.h ../include
[developer@localhost src]$cd../bin
[developer@localhost src]$./chapter1906
please input a char:
a
b
c
d
#
4.程序剖析
本实例主要演示通过标准C++文件流提供字符处理put方法,通过输出文件流对象向文件循环写入字符数据的应用情况。实例中首先定义文件输出流ofstream对象outfile,通过采用构造函数方式以文件输出模式打开或者创建名为recordStreamFile.txt文本文件。
随后通过cout输出流对象提示从键盘输入字符数据,通过while循环控制结构,根据结构中判断条件,设定输入字符数据中遇到‘#’则退出该循环,继续下一步程序执行。上述程序从键盘依次输入字符数据‘a’、‘b’、‘c’与‘d’四个字符数据之后以#字符结束输入,每次输入字符数据直接通过输出流对象调用put方法,将输入的字符数据作为参数实参传入该方法,将字符数据顺序写入相应文件。
19.2.2 Linux下C++文件字符数据读取
与字符数据写入相对应,标准的C++库中同样提供了字符数据方式读取文件的方法,该方法接口相当简洁明了,分别从关联相应文件的输入流中读取文件相应字符数据,该方法接口原型如下所示。
#include <fstream>
istream& get(char &ch);
该方法通过标准文件处理流类提供,主要作用用于从当前操作的文件流关联文件中按字符读取数据。该方法在标准库中有三个以上的重载实现,分别针对不同种情况提供更多的读取文件的功能。鉴于本小节主要说明字符数据在文件中的操作处理情况,所以此处暂不介绍重载实现的get方法接口情况。
对于标准文件库提供的单个字符从文件读取的方法get的学习,依然会采用一个完整的文件字符读取实例,使用上述提供的标准库方法接口,实现相应的功能。该实例代码编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1907.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示
/**
* 实例chapter1907
* 源文件chapter1907.cpp
* 文件字符读写操作实例
*/
//源文件chapter1907.cpp
#include <iostream>
#include <fstream>
using namespace std;
/*主程序入口*/
int main()
{
ifstreaminfile("recordStreamFile.txt",ios::in); //文件输入流对象,以输入的方式打开指定文件
charch; //定义字符变量ch
cout<<"chardata:"; //提示输出读取的字符
infile.get(ch); //通过输入流对象的get方法读取文件中的字符放入ch
while(!infile.eof()) //判断是否读取到文件尾部
{
cout<<ch<<""; //打印输出读取的字符
infile.get(ch); //如果没有读到文件尾部,则继续读取下一个字符
}
cout<<endl; //提示换行
infile.close(); //处理结束,关闭文件操作
return0;
}
本实例程序组成很简单,主要在主函数内部实现针对文件字符数据读取操作。主流程都在主函数内部实现,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下需要编译的源文件为chapter1907.cpp,相关makfile工程文件命令编辑如下所示。
OBJECTS=chapter1907.o
CC=g++
chapter1907: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1907
clean:
rm -fchapter1907 core $(OBJECTS)
submit:
cp -f -rchapter1907 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后make submit提交命令将该程序文件拷贝至本实例bin目录,通过cd命令定位至bin目录后,运行程序读取文件结果如下所示。
[developer@localhost src]$make
g++ -c -ochapter1907.o chapter1907.cpp
g++ chapter1907.o -g -o chapter1907
[developer@localhost src]$make submit
cp -f -r chapter1907 ../bin
cp -f -r *.h ../include
[developer@localhost src]$cd ../bin
[developer@localhost src]$cp recordStreamFile.txt../../chapter1907/bin
[]$ll
总用量 20
-rwxr-xr-x 1 ocs linkage 14922 1月 7 03:13 chapter1907
-rw-r--r-- 1 ocs linkage 4 1月 7 03:17 recordStreamFile.txt
[developer@localhost src]$./chapter1907
char data:a b c d
本实例主要通过操作标准文件库提供的字符读取文件的方法get,演示文件中字符读取的过程。该实例中依然采用输入文件流对象,通过输入文件流对象关联打开或创建的文件,调用相应的get方法从文件读取字符数据。
主程序中首先通过输入文件流ifstream定义输入流对象infile,采用其构造方法根据传入的参数以输入文件方式打开或创建文本文件recordStreamFile.txt。随后提示输出打印读取数据信息,while循环结构中通过infile对象调用其eof方法接口判断文件读取是否到达尾部,如果已经达到则退出循环体,并打印输出读取的字符数据,最后不要忘记关闭打开的文件。
19.2.3 Linux下C++文件字符串行数据读写
标准C++文件库中写入读取数据可以使用重载输入输出操作符,也可以使用write与read方法来读写记录或者数据块。另外标准库还提供了专门针对字符数据文件读写操作,本小节主要介绍文件无格式字符串行数据读写操作。标准库对于字符串行读取操作主要提供了方法geline,该方法原型如下所示。
#inlcude <fstream>
istream& getline(char *buffer,streamsize num);
istream& getline(char *buffer,streamsizenum,char delim);
上述方法原型中主要提供两个重载实现的getline方法,第一个方法主要用于从文件读取长度为num的数据存放至buffer指针指向的缓冲区中;而第二个方法则增加了分隔符号,在指定字符串大小之间以分隔符为界限读取分隔符之前的数据。
标准库getline方法为文件输入流类提供,读取文件中字符串数据放入指定的缓冲区中。该方法操作中需要指定读取文件字符串数据大小,该方法有几种使用情况。第一当指定的文件数据大小足够时候,从当前输入流读取num-1大小字符串数据成行;第二默认情况下,文件中数据读取在指定的大小内遇到换行符即读取结束;第三当在指定大小内与到文件结束符,则直接读取该结束符之前的数据成行放入缓冲区中。第四则为指定大小时,指定分割符号,按照分割符号在指定大小内以分隔符为结束读取数据。
下面将会通过一个应用文件行数据读取方法的完整实例,演示两种文件行数据读取方法的使用情况,该实例代码编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1908.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
//源文件chapter1908.cpp
#include <iostream>
#include <fstream>
using namespace std;
/*读取文件单行数据操作*/
void readFile(ifstream &infile,string&buffer)
{
charbuff[1024+1]; //定义缓冲区空间
memset(buff,0,1024+1); //缓冲区初始化为0
if(!infile.eof()) //判断是否读取至文件尾部
{
infile.getline(buff,1024,'\n'); //通过文件输入流对象调用getline方法读取文件单行数据,并 //将其放入缓冲区buff
buffer= buff; //将buff内容赋给传入的缓冲区参数buffer
}
else
{
cout<<"fileis end!"<<endl; //如果已经读取至文件底部则打印提示信息
}
}
/*读取文件指定长度数据*/
void readFile(ifstream &infile,string&buffer,const int &length)
{
charbuff[1024+1]; //定义缓冲区空间
if(!infile.eof()) //判断读取文件是否到尾部
{
infile.getline(buff,length,'\n'); //通过文件输入流对象调用getline方法根据传入指定读取数 //据长度,读取单行数据,将其放入缓冲区buff
buffer= buff; //将buff内容赋给传入的缓冲区参数buffer
}
else
cout<<"file isend!"<<endl; //如果已经读取至文件底部则打印提示信息
}
/*主程序入口*/
int main()
{
stringdata; //定义字符串变量data,用于表示输入数据
stringbuffer; //定义字符串变量buffer,表示读取文件数据缓冲区
ofstreamoutfile("temp.txt",ios::out); //文件输出流对象,以输出的方式打开指定文件
if(!outfile) //判断文件创建或者打开是否成功
{
cout<<"Can'topen file!"<<endl; //文件打开不成功输出提示
}
cout<<"Pleaseinput data:"<<endl; //提示从键盘输入写入文件的数据
for(inti = 0;i < 2;i++) //循环两次写入文件的字符串数据
{
cin>>data; //从键盘输入数据
outfile<<data; //通过输出流对象的<<重载方法,将该数据写入文件
}
outfile.close(); //写入文件操作完毕后关闭
ifstreaminfile("temp.txt",ios::in); //文件输入流对象,以输入的方式打开指定文件
readFile(infile,buffer); //调用单行读取文件方法
cout<<buffer<<endl; //输出读取到单行的字符串数据
infile.close(); //关闭输入流文件操作
buffer.clear(); //缓冲区清空
ifstreaminfile1("temp.txt",ios::in); //文件流输入对象,以输入的方式打开文件
readFile(infile1,buffer,10); //调用指定读取文件数据长度方法
cout<<buffer<<endl; //输出读取到单行指定长度的字符串数据
infile1.close(); //关闭输入流文件操作
return0;
}
本实例程序组成很简单,主要在主函数内部实现针对文件字符串数据读写操作。主流程都在主函数内部实现,封装了按行读取文件数据和读取文件指定长度数据的函数,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下需要编译的源文件为chapter1908.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1908.o
CC=g++
chapter1908: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1908
clean:
rm -fchapter1908 core $(OBJECTS)
submit:
cp -f -rchapter1908 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息。
3.编译运行程序
当前shell下执行make命令,编译生成可执行程序,随后通过make submit命令提交程序文件至当前实例bin目录,通过cd命令进入当前实例bin目录后,执行程序文件该程序运行结果如下所示。
[developer@localhost src]$make
g++ -c -ochapter1908.o chapter1908.cpp
g++ chapter1908.o -g -o chapter1908
[developer@localhost src]$make submit
cp -f -r chapter1908 ../bin
cp -f -r *.h ../include
[developer@localhost src]$cd../bin
[developer@localhost src]$./chapter1908
Please input data:
hello,jack!
hello,lini!
hello,jack!hello,lini!
hello,jac
4.程序剖析
本实例主要演示标准文件库中针对文件字符串行数据处理方法应用,实例中主要通过重载实现两个readFile方法来应用getline提供的两个重载方法实现文件中字符串数据行读取操作。两个重载方法中,第一个readFile方法参数主要以传入相应的输入文件流以默认数据大小读取文件数据缓冲区;第二个方法在其基础之上加入长度的参数,按照指定长度来读取文件中的数据行。
默认情况下缓冲区大小为1024,重载实现的readFile内部首先会定义缓冲区,并使用memset方法初始化为0。判断输入文件流对象是否成功,成功则调用getline方法来读取文件中的字符串数据形成一行。
主程序中定义两个字符串对象,分别表示写入文件数据缓冲区与读取文件数据缓冲区。首先根据输出流对象以输出方式创建或打开temp.txt文本文件,随后通过循环控制向文件写入两条数据,并关闭输出流打开的文件。定义两个输入流对象infile与infile1,分别用来演示两种getline方法的使用。
首先调用readFile,传入输入流对象infile以及输出存放缓冲区,函数内部根据默认情况读取1024大小的字符串行数据,并且以换行符‘\n’来结束字符串行数据。根据输入的两条数据,第一个方法从文件中循环读取指定大小行数据,直到遇到文件结束符。由于第一个方法没有指定大小,采用默认1024大小数据,那么该方法读取的结果为将两个输入的记录连在一起成行放入缓冲区中,原因在于输入数据时是连续存放的,并没有换行。
而第二个方法则指定了文件中一次行读取的大小,该大小为10字节,调用之后从文件一条记录只能读取10个字节数据成行放入缓冲区中。getline方法相对读取字符串行比较灵活,初学者可以通过上述实例修改分割符,来测试学习该方法全部的功能。
19.3 Linux下C++实现文件定位
文件处理实际应用中可能需要快速定位到指定位置进行文件某部分数据的操作,而不需要从文件开始位置来操作文件。比如对于文件中定长结构的记录数据,每次访问都需要定位到正确的位置,才能访问到相应的完整记录信息。
标准C++文件流处理类提供了两类方法专门应用于文件定位操作,在介绍这两类文件定位操作之前需要介绍流处理提供的另外两个相关联的方法,该类方法主要用于返回获取当前流指针位置。根据获取当文件流处理位置,可以将当前指针位置用于定位方法中根据实际需要定位文件,该类方法原型如下。
#include <fstream>
pos_type tellg();
pos_type tellp();
上述两个获取当前流指针位置的方法操作都没有参数,返回值类型为pos_type,根据不同的系统内部遵循的标准来说明该类型,标准c++中该类型为一个整型数,表示当前流指针的位置。
在所有的C++文件流处理中,都会包含到一个与文件关联的流指针。在ifstream输入流中,存在一个称为get pointer的流指针,用于指向文件中下一个被读取的元素。而在ofstream输出流中,同样存在一个put pointer流指针,指向写入的下一个元素位置。自然继承至上述流而来的fstream类也同时继承了get pointer与put pointer流指针操作。为此tellg与tellp方法主要作用用于获取并返回输出输入流中相应的流指针位置。
Linux下C++文件定位则是通过seekg与seekp两个方法接口来实现的,两个方法接口主要使用前面tellg与tellp获取到当前流指针指向的位置,根据获取位置来修改并定位文件,该类方法接口原型如下。
#include <fstream>
istream& seekg(off_type offset,ios::seekdirorigin);
istream& seekg(pos_type position);
ostream& seekp(off_type offset,ios::seekdirorigin);
ostream& seekp(pos_type position);
标准库中文件定位方法共两类,每个方法有两个重载实现。第一个方法使得当前文件流中的文件流指针位置从文件开始位置定位到一个绝对位置处;而第二个方法显得更加的灵活,通过后面提供的origin参数可以指定文件偏移位置中具体流指针从哪一个位置处开始计算,其中参数offset为指定开始文件位置后的偏移量。
origin参数一共提供了三类类别,分别表示文件不同位置作为开始。三个类别分别为ios::beg、ios::cur、ios::end。第一个表示从流中开始的位置计算偏移量;第二个表示从流中指针指向的当前位置开始计算偏移量;而第三种则表示从流末尾处开始计算文件中需要定位的偏移量。
通过上述简介的接口介绍,下面将会通过一个完整的文件定位操作实例,来演示标准C++文件库提供的文件定位操作使用方法。该实例包含两个方法接口的使用,实例代码编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1910.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1910
* 源文件chapter1910.cpp
* 文件定位操作实例
*/
//源文件chapter1910.cpp
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
struct recordData //文件数据记录结构
{
intid; //记录编号
stringdata; //记录数据
};
/*定位写文件方法*/
void writeFile(recordData &record)
{
ofstreamoutfile("testFile.txt",ios::out); //输出流对象,以输出方式创建或打开指定文件
if(!outfile) //判断文件创建或打开是否成功
{
cout<<"Can'topen file!"<<endl; //不成功则打印提示信息
}
for(inti = 0; i < 3; i++) //循环3次生成记录写入随机文件
{
record.id= i; //记录对象编号赋值
cout<<"pleaseinput data:"<<endl; //提示从键盘输入数据
cin>>record.data; //输入对应结构体记录的数据
outfile.write(reinterpret_cast<constchar*>(&record),sizeof(recordData)); //写入记录到文件
}
outfile.close(); //输出流文件操作完毕关闭
}
/*主程序入口*/
int main()
{
intfirstPos,endPos; //定义位置变量
recordDatarecord; //定义记录结构对象
writeFile(record); //调用记录数据写入文件函数
ifstreaminfile("testFile.txt",ios::in); //定义文件输入流对象
if(!infile) //判断输入流对象打开文件是否成功
{
cout<<"Can'topen file!"<<endl; //文件打开不成功则打印出错信息
}
firstPos= infile.tellg(); //获取文件流中指针指向位置,为初始位置
cout<<"Filefirst pos:"<<firstPos<<endl; //打印文件数据首位置
infile.seekg(0,ios::end); //设置定位文件流指针指向为文件末尾位置
endPos= infile.tellg(); //获取文件流中指针指向位置,为结束位置
cout<<"File endpos:"<<endPos<<endl; //打印文件数据尾部位置
cout<<"testFile.txtsize:"<<(endPos-firstPos)<<"bytes!"<<endl; //计算文件大小,通过首尾位置相减计算
return 0;
}
本实例程序组成很简单,主要在主函数内部实现针对文件数据定位操作。主流程都在主函数内部实现,封装了按记录结构写文件的函数,引用了文件操作的相关头文件fstream,具体程序讲解见程序剖析。
2.编辑makefile
Linux平台下需要编译的源文件为chapter1910.cpp,相关makefile工程文件编译命令编辑如下所示。
OBJECTS=chapter1910.o
CC=g++
chapter1910: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1910
clean:
rm -fchapter1910 core $(OBJECTS)
submit:
cp -f -rchapter1910 ../bin
cp -f -r*.h ../include
上述makefile文件套用前面的模板格式,主要替换了代码文件、程序编译中间文件、可执行程序等。在编译命令部分-g选项的加入,表明程序编译同时加入了可调式信息
3.编译运行程序
当前shell下执行make命令,生成可执行程序文件,随后通过make submit命令提交程序文件至本实例bin目录,通过cd命令定位至bin目录,执行该程序文件运行结果如下所示。
[developer@localhost src]$make
g++ -c -ochapter1910.o chapter1910.cpp
g++ chapter1910.o -g -o chapter1910
[developer@localhost src]$make submit
cp -f -r chapter1910 ../bin
cp -f -r *.h ../include
[developer@localhost src]$cd../bin
[developer@localhost src]$./chapter1910
please input data:
hello!
please input data:
jack!
please input data:
haha
File first pos:0
File end pos:24
testFile.txt size:24 bytes!
4.剖析程序
本实例程序主要演示采用标准库提供的定位方法应用,使用定位操作计算刚刚写入数据文件的大小。程序主要由两个函数组成,一个为创建文件并负责向其中写入数据的方法,另一个为程序中主函数操作。
写文件操作函数中根据传入的结构体对象引用,通过输出流创建或打开文件,调用其write方法写入这个记录结构。循环控制中负责写入结构体记录数据,主要设定为循环写入3条记录。主程序中,首先定义两个长整型变量firstPos与endPos分别表示文件流中开始与结束的位置。随后定义结构体记录对象record,调用写入文件操作方法writeFile,根据传入参数写入指定记录数据。
之后定义输入流对象infile,以输入方式打开文件。首先判断文件打开是否成功,如果成功打开通过调用定位操作中tellg方法,获取文件流此时指针指向的位置并打印输出。调用定位输入流指针指向位置方法seekg,将其设定为从文件末尾,指向偏移量为0的位置,即此时文件流指针指向在文件中数据末尾位置。通过输入流再次调用tellg方法,获取当前文件流指针指向位置并打印输出。
由于两个位置变量分别获取到了文件流指针指向的开始与结束位置,通过两者之间相减运算,可以求出当前文件字节数大小。由于结构体在32位Linux系统下为4字节对齐,因此拥有两个数据成员的结构体每个大小为8个自己。上述实例程序向文件中写入3条结构体记录,因此最后通过计算文件大小应该为24字节。
19.4 实例19-1 Linux下C++文件操作类封装
通过本上上面小节针对C++文件操作的介绍,下面将会通过类类型的方式封装常见文件的操作。该文件类主要包文件创建、打开、写入以及读取等基本操作方法接口。通过将封装实现的文件操作类编译成静态库,只需要在应用程序中包含需要使用封装类的头文件,通过编译连接即可直接使用库中文件操作方法。
C++封装实现通用操作成自定义文件类类型,主要包含两个部分,一个为文件常用操作的接口方法成员,另一个为文件操作所需要的数据成员。本实例代码文件编辑如下所示。
1.准备实例
打开UE工具,创建新的空文件并且另存为chapter1911.cpp。该代码文件随后会同makefile文件一起通过FTP工具传输至Linux服务器端,客户端通过scrt工具访问操作。程序代码文件编辑如下所示。
/**
* 实例chapter1911
* 源文件chapter1911.cpp
* 文件操作类封装实例
*/
//文件操作头文件chapter1911.h
#ifndef FILEOPERATOR_H
#define FILEOPERATOR_H
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
#define BUF_SIZE 1024 //定义数据缓冲区大小
enum OPEN_MODE {READ=1,WRITE}; //文件读写模式
/*文件操作类*/
class FileOperator //文件操作类
{
public:
FileOperator(); //文件操作类构造函数声明
~FileOperator(); //文件操作类析构函数声明
stringgetFileName(); //文件操作类获取文件名方法
void setFileName(const string &fileName,conststring &path); //文件操作类设置文件名方法
bool openFile(OPEN_MODE mode); //文件操作类打开文件操作方法
void closeFile(); //文件操作类关闭文件操作方法
int readFile(string &buffer); //文件操作类读取文件操作方法
int readFile(string &buffer,const int&length); //文件操作类读取文件操作重载方法
bool writeFile(const string &data); //文件操作类写入文件操作方法
private:
string m_fileName; //文件名
string m_filePath; //路径名
string m_fullName; //全文件名,包含路径
ifstream m_infile; //输入文件流对象
ofstream m_outfile; //输出文件流对象
int m_inFlag; //输入文件打开标记
int m_outFlag; //输出文件打开标记
};
#endif
//文件操作封装类源文件chapter1911.cpp
#include <sys/stat.h>
#include <unistd.h>
#include "FileOperator.h"
/*文件操作类构造函数定义实现*/
FileOperator::FileOperator()
{
m_inFlag= 0; //初始化输入文件打开标记
m_outFlag=0; //初始化输出文件打开标记
}
/*文件操作类析构函数定义实现*/
FileOperator::~FileOperator()
{
closeFile(); //关闭文件方法调用
}
/*文件操作类获取文件名方法定义实现*/
string FileOperator::getFileName()
{
returnm_fileName; //直接返回文件名称
}
/*文件操作类设置文件名定义实现*/
void FileOperator::setFileName(const string&fileName,const string &path)
{
m_fileName= fileName; //根据传入参数,给数据成员文件名赋值
m_filePath= path; //根据传入参数,给数据成员路径名赋值
if(strlen(path.c_str()) > 0) //判断传入路径名是否有效
{
if(path.c_str()[path.size() - 1]!='/')
m_filePath+='/';
m_fullName= m_filePath + m_fileName;
}
else
{
m_fullName ="/" + m_fileName;
}
}
/*文件操作类打开文件方法定义实现*/
bool FileOperator::openFile(OPEN_MODE mode)
{
switch(mode) //根据传入的文件打开模式选择执行项
{
caseREAD: //如果是读取方式打开
if(m_inFlag==1) //判断输入流标记值,如果为1表明文件已经打开
return true; //直接返回成功即可
m_infile.open(m_fullName.c_str(),ios::in); //如果标记不为1,则重新使用输入流对象打开文件
if(!m_infile) //判断文件打开对象是否成功
return false;
m_inFlag= 1; //打开后需要将标记设置为1,表示文件已经被打开
returntrue;
break;
caseWRITE: //如果是写入方式打开
if(m_outFlag==1) //判断输出流标记值,如果为1表明文件已经打开
returntrue;
m_outfile.open(m_fullName.c_str(),ios::out); //如果标记不为1,使用输出流对象打开文件
if(!m_outfile) //判断输出流文件对象打开文件是否成功
returnfalse;
m_outFlag= 1; //同时置上文件打开标记
returntrue;
break;
default:
break;
}
return false;
}
/*文件操作类关闭文件方法定义实现*/
void FileOperator::closeFile()
{
if (m_inFlag == 1) //如果输入流打开文件标记为1,表明文件已打开
{
m_infile.close(); //输入流对象调用close成员关闭文件
m_infile.clear(); //清除当前流中数据
m_inFlag= 0; //设置输入流文件标记值为0
}
if (m_outFlag == 1) //如果输出流打开文件标记为1,表明文件已打开
{
m_outfile.flush(); //输出流对象调用flush方法刷新当前缓冲区
m_outfile.close(); //关闭输出流文件
m_outfile.clear();
m_outFlag= 0; //重置标记值
}
}
/*文件操作类读取文件方法定义实现*/
int FileOperator::readFile(string &buffer)
{
char buff[BUF_SIZE+1]; //定义缓冲区
int len; //
memset(buff,0,BUF_SIZE+1);
if (m_inFlag==0)
{
return -1;
}
if(!m_infile.eof())
{
m_infile.getline(buff,BUF_SIZE,'\n');
len = strlen(buff);
if (len == 0)
return 0;
buffer.erase();
buffer = buff;
return 1;
}
else
{
return0;
}
}
/*文件操作类指定长度读取操作方法定义实现*/
int FileOperator::readFile(string&buffer,const int &length)
{
char buff[BUF_SIZE+1];
int len;
if (m_inFlag == 0)
{
return -1;
}
if(!m_infile.eof())
{
m_infile.getline(buff,length,'\n');
len= strlen(buff);
if(len== 0)
return0;
buffer= buff;
return1;
}
else
return0;
}
/*文件操作类写入文件操作方法定义实现*/
bool FileOperator::writeFile(const string&data)
{
if(m_outFlag==0)
{
return false;
}
m_outfile<<data;
returntrue;
}
本实例主要采用C++语言封装了文件基本操作类FileOperator,该类主要包含头文件声明与源文件实现两个部分。FileOperator类中主要包含了设置文件名(setFileName)、打开文件(openFile)、关闭文件(closeFile)、写文件(writeFile)、读文件(readFile)等一系列的文件基本操作方法,作为公开接口提供给外部使用。具体程序讲解见程序剖析部分。
2.编译静态库
Linux平台下需要创建为静态库的源文件为chapter1911.cpp,文件操作静态库创建过程如下所示。
[developer@localhost file]$ g++ -O -c chapter1911.cpp
[developer@localhost file]$ ar -rsvlibFileOperator.a chapter1911.o
a - chapter1911.o
[developer@localhost file]$ ll *.a
-rwx------ 1 root root 11976 Sep 7 2008libFileOperator.a
文件基本操作封装类创建为静态库libFileOperator.a,下面通过应用实例中编译连接该静态库,利用静态库中提供的文件操作类类型,在实际应用中采用该类类型操作文件,该实例代码编辑如下所示。
//测试文件操作类类型源文件testFileOperator.cpp
#include "chapter1911.h"
/*主程序入口*/
int main()
{
stringpath,name; //定义字符串变量分别表示文件路径、文件名
stringdata,buffer; //定义数据字符串变量及缓冲区
FileOperatorfile1; //文件操作类对象1定义
cout<<"Pleaseinput File path:"<<endl; //提示从键盘输入文件路径
cin>>path; //输入文件路径
cout<<"Pleaseinput File name:"<<endl; //提示从键盘输入文件名
cin>>name; //输入文件名
file1.setFileName(name,path); //通过文件操作对象调用其设置文件名方法,设置全名
file1.openFile(WRITE); //通过文件操作对象调用其打开文件方法,以文件写入方 //式打开文件
cout<<"Pleaseinput File data:"<<endl; //打印从键盘输入文件数据提示
cin>>data; //输入文件数据
file1.writeFile(data); //通过文件操作对象调用写入文件方法,根据传入的数据 //写入文件
file1.closeFile(); //文件操作完毕后调用其关闭方法关闭文件操作
FileOperatorfile2; //文件操作类对象2定义
cout<<"Pleaseinput File path:"<<endl; //提示从键盘输入文件路径
cin>>path; //输入文件路径
cout<<"Pleaseinput File name:"<<endl; //提示从键盘输入文件名
cin>>name; //输入文件名
file2.setFileName(name,path); //通过文件操作对象调用其设置文件名方法,设置全名
file2.openFile(READ); //通过文件操作对象调用其打开文件方法,以文件读取的 //方式打开文件
cout<<"outputFile data:"<<endl; //打印读取文件数据提示
file2.readFile(buffer); //通过文件操作对象调用其读取文件方法,传入缓冲区变 //量,将数据从文件读取至其中
cout<<buffer<<endl; //打印输出读取到的缓冲区数据
return0;
}
3.编译运行程序
Linux平台下需要编译的源文件为testFileOperator.cpp,该实例编译连接文件操作静态库使用如下。
[developer@localhost file]$ g++ -O -otestFileOperator testFileOperator.cpp ./libFileOperator.a
[developer@localhost file]$ ./testFileOperator
Write File operator:
Please input File path:
/mnt/hgfs/share/worktest/c/file
Please input File name:
test.txt
Please input File data:
hello,C++!
Read File operator:
Please input File path:
/mnt/hgfs/share/worktest/c/file
Please input File name:
test.txt
output File data:
hello,C++!
4.程序剖析
封装实现的文件操作类创建为对应的静态库libFileOperator.a,随后实例程序testFileOperator主要用于连接并使用该文件操作静态库,使用文件操作类类型提供的方法接口,在实际应用中操作文件。
文件基本操作封装类中提供的基本方法详细说明可以通过源文件注释来理解,实例应用程序中主要演示了采用文件操作类型创建或打开文件,并且使用相应的操作方法向文件中写入以及读取数据。
实例程序中主要采用文件路径全名方式创建打开对应的文件,该路径与文件名实际应用中可以根据读取配置文件获取。本实例中主要通过从键盘输入,来创建或打开操作对应的文件。主程序中首先定义字符串对象path与name表示对应的需要输入的文件路径与文件名。另外定义两个字符串对象data与buffer,分别对应写入文件数据与读取文件数据的变量。随后通过输出提示,从键盘输入当前文件路径与文件名称。
定义文件操作类FileOperator类对象实例file1与file2,分别用来写入数据到文件以及从文件读取数据。在操作文件之前首先通过file1对象调用setFileName方法,通过传入的参数信息设置需要打开文件的全名。随后,通过文件对象file1调用openFile方法,并且指定文件以写入的方式操作。从键盘输入字符串数据data,调用writeFile方法传入该数据变量,向文件中写入从键盘输入的数据,最后调用closeFile方法关闭打开的文件。
而文件操作类FileOperator对象实例file2,主要用于读取文件中数据操作实现。从键盘输入指定读取文件的路径与文件名,以文件可读的方式打开已经存在的文件,设置完需要读取的文件全名之后,调用openFile操作方法,指定以读取的方式打开文件。随后通过文件对象实例file2调用readFile方法,根据传入的缓冲区变量,按行读取文件中的数据,最后打印输出结果。
19.5 小结
Linux系统下文件操作是个比较重要的部分,由于Linux系统从Unix发展而来,一切皆文件的说法在Linux系统中依然存在。日常软件应用开发中涉及文件数据处理也比较的多,本章主要介绍了Linux系统下C++标准文件库针对文件实现提供的操作方法,通过本章的标准C++文件操作方法介绍,初学者可以明显了解到标准C++库提供的文件操作相比低级文件操作库来讲更加容易操作与掌握。
通过上述顺序文件、记录文件以及文件中与格式操作无关的文件字符处理、行处理与二进制块数据处理的介绍,基本能够了解到C++中标准库文件处理的操作方法使用。最后通过封装实现文件基本操作类类型,展示C++语言在面向对象部分的编程思想的优势,同时也提供一种软件工程开发中常见的封装实现组件库的基本方法。初学者可以在此基础上扩展学习。
这篇关于Linux下C++标准文件库编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!