C++ IO 库:文件的输入输出 (使用文件流对象 与 文件模式)

文件输入输出

​ 头文件 fstream 定义了三个类型来支持文件 IO:ifstream 从一个给定文件读取数据, ofstream 向一个给定文件写入数据。以及 fstream 可以读写给定文件。

​ 这些类型提供的操作与我们之前已经使用过的对象 cin 和 cout 的操作一样。特别是,我们可以用 IO 运算符 (>> 和 <<) 来读写文件,可以用 getline 从一个 ifstream 读取数据。

​ 除了继承自 iostream 类型的行为外,fstream 中定义的类型还增加了一些新的成员来管理与流关联的文件。下列操作,我们可以对 fstream、ifstream 和 ofstream 对象调用这些操作,但不能对其他 IO 类型调用这些操作。

fstream fstrm;		// 创建一个未绑定的文件流。fstream 是头文件 fstream 中定义的一个类型

fstream fstrm(s);	// 创建一个 fstream,并打开名为 s 的文件。s 可以是 string 类型,或者指向一个
					// 指向 C 风格的指针。这些构造函数都是 explicit 的。默认的文件模式 mode 依赖于
					// fstream 的类型
					
fstream fstrm(s, mode);	// 与前一个构造函数类似,但按指定 mode 打开

fstrm.open(s);			// 打开名为 s 的文件,并将文件与 fstrm 绑定。s 可以是一个 sting 或者指向C
						// 风格的指针。默认的文件 mode 依赖于 fstream 的类型。返回 void
						
fstrm.close();			// 关闭与 fstrm 绑定的文件。返回 void

fstrm.is_open();		// 返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开且尚未关闭
使用文件流对象

​ 当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来。每个文件流类都定义了一个名为 open 的函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。

​ 创建文件流对象,我们可以提供文件名(可选)。如果提供了一个文件名,则 open 会自动调用:

ifstream in(ifile);			// 构造一个 ifstream 并打开给定文件
ofstream out;				// 输出文件未关联到任何文件
用 fstream 代替 iostream&

​ 在要求使用基类型对象的地方,我们可以用继承类型的对象来替代。这意味着,接受一个 iostream 类型引用 (或指针) 参数的函数,可以用一个对应的 fstream (或 sstream) 类型来调用。也就是说,如果有一个函数接受一个 ostream& 参数,我们在调用这个函数时,可以传递给它一个 ofstream 对象,对 istream& 和 ifstream 也是类似的。

ifstream input(argv[1]);			// 打开销售记录文件
ofstream output(argv[2]);			// 打开输出文件
Sales_data total;					// 保存销售总额的变量
if(read(input,total)) {
    Sales_data trans;
    while(read(input,trans)) {
        if(total.isbn() == trans.isbn())
            total.combine(trans);
        else {
            print(output,total) << endl;		// 注意,endl 换行同样会写入文件,因为print 返
            									// 回的仍然是 output
            total = trans;
        }
    }
    print(output,total) << endl;
} else 
    cerr << "No data?!" << endl;

// read 与 print
istream &read(istream &is,Sales_data &item) {
    double price = 0;							// price 表示某书单价
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}
ostream &print(ostream &os,const Sales_data &item) {
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}

可知:虽然 read 与 print 函数定义时的参数是 istream& 与 ostream&,但仍可以向它们传递 fstream 对象。

成员函数 open 和 close

​ 如果我么定义了一个空文件流对象,可以随后调用 open 来将它与文件关联起来;

ifstream in(ifile);			// 构筑一个 ifstream 并打开给定文件
ofstream out;				// 输出文件流未与任何文件关联
out.open(ifile + ".copy");	// 打开指定文件

如果 open 调用失败,failbit 会被置位。所以进行 open 是否成功的检测通常是一个好习惯:

	if(out) {		// 检测 open 是否成功
    	// ...		// open 成功,可以使用文件
	}

​ 一旦文件流已经打开,它就保持与对应文件的关联。对一个已经打开的文件流调用 open 会失败,并会导致 failbit 置位。随后的试图使用文件的操作都会失败。为了将文件流关联到另一个文件,必须首先关闭已经关联的文件。一旦文件关闭成功,我们可以打开新的文件:

	in.close();			// 关闭文件
	in.open(ifile + "2");		// 打开另一个文件

​ 如果 open 成功,则 open 会设置流的状态,使得 good() 为 true。

