13. 文件处理
13.1 引言
内存中数据的存储是暂时的。文件用于数据的持久化- -数据的永久保留。计算机将文件存储在二次存储设备上,如硬盘、CD、DVD、闪存和磁带。
13.2 文件和流(Files and Streams)
C++将文件简单的看作一系列字节。
每个文件以end-of-file标记或在操作系统维护的管理数据结构中记录的特定字节数结束。当一个文件打开,就创建了一个对象,流就和此对象联系起来。流和对象之间的联系为程序和文件提供了交流通道。
为了文件处理,C++的头文件<iostream><fstream>必须包含。
头文件fstream定义了三个类型来支持文件IO:ifstream从一个给定文件读取数据,ofstream向一个给定的文件写入数据,fstream可以读写给定文件。要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件
每当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。
13.3 向文件写入数据
ofstream类可以向文本文件写入数据。
当文件已经存在的情况下,已有的内容会被清除。
13.4 ofstream类使用的示例
C++不对文件施加任何结构。因此,在C++中不存在类似于记录(可以用几个相关的字段组成一条记录)的概念。必须构造满足应用需求的文件。下面的例子展示了如何在一个文件上施加一个简单的记录结构。
#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
using namespace std;
int main() {
// ofstream constructor opens file
ofstream outClientFile{"clients.txt", ios::out};
// exit program if unable to create file
if (!outClientFile) {//overloaded ! operator
cerr << "File could not be opened" << endl;
exit(EXIT_FAILURE);
}
cout << "Enter the account, name, and balance.\n"
<< "Enter end-of-file to end input.\n? ";
int account; // the account number
string name; // the account owner's name
double balance; // the account balance
// read account, name and balance from cin, then place in file
while(cin>>account>>name>>balance){
outClientFile << account << ' ' << name << ' ' << balance << endl;
cout<<"? ";
}
}
上述代码,创建一个顺序文件,该文件可能用于应收账款系统,以帮助管理其信贷客户欠公司的钱。
13.4.1 文件打开模式
上述代码将数据写入文件,因此我们通过创建ofstream对象打开文件进行输出。向对象的构造函数传入了两个实参,文件名字和文件打开方式(file-open mode)。对于一个ofstream对象,文件打开模式可以是ios::out(the default),用以向文件输出数据。或者ios::app,用以在文件末尾增加数据(不会修改文件中原先存在的任何数据)。在上述代码中,创造了ofstream的对象-outClientFile,和文件clients.txt联系起来。由于没有指定文件的存储路径,最终文件会和程序存放在同一路径。ofstream的构造函数打开文件---这与文件建立了一条通信线路。因为ios::out是默认的,所以:
ofstream outClientFile{"clients.txt", ios::out};
也可以写为:
ofstream outClientFile{"clients.txt"};
现存的文件由模式ios::out打开会被截断---即文件中的所有数据均被丢弃。如果文件不存在,则ofstream对象会创造一个文件(文件名即为指定的名字)。
上述几种打开模式可以组合在一起,使用位或运算符。
stream.open("name.txt",ios::out|ios::app);
13.4.2 通过open函数打开文件
也可以创造一个ofstream对象而不需要打开特定的文件---这种情况下,文件可以在后续的操作中附加到对象上。例如,下列的语句:
ofstream outClientFile;
创造了对象,但并没有和文件联系起来。ofsream的成员函数open打开一个文件,并将其附加在一个现存的ofstream对象上,如下所示:
outClientFile.open("clients.txt", ios::out);
上述语句中的ios::out也是默认的,即也可以省略。
13.4.3 测试文件是否成功打开
在创建了ofstream对象后并试图打开它,if语句使用了重载的ios的成员函数operator!来判定打开操作是否成功。operator!函数返回true如果failbit或者badbit被流设置---这种情况下,由于打开操作失败,两者其一或者两者均被设置。可能的原因是:
1.试图打开一个不存在的文件以读取
2.试图打开一个在无权访问的文件夹中的文件以读写。
3.为了写入打开文件,但没有可用的硬盘空间。
The argument Passing to exit is returned to the environment from which the program was invoked.EXIT_SUCCESS (defined in <cstdlib>) to exit indicates that the program terminated normally; passing any other value (in this case EXIT_FAILURE) indicates that the program terminated due to an error.
13.4.4 Overloaded bool Operator
上述代码在while语句中,隐式的对cin调用成员函数operator bool。只要failbit和badbit均未被设置,while的条件就会保持true的状态。
13.4.5 Processing Data
如果上述程序成功打开文件,程序就开始处理数据。When end-of-file is encountered or bad data is entered, operator bool returns false and the while statement terminates.
13.4.6 Closing a File
对于上述程序,一旦用户输入end-of-file指示符,main函数终止。此操作隐式的调用了outClientFile的析构函数,此析构函数用来关闭文件。也可以显示的关闭,使用成员函数close:
outClientFile.close();
Always close a file as soon as it’s no longer needed in a program.
13.5 从文件中读取数据
13.5.1 基本概念
ifstream类可以从文本文件中读取数据。需要检测文件是否成功打开。
若要正确读取数据,必须确切了解数据的存储格式。
13.5.2 检测文件是否成功打开:
1.可能出现的错误:
读文件时,文件不存在;写文件时介质只读
2.检测文件是否成功打开的方法
open()之后马上调用fail()函数,fail()函数返回true,文件未打开
ofstream output("hello.txt");
if(output.fail()){
cout<<R"(can't open file "hello.txt")";
}
13.5.3 检测文件是否到达末尾
使用eof()函数检查是否到达文件末尾。
ifstream in("hello.txt");
while(in.eof()==false){
cout<<static_cast<char>(in.get());
}
13.5.3 Opening a File for Input
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <cstdlib>
using namespace std;
void outputLine(int,const string&,double);
int main() {
// ifstream constructor opens the file
ifstream inClientFile("clients.txt", ios::in);
// exit program if ifstream could not open file
if(!inClientFile){
cerr<<"File could not be opened" << endl;
exit(EXIT_FAILURE);
}
cout << left << setw(10) << "Account" << setw(13)
<<"Name" << "Balance\n" << fixed << showpoint;
int account;
string name;
double balance;
// display each record in file
while(inClientFile>>account>>name>>balance){
outputLine(account, name, balance);
}
}
// display single record from file
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 inClientFile("clients.txt");
上述语句打开文件。
13.5.3 File-Position Pointers
程序通常从文件的开头按顺序读取,并连续读取所有数据,直到找到所需的数据。在程序执行期间,可能需要(从头开始)按顺序处理文件多次。istream and ostream提供了成员函数seekg和seekp。seekg重新定位文件位置指针(文件中要读取或写入的下一个字节的字节号)。每个 istream 对象都有一个 get 指针,该指针指示文件中下一个输入的字节号,每个 ostream 对象都有一个 put 指针,该指针指示文件中应放置下一个输出的字节号。例如:
inClientFile.seekg(0);
将文件位置指针定位到文件的开始(位置0),并将文件附加给对象。seekg的成员函数的参数是整数。该函数还可以有第二个参数,可以指定第二个参数来指示搜索方向,该参数可以是 ios::beg(默认值)表示相对于流的开头进行定位,ios::cur 表示相对于流中的当前位置进行定位,或者 ios::end 表示相对于流的末尾向后定位。文件位置指针是一个整数值,用于将文件中的位置指定为距文件起始位置的字节数(也称为与文件开头的偏移量)。定位 file-position 指针的一些示例包括:
ostream的成员函数seekp也可以执行相同的操作。成员函数tellg和tellp可以分别返回文件位置指针当前的位置。下列的语句将文件位置指针赋给long类型的变量location:
location = fileObject.tellg();
13.6 C++14: Reading and Writing Quoted Text
C++14’s new stream manipulator—quoted (header <iomanip>)—enables a program to read quoted text from a stream, including any white space characters in the quoted text, and discards the double quote delimiters.
13.7 Updating Sequential Files
如第 14.3 节所示,格式化并写入顺序文件的数据不能被修改,否则会有破坏文件中其他数据的风险。例如,如果需要将名称“White”更改为“Worthington”,则在不损坏文件的情况下无法覆盖旧名称。White 的记录被写入文件,如下所示:
300 White 0.00
如果使用较长的名称从文件中的同一位置开始重写此记录,则该记录将为:
300 Worthington 0.00
新记录包含的字符比原始记录多 6 个字符。因此,“Worthington”中“h”之外的字符将覆盖文件中下一个顺序记录的 0.00 和开头。问题在于,在使用流插入运算符 << 和流提取运算符 >> 的格式化输入/输出模型中,字段(以及记录)的大小可能会有所不同。例如,值 7、14、–117、2074 和 27383 都是整数,它们在内部存储相同数量的“原始数据”字节(在 32 位计算机上通常为 4 个字节,在某些 64 位计算机上可能为 8 个字节)。但是,当输出为格式化文本(字符序列)时,这些整数会根据其实际值成为不同大小的字段。因此,格式化的输入/输出模型通常不用于就地更新记录。第 14.7–14.11 节介绍如何使用固定长度的记录执行就地更新。
这样的更新是可以完成的,但很尴尬。例如,要进行前面的名称更改,可以将顺序文件中 300 White 0.00 之前的记录复制到新文件,然后将更新的记录写入新文件,并将 300 White 0.00 之后的记录复制到新文件。然后可以删除旧文件并重命名新文件。这需要处理文件中的每条记录以更新一条记录。但是,如果在一次文件传递中更新了许多记录,则此技术是可以接受的。
13.8 随机访问文件(Random-Access Files)
随机访问意味着可以读写文件的任意位置。
要实现随机访问文件,需要:
1.我们能知道文件定位器在什么位置
2.我们能在文件中移动文件定位器
可以将数据插入到随机访问文件中,而不会破坏文件中的其他数据。以前存储的数据也可以更新或删除,而无需重写整个文件。在以下各节中,我们将介绍如何创建随机访问文件,将数据输入到文件中,按顺序和随机读取数据,更新数据并删除不再需要的数据。
13.8.1 seek的用法
2.1. seek的原型
xxx_stream& seekg/seekp( pos_type pos );
xxx_stream& seekg/seekp( off_type off, std::ios_base::seekdir dir);
13.8.2 文件指针
1.File Positioner (文件位置指示器)
1.1. file positioner (fp):
A file consists of a sequence of bytes.(文件由字节序列构成)
File positioner is a special marker that is positioned at one of these bytes. (一个特殊标记指向其中一个字节)
1.2. A read or write operation takes place at the location of the file positioner. (读写操作都是从文件位置指示器所标记的位置开始)
When a file is opened, the fp is set at the beginning. (打开文件,fp指向文件头)
When you read or write data to the file, the file pointer moves forward to the next data item. (读写文件时,文件位置指示器会向后移动到下一个数据项)
1.3. File Positioner(文件位置指示器)的其它说法
File Pointer(文件指针):易与C语言的FILE* 混淆
File Cursor(文件光标):借用数据库中的“光标”概念
2.Example of File Positioner
调用aFileStream.get()函数,会导致fp = fp + 1;
13.9 Creating a Random-Access File
ostream 成员函数write从内存中的特定位置开始,将固定数量的字节写入到指定的流。当流与文件关联时,函数写入将数据写入 put file-position 指针指定的文件中的位置。istream 成员函数read将输入从指定流读取到内存中从指定地址开始的区域的固定字节数。如果流与文件关联,则函数在“get”文件位置指针指定的文件中的位置读取输入字节。
13.9.1 Writing Bytes with ostream Member Function write
将整数写入文件使用下列语句:
outFile << number;
13.10 向字符串输出
13.11 C++17引入的文件系统库
13.11.1 简介
文件系统库放在名字空间std::filesystem中。标准库的filesystem提供在文件系统与其组件,例如路径、常规文件与目录上进行操作的方法。
文件:持有数据的文件系统对象,能被写入或读取。文件有名称和属性,属性之一是文件类型
路径:标识文件所处位置的一系列元素,可能包含文件名。
路径类:
namespace fs=std::filesystem;
fs::path p{"checkPath.cpp"};
路径的概念:
绝对路径:包含完整的路径和驱动器符号
相对路径:不包含驱动器以及/符号。文件存在于相对于当前路径的位置。
13.11.2 路径类及操作
13.12 二进制输入输出
13.2.1 如何实现二进制读写
1.The write Function (write函数)
1.1. prototype (函数原型)
ostream& write( const char* s, std::streamsize count );
1.2. 可直接将字符串写入文件
fstream fs("GreatWall.dat", ios::binary|ios::trunc);
char s[] = "ShanHaiGuan\nJuYongGuan";
fs.write(s, sizeof(s));
1.3. 如何将非字符数据写入文件
(1) Convert any data into a sequence of bytes (byte stream) (先将数据转换为字节序列,即字节流)
(2) Write the sequence of bytes to file with write() (再用write函数将字节序列写入文件)
2.How to convert any data into byte stream? (如何将信息转换为字节流)
2.1. reinterpret_cast
该运算符有两种用途:
(1) cast the address of a type to another type (将一种类型的地址转为另一种类型的地址)
(2) cast the address to a number, i.e. integer (将地址转换为数值,比如转换为整数)
2.2. 语法:
reinterpret_cast<dataType>(address)
address is the starting address of the data (address是待转换的数据的起始地址)
dataType is the data type you are converting to. (dataType是要转至的目标类型)
对于二进制I/O来说,dataType是 char*
2.3. 例子
long int x {0};
int a[3] {21,42,63};
std::string str{"Hello"};
char* p1 = reinterpret_cast<char*>(&x); // variable address
char* p2 = reinterpret_cast<char*>(a); // array address
char* p3 = reinterpret_cast<char*>(&str); // object address
3.The read Function (read成员函数)
3.1. prototype (函数原型)
istream& read ( char* s, std::streamsize count);
3.2. 例子
// 读字符串
fstream bio("GreatWall.dat", ios::in | ios::binary);
char s[10];
bio.read(s, 5);
s[5] = '\0';
cout << s;
bio.close();
// 读其它类型数据(整数),需要使用 reinterpret_cast
fstream bio("temp.dat", ios::in | ios::binary);
int value;
bio.read(reinterpret_cast<char *>(&value), sizeof(value));
cout << value;
标签:文件,13,函数,处理,ios,file,ofstream,字节
From: https://www.cnblogs.com/yyyylllll/p/18390210