以下随笔大概为 网站的一个翻译
C++ 提供了一下类来进行文件的读写操作:
ofstream:可以进行文件写,
ifstream:可以从文件中读取
fstream:既可以读文件,也可以写文件。
这些类直接的或者间接的继承自istream和ostream。我们经常使用的istream和ostream的类是标准库中的 cin 和cout
1 // basic file operations
2 #include <iostream>
3 #include <fstream>
4 using namespace std;
5
6 int main () {
7 ofstream myfile;
8 myfile.open ("example.txt");
9 myfile << "Writing this to a file.\n";
10 myfile.close();
11 return 0;
12 }
打开一个文件 example.txt,(如果这个文件不存在,那么创建这个文件。如果这个文件已经存在,将它的内容清空)。然后向文件中写入一句话。
Let's go step by step.
打开文件(open file)
创建了一个ofstream或者ifstream流,之后,可以调用open成员函数来打开文件。
open (filename, mode);
filename表示要打开文件的字符串,mode是一个可选的参数,mode的值可以是以下值的组合(或)。
ios::out open for output operations
ios::in open for input operations
ios::binary open in binary mode
ios::ate set the initial positioin at the end of the file. If this flag is not set , the initial positon will be the begin of the file.
ios::app All output operation are performed at the end of the file, appending the content to the current content of the file.
ios::trunc If the file is opened for output operations and it already exists, it's previous content is deleted and replaced by the new one.
所有的这些flags都可以用bitwise operation OR(|)进行组合,比如我们想以二进制模式打开文件,并且向文件尾追加内容。
1 ofstream myfile;
2 myfile.open("example.bin", ios::out | ios::app | ios::binary);
fstream, ofstream, 和ifstream的open成员函数都有一个默认的打开模式。
同样我们也可以在定义对象的时候打开文件(相当于定义对象并且调用它的open成员函数)。
ofstream myfile ("example.bin", ios::out | ios::app | ios::binary);
关闭文件(close a file)
fstream ofstream, ifstream这些类都是利用操作系统提供的底层函数来实现的。当我们打开一个文件的时候,会占用操作系统分配的资源(保存在这些创建的对象中)。所以当我们不需要文件的时候要关闭这个流.
myfile.close()
一旦调用了这个函数,the stream object 可以用来打开其他的文件,相应的这个文件也可以被其他的对象来访问。
如果一个stream object 要被销毁的时候却 still ossotiate with an open file,那么析构函数就会调用the member function close().
文本文件(txt files)
文本文件是由一系列的字符组成的,比如123其实是字符‘1’,‘2’,‘3’。所以如果我们想要从这个文本文件中提取一个整数,首先这个整数字符必须要连续存储着,然后这个整数字符串的结束要与别的字符隔开(比如 逗号,'\t','\n'等等。虽然这些也是字符)。然后我们就可以读取这个字符串,保存在char类型的数组中,或者保存在string中。然后调用函数将这个字符串转化成整数,是不是很麻烦,别着急,还有更麻烦的。
如果现在字符串是这个样子“123\t456” 我们读取123到string 中的时候遇到\t停止,问题来了: 很明显\t是不能保存在string中,否则sting就是 123\t 了,在转化为整数的时候会发生错误。
如果 \t 不保存在sting中,那么 \t 可以留在文件中,也就是说这个流在文件中的位置是 \t456, 那么下一次我们要从文件中读取这个整数的时候就发生问题了,因为第一个字符 不是数字字符,而是我们规定的所谓结束字符 \t 。 所以我们就会得到一个不正确的结果。那么正确的方式就是读取出来 \t ,让流指针在文件中的位置为 456 ,然后读取这个整数的字符串。(这个问题绝对是每个人都会遇到的问题。)
所以当我们调用 比如 getline(ifstream , string, '\n') 之类的函数的时候(表示读取一个字符串:从现在的位置到 '\n'结束):
我们一定要清楚: 这个 \n 是如何处理的。
1. 是留在文件中吗? 如果留在文件中,我们要将 \n 读取出来,以此保证下一次读取正确。
2. 是读取了 保存在 string中吗? 如果是保存在string中,那么我们以后要将string转化为整数的时候或许会出错,
3. 是读取了,但是没有保存在string中。
好了,废话少说,我们看一下到底是如何读取文本文件的。
如果一个流的 ios::binary 标志没有被包含,那么就是读写文本文件。文本文件是用来存储字符的,所以向其中写入值,或者从中读取值的时候 可能要经过一些格式转化。比如: cout << 123。 这里123是一个整数,却被以字符串的形式显示到了标准输出设备上,所以其是经历过格式转化的(我们向文件中写入的时候也同样是如此)。
1 // writing on a text file
2 #include <iostream>
3 #include <fstream>
4 using namespace std;
5
6 int main () {
7 ofstream myfile ("example.txt");
8 if (myfile.is_open())
9 {
10 myfile << "This is a line.\n";
11 myfile << "This is another line.\n";
12 myfile.close();
13 }
14 else cout << "Unable to open file";
15 return 0;
16 }
读文件也同样如此
// reading a text file
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main () {
string line;
ifstream myfile ("example.txt");
if (myfile.is_open())
{
while ( getline (myfile,line) )
{
cout << line << '\n';
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
我们现在看一下 getline函数
(1) istream& getline (istream& is, string& str, char delim);
(2) istream& getline (istream& is, string& str);
Extracts characters from is and stores them into str until the delimitation character delim is found (or the newline character, '\n', for (2)).
The extraction also stops if the end of file is reached in is or if some other error occurs during the input operation.(如果达到文件尾,或者读取过程中发生了别的错误,那么提取字符串的过程也会结束。)
If the delimiter is found, it is extracted and discarded, i.e. it is not stored and the next input operation will begin after it.(如果发现了delimiter 字符,从流中提取出来,并且抛弃。也就是说它并没由存储string中,且下一个读取位置在这个delimiter之后)
Each extracted character is appended to the string as if its member push_back was called.(感觉这里最后一句话是不对的,应该是替换了string中的字符串,并且添加进去新的字符串)
可以看到,读/写文件还是有很多方面需要关注的(是不是可以说C++的读写文件就是shit,java倒是包装的很好,读写都很方便)。
检查状态标志位(check state flags)
有一些成员函数用来检查一个流的状态,我们通过流的状态可以判断这个流是否可以进行 input/output操作:
bad()
当一个读取/写入操作发生错误了,这个成员函数返回真。例如当我们向一个文件写内容,但是这个文件is not open for writing, 或者我们要写入的设备没有空间了。
fail()
Return true in the same case as bad(). 但是当格式错误的发生的时候也会return true。例如:当我们尝试读取一个整数的时候,却从文件中读到了一个字符。(这个问题我还不是很理解)
eof()
Returns true
if a file open for reading has reached the end.(当读取文件的时候到达文件尾)
good()
它是最通用的状态检测标志:当以上所有的函数都返回 true的时候,它返回false。注意,good() 和 bad() 并不是完全的相对的。(good() 成员函数一次会检测更多的状态)
成员函数 clear() 可以用来 重置(reset) 这些 falgs。 clear() 函数很重要,比如我们读取一个文件的内容,达到文件尾。此时 eof() 会返回true。 如果我想要从头开始重新读取文件: 理论上可以用 seekg( postion ) 函数,定位到文件头,然后在重新读取。但是要注意,此时如果调用 good() 是返回 false 的, 也就是说读取会失败的。 为什么呢,因为这个流的 eof 标志位 被值1 表示到了文件尾,然后它就认为现在是文件尾无法读取(应该在每次读取之前都会检查),虽然我们已经将这个流重新定位到了文件头。 所以此时要利用 clear() 来重置,这些标志位。然后再读取才行。
(以下话题会不定时更新,未完待续)
flush()
读/写 文件定位 (get and put stream positioning)
二进制文件(binary files)
缓冲区和同步(Buffers and Synchronization)