发现的问题

这个是我做练习时发现的问题。我想打开一个文件两次。

假设文件名是 in.txt,有以下代码:

	ifstream input(in.txt);
	if(input) {
     // ... 执行操作
 }
	input.close();
	input.open(in.txt);
	//.. 发现错误,第二次相当于没有打开

然后尝试了两种方式:定义两个 ifstream 读取同一个文件;或者是 创建两个文件,然后用同一个 ifstream 读取。都是可行的。

经查阅:

ifstream 是有状态的对象,一个 ifstream 操作完后一般处于非正常状态,内部的 eofbit 标记 failbit 标记等可能已被设定。重新打开文件并不会清除这些标记,因此需要添加一步:
ifs.clear();  // 去除 ifs 中的错误标记(如文件末尾标记或读取失败标记等)
然后再进行 ifs.open();

对于同一个 ifstream 用来 open 两个文件为什么没有问题还不知道

自动构造和析构

​ 考虑这样一个程序,它的 main 函数接受一个要处理的文件列表。这种程序可能会有如下循环:

// 对每个传递给程序的文件执行循环操作
for(auto p = argv + 1;p != argv + argc;++ p) {
    ifstream input(*p);
    if(input) {
        process(input);
    }
    else
        cerr << "couldn't open:" + string(*P);
}	// 每个循环步 input 都会离开作用域,因此会被销毁

​ input 是循环的局部变量,它在每个循环步中都要创建和销毁一次。当一个 fstream 对象离开其作用域时,与之关联的文件会自动关闭。

当一个 fstream 对象被销毁时,close 将自动调用

文件模式

​ 每个流都有一个关联的文件模式,用来指出如何使用文件。

in: 以读方式打开

out: 以写方式打开

app: 每次写操作均定位到文件末尾

ate: 打开文件后立即定位到文件末尾

trunc: 截断文件

binary: 以二进制方式进行 IO

​ 无论以哪种方式打开文件,我们都可以指定文件模式。调用 open 打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以。指定文件模式有以下限制:

  • 只可以对 ofstream 或 fstream 对象设定 out 模式
  • 只可以对 ifstream 或 fstream 对象设定 in 模式
  • 只有当 out 也被设定时才可以设定 trunc 模式
  • 只要 trunc 没被设定,就可以设定 app 模式。在 app 模式下,即使没有显式指定 out 模式,文件也总是以输出方式被打开
  • 默认情况下,即使我们没有指定 trunc,以 out 模式打开的文件也会被截断(指 out 默认情况下都是重写文件,而不是追加)。为了保留以 out 模式打开的文件的内容,我们必须同时指定 app 模式,这样只会将数据追加写道文件末尾;或者同时指定 in 模式,即打开文件同时进行读写操作。(之后将会介绍对同一文件即输入又输出的方法)
  • ate 和 binary 模式可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用

每个文件流类型都定义了默认的文件模式,当未指定文件模式时,便使用默认模式。如与 ifstream 关联的文件默认以 in 模式打开;与 ofstream 关联的文件默认以 out 模式打开;与 fstream 关联的文件默认以 in 和 out 模式打开。

以 out 模式打开文件会丢弃已有数据

​ 默认情况下,当我们打开一个 ofstream 时,文件内容会被丢弃。阻止一个 ofstream 清空给定文件内容的方法是同时指定 app 模式。

// file1 下面几种方式都将被截断
ofstream out("file1");						// 隐含以 out 模式打开文件,并截断
ofstream out2("file1", ofstream::out);		// 隐含地截断文件
ofstream out3("file1", ofstream::out | ofstream::trunc);
// 为了保存文件内容,我们必须显式指定 app 模式
ofstream app("file2", ofstream::app);		// 隐含为 out 模式
ofstream app2("file2", ofstream::out | ofstream::app);

保留 ofstream 打开的文件已有数据的唯一方式显式指定 app 或 in 模式。

每次调用 open 时都会确定文件模式

​ 对于一个给定流,每当打开文件时,都可以改变其文件模式。

ofstream out;				// 未指定打开文件模式
out.open("scratchpad");		// 模式隐式设置为 输出 和 截断
out.close();
out.open("precious", ofstream::app);		// 模式为输出和追加
out.close();

​ 通常情况下,out 模式意味着同时使用 trunc 模式。在指定 append 模式后,文件已有的数据都得以保留,所有写操作都在文件末尾进行。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值