1. 简单的文件I/O
写入文件:
让程序写入文件,其步骤大致为如下:
- 创建一个
ofstream
对象来管理输入流; - 将该对象与特定的文件关联起来;
- 用使用
cout
的方式使用该对象,唯一的区别是输出将进入文件,而不是屏幕。
例子:
代码语言:javascript复制//需要包含头文件fstream
ofstream fout; //#1
fout.open("a.txt");//#2 若a.txt不存在,则会新建一个,若存在,则会清空源文件内容
fout << "hello"; //#3 将hello写入a.txt中
【注:以默认模式打开文件(即上述方式)进行输出将自动把文件的长度截短为零,这相当于删除已有的内容。具体原因见本文第三节文件模式中的注】
读取文件
读取文件的要求与写入文件相似:
- 创建一个
ifstream
对象来管理输入流; - 将该对象与特定的文件关联起来;
- 以使用
cin
的方式使用该对象。
例子:
代码语言:javascript复制//需要包含头文件fstream
ifstream fin; //#1
fin.open("a.txt");//#2
char ch;
fin >> ch; //#3 读取一个字符放入ch
【注:当输入和输出流对象过期(如程序终止)时,到文件的连接将自动关闭。另外,也可以使用close()
方法来显式地关闭到文件的连接。关闭这样的连接并不会删除流,而只是断开流到文件的连接。然而,流管理装置仍被保留。】
流状态检查
C 文件流类从ios_base
类那里继承了一个流状态成员。检查文件是否成功打开的常见方式如下:
ifstream fin;
fin.open(argv[file]);
if (fin.fail()){...} //判断文件打开是否成功
if (!fin.good()){...} //判断文件打开是否成功
if (!fin){...} //判断文件打开是否成功
//is_open()能够检测到这种错误以及good()能够检测到的错误。然而,老式C 实现没有is_open( )。
if (!fin.is_open()){...} //判断文件打开是否成功
2. 命令行处理技术
文件处理程序通常使用命令行参数来指定文件。例如:
代码语言:javascript复制exc a.txt b.txt
exc
为程序可执行文件名,a.txt
和b.txt
为程序exc
执行过程中要使用到的文件。要实现上述功能,则程序的主函数应该写为:
int main(int argc, char* argv[])
argc
为命令行中的参数个数,其中包括命令名本身。argv
变量为一个指针,它指向一个指向char的指针。这过于抽象,但可以将argv
看作一个指针数组,其中的指针指向命令行参数,argv[0]
是一个指针,指向存储第一个命令行参数的字符串的第一个字符,依此类推。也就是说,argv[0]
是命令行中的第一个字符串,依此类推。因此上面的例子中:argc
为3,argv[0]
为exc
,argv[1]
为a.txt
,argv[2]
为b.txt
。
3. 文件模式
文件模式描述的是文件将被如何使用:读、写、追加等。将流与文件关联时(无论是使用文件名初始化文件流对象,还是使用open()
方法),都可以提供指定文件模式的第二个参数:
ifstream fin("banjo", mode1); // mode1为文件模式
ofstream fout();
fout.open("harp", mode2); // mode2为文件模式
文件模式的常量有:
常量 | 含义 |
---|---|
ios_base::in | 打开文件,以便读取 |
ios_base::out | 打开文件,以便写入 |
ios_base::ate | 打开文件,并移到文件尾 |
ios_base::app | 追加到文件尾 |
ios_base::trunc | 如果文件存在,则截短文件 |
ios_base::binary | 二进制文件 |
【注:ifstream
open()
方法和构造函数用ios_base::in
(打开文件以读取)作为模式参数的默认值,而ofstream
open()
方法和构造函数用ios_base::out | ios_base::trunc
(打开文件,以读取并截短文件,也就是说,其以前的内容将被删除)作为默认值。位运算符OR(|
)用于将两个位值合并成一个可用于设置两个位的值。fstream
类不提供默认的模式值,因此在创建这种类的对象时,必须显式地提供模式。】
如果要保留文件内容,并在文件尾添加(追加)新信息,则可以使用ios_base::app
模式:
ofstream fout("bagels", ios_base::out | ios_base::app);//使用|运算符来合并模式,启用模式out和app
老式C 实现之间可能有一些差异。例如,有些实现允许省略前一例子中的ios_base::out
,有些则不允许。如果不使用默认模式,则最安全的方法是显式地提供所有的模式元素。
要以二进制格式(而不是文本格式)存储数据,可以使用ofstream
对象的write()
成员函数。该方法将内存中指定数目的字节复制到文件中。若从二进制文件读取数据,可以使用ifstream
对象的read( )
成员函数。该方法从文件中内容复制到目标结构中。这两者的对应的使用方法如下:
const int LIM = 20;
struct planet
{
char name[LIM]; // name of planet
double population; // its population
double g; // its acceleration of gravity
};
planet pl;
planet p2;
//写入
//ofstream fout("planets.dat", ios_base:: out | ios_base::app);
//fout << pl.name << " " << pl.population << " " << pl.g << "n";
ofstream fout("planets.dat",
ios_base:: out | ios_base::app | ios_base::binary);
fout.write( (char *) &pl, sizeof(pl));
//读取
ifstream fin("planets.dat", ios_base::in | ios_base::binary);
fin.read((char *) &p2, sizeof(p2));
4. 随机存取
随机存取指的是直接移动(不是依次移动)到文件的任何位置。要实现读/写的效果,需要同时使用in
模式和out
模式将得到读/写模式,要使用|
运算符来组合模式。因此,需要使用下面的语句:
fstream finout;//fstream类是从iostream类派生而来的,而后者基于istream和ostream两个类,因此它继承了它们的方法。它还继承了两个缓冲区,一个用于输入,一个用于输出,并能同步化这两个缓冲区的处理。
finout.open(file, ios_base::in | ios_base::out | ios_base::binary);
接下来,需要一种在文件中移动的方式。fstream
类为此继承了两个方法:seekg()
和seekp()
,前者将输入指针移到指定的文件位置,后者将输出指针移到指定的文件位置(实际上,由于fstream
类使用缓冲区来存储中间数据,因此指针指向的是缓冲区中的位置,而不是实际的文件)。也可以将seekg()
用于ifstream
对象,将seekp()
用于oftream
对象。
seekg()
和seekp()
的原型及使用方法如下:
//seekg()
istream & seekg(streamoff, ios_base::seekdir);//第一个原型定位到离第二个参数指定的文件位置特定距离(单位为字节)的位置;streamoff值被用来度量相对于文件特定位置的偏移量(单位为字节)。
istream & seekg(streampos);//第二个原型定位到离文件开头特定距离(单位为字节)的位置。streampos对应的是距离(单位为字节)。
//seek_dir参数是ios_base类中定义的另一种整型,有3个可能的值:
//常量ios_base::beg指相对于文件开始处的偏移量;
//常量ios_base::cur指相对于当前位置的偏移量;
//常量ios_base::end指相对于文件尾的偏移量。
fin.seekg(30, ios_base::beg); //将读取位置设置为从文件开头开始的第31个字节(字节编号为30)
fin.seekg(-1, ios_base::cur); //将读取位置设置为从当前位置后退1个字节
fin.seekg(0, ios_base::end); //将读取位置设置为从文件尾0个字节的位置
fin.seekg(112); //第一个字节的编号为0。因此该语句将读取位置设置为第112个字节,也就是文件中的第113个字节
//seekp()
ostream & seekp(streamoff offset,seek_dir origin);
ostream & seekp (streampos pos);
fout.seekp(30, ios_base::beg); //将写入位置设置为从文件开头开始的第31个字节(字节编号为30)
fout.seekp(-1, ios_base::cur); //将写入位置设置为从当前位置后退1个字节
fout.seekp(0, ios_base::end); //将写入位置设置为从文件尾0个字节的位置
fout.seekp(112); //第一个字节的编号为0。因此该语句将写入位置设置为编号为112的字节,也就是文件中的第113个字节
【注:如果要检查文件指针的当前位置,则对于输入流,可以使用tellg()
方法,对于输出流,可以使用tellp()
方法。它们都返回一个表示当前位置的streampos
值(以字节为单位,从文件开始处算起)。】
seekg()
和seekp()
函数提供对文件的随机存取。这些类方法使得能够将文件指针放置到相对于文件开头、文件尾和当前位置的某个位置。tellg()
和tellp()
方法报告当前的文件位置。
参考文献 C Primer Plus(第六版) - 第17章 输入、输出和文件