大多数计算机程序都使用了文件。文件本身是存储在某种设备上的一系列字节。
通常,操作系统管理文件,跟踪它们的位置、大小、创建时间等。
除非在操作系统级别上编程,否则通常不必担心这些事情。
真正需要的是将程序与文件相连的途径、让程序读取文件内容的途径以及让程序创建和写入文件的途径。
C++ I/O类软件包处理文件输入和输出的方式与处理标准输入和标准输出的方式非常相似。
要写入文件,需要创建一个ofstream对象,并使用ostream方法。
要读取文件,需要创建一个ifstream对象,并使用istream方法。
然而,与标准输入和输出相比,文件的管理更加复杂。
例如:必须将新打开的文件和流关联起来。可以以只读模式、只写模式或读写模式打开文件。
为了帮助处理这些任务,C++在头文件fstream中定义了多个新类,其中包括用于文件输入的ifstream类和用于输出的ofstream类。
C++还定义了一个fstream类,用于同步I/O。这些类都是从头文件iostream中派生而来的。
====================================================
一、简单的文件I/O
要让程序写入文件,必须这样做:
1、创建一个ostream对象来管理输出流;
2、将该对象与特定文件关联起来;
3、以使用cout的方式使用该对象,唯一的区别是输出将进入文件,而不是屏幕;
首先要包含fstream头文件,一般包含了该文件就不需要显式包含iostream。
ofstream fout;
接下来,将这个对象与特定的文件关联起来:
fout.open("jar.txt");
可以使用另一个构造函数将这两步合并成一条语句:
ofstream fout("jar.txt");
然后像使用cout的方式使用该对象fout,例如:将Dull Data放到文件中
fout << "Dull Data";
由于ostream是ofstream的基类,因此可以使用所有ostream的方法。例如:各种插入运算符的定义、格式化方法和控制符。
ofstream类使用被缓冲的输出,因此在程序创建像fout这样的ofstream对象时,将为输出缓冲区分配空间。
如果创建了两个ofstream对象,程序将创建两个缓冲区,每个对象各一个。
fout这样的ofstream对象从程序一个一个字节地收集输出,当缓冲区填满后,它便将缓冲区内容一同传输给目标文件。
由于磁盘驱动器被设计称以大块的方式传输数据,而不是逐字节地传输,因此通过缓冲可以大大提高从程序到文件传输数据的速度。
读取文件的要求与写入文件相似:
1、创建一个ifstream对象来管理输入流;
2、将该对象与特定的文件关联起来;
3、以使用cin的方式使用该对象;
ifstream fin;
fin.open("jellyjar.txt");
也可以用一条语句实现:ifstream fin("jellyjar.txt");
char ch;
fin >> ch; //read a character from the jelly
char buf[80];
fin >> buf; // read a word from the file
fin.geline(buf, 80) // read a line from the file
string line;
fin.getline(fin, line);
输入和输出一样,也是被缓冲的。同样,创建一个对象fin时,也将创建一个相应的输出缓冲区。
通过缓冲区传输数据的速度比以字节的方式传输要快得多。
当输入和输出流对象过期时,到文件的连接将自动关闭。
当然也可以使用close()方法来显式地关闭到文件的连接:
fout.close();
fin.close();
关闭这样的连接并不会删除流,而只是断开流到文件的连接。然而流管理装置仍然被保留。例如fin对象与它管理的输入缓冲区仍然存在。
接下来看一个例子:
1 //fileio.cpp -- saving to a filebuf 2 #include<iostream> 3 #include<fstream> 4 #include<string> 5 6 int main() 7 { 8 using namespace std; 9 string filename; 10 11 cout << "Enter name for new file:"; 12 cin >> finname; 13 14 ofstream fout(filename.c_str()); //关联一个文件,用c_str()方法给构造函数提供C-风格字符串参数 15 16 fout<<“For your eyes only! ”; 17 cout<<"Enter your secret number: "; 18 float secret; 19 cin>> secret; 20 fout<< "Your secret number is "<<secret<<endl; 21 fout.close() //取消关联文件 22 23 ifstream fin(filename.c_str()); 24 cout<<"Here are the contents of "<<filename<<": "; 25 char ch; 26 while (fin.get(ch)) 27 cout<<ch; 28 cout<<"Done "; 29 fin.close(); 30 31 return 0; 32 }
====================================================
二、流状态检查和is_open()
C++文件流类从ios_base类那里继承了一个流状态成员。
该成员存储了指出流状态的信息;一切顺利,已达到文件尾,I/O操作失败等;
如果一切顺利,则流状态为零。
其他状态都是通过特定位设置为1来记录的。
文件流类还继承了ios_base类中报告流状态的方法。
例如:检查试图打开文件时是否成功?试图打开一个不存在的文件进行输入时,将设置failbit位。
fin.open(argv[file]);
if( fin.fail() )
{
...
}
新的C++实现了提供了一种更好的检查文件是否被打开的方法——is_open()方法:
if(!fin.is_open())
{
...
}
====================================================
三、打开多个文件
程序可能需要打开多个文件,打开多个文件的策略取决于怎么使用它们;
如果需要同时打开两个文件,则需要为每个文件创建一个流;
例如,可能要计算某个名称在10个文件中出现的次数,在这种情况下,可以打开一个流,并将它依次关联到各个文件。
这在节省计算机资源方面,比为每个文件打开一个流的效率更高。
====================================================
四、命令行处理技术
文件处理程序通常使用命令行参数来指定文件。
命令行参数是用户在输入命令时,在命令行中输入的参数。
C++有一种让在命令行环境中运行的程序能够访问命令行参数的机制。
int main(int argc, char *argv[ ])
argc 命令行中的参数个数,其中包括命令名本身;
argv 变量为一个指针,它指向一个指向char的指针。这看起来有点抽象,可以将argv看作是一个指针数组。
其中的指针的指向命令行参数。argv[0]是一个指针,指向存储第一个命令行参数的字符串的第一个字符,
1 //count.cpp -- counting characters in a list of files 2 #include<iostream> 3 #include<fstream> 4 #include<cstdlib> 5 6 int main() 7 { 8 using namespace std; 9 if (argc == 1) //quit if no arguments 10 { 11 cerr<<"Usage: "<<argv[0]<<"filenames[s] "; 12 exit(EXIT_FAILURE); 13 } 14 15 ifstream fin; 16 long count; 17 long total = 0; 18 char ch; 19 20 for(int file = 1; file<argc; file++) 21 { 22 fin.open(argv[file]); 23 if(!fin.is_open()) 24 { 25 cerr<<"Could not open "<<argv[file]<<endl; 26 fin.clear(); 27 continue; 28 } 29 count =0; 30 while (fin.get(ch)) 31 count++; 32 cout<<count<<" characters in "<<argv[file]<<endl; 33 total += count; 34 fin.clear(); 35 fin.close(); 36 } 37 38 cout<< total << "characters in all files "; 39 return 0; 40 }
====================================================
五、文件模式
文件模式描述的是文件将如何被使用:读、写、追加等;
将流与文件关联的时候,都可以提供指定文件模式的第二个参数;
ifstream fin ("banjo", mode1); // constructor with mode arguments
ofstream fout();
fout.open("harp", mode2); // open() with mode arguments
iosbase类定义了一个openmode类型,用于表示模式;
这是一种bitmask类型。可以选择ios_base类中定义的多个常量来制定模式
常量如下:
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::trunc 意味着打开已有的文件,在接受程序的输出时将被截短,也就是说,其以前的内容将被删除;
如果不希望打开文件时将内容删除,则可以有其他选择如下:
ofstream fout("bagels", ios_base::out | ios_base::app);
上述代码使用|运算符来合并模式;因此使用ios_base::out | ios_base::app意味着启用模式out和app。
追加文件
接下来来看一个在文件尾追加数据的程序:
1 //append.cpp -- appending information to a file 2 #include<iostream> 3 #include<fstream> 4 #include<string> 5 #include<cstlib> //for exit() 6 7 const char * file = "guests.txt"; 8 int main() 9 { 10 using namespace std; 11 char ch; 12 13 ifstream fin; 14 fin.open(file); 15 16 if(fin.is_open()) 17 { 18 cout<<"Here are the current constents of the "<<file<<" file: "; 19 while(fin.get(ch)) 20 cout<<ch; 21 fin.close(); 22 } 23 24 ofstream fout(file, ios::out | ios::app); 25 if(!fout.is_open()) 26 { 27 cerr<<"Can't open "<<file<<" file for for output. "; 28 exit(EXIT_FAILURE); 29 } 30 31 cout<<"Enter guest names(enter a blank line to quit): "; 32 string name; 33 while(getline(cin, name) && name.size() >0) 34 { 35 fout<<name<<endl; 36 } 37 fout.close(); 38 39 fin.clear(); //not necessary for some compilers 40 fin.open(file); 41 if(fin.is_open()) 42 { 43 cout<<"Here are the new contents of the "<<file<<" file: "; 44 while(fin.get(ch)) 45 cout<<ch; 46 fin.close(); 47 } 48 cout<<"Done. "; 49 return 0; 50 }
二进制文件
文本格式是指将所有内容都存储为文本;
二进制格式指的是存储值的计算机内部表示;
对于数字来说,二进制表示和文本表示有很大差异;
每种格式都有自己的优点:
文本格式便于读取,可以使用编辑器或字处理器来读取和编辑文本文件;可以很方便地将文本文件从一个计算机系统传输到另一个计算机系统;
二进制格式对于数字来说比较精确,因为不需要转换,并可以大块地存储数据。二进制格式通常占用空间比较小,这取决于数据的特征。
二进制文件和文本文件:文件格式有两种-文本格式和二进制格式
到底什么是二进制文件、和文本文件?
首先文件无非就是一堆二进制数的集合;八个二进制成一个字节,也就是一堆字节的集合;
其实文本文件就是基于字符编码的文件;
二进制文件是基于值编码的文件;
文本文件和二进制文件的区别不是物理上的,而是逻辑上的;二者是在编码层上有差异;
文本文件基本上是定长编码;
而二进制文件可以看成是变长编码,多少个比特代表一个值,完全由你决定;完全基于具体应用,指定某个值代表什么意思,用户一般不能直接读懂它,要借助软件才行;
以上是定义上的区别,接下来讨论一下存储上的区别:
文本工具打开一个文件,首先读取文件物理上所对应的二进制比特流,然后按照所选择的解码方式来解释这个流,然后将解释结果显示出来。
一般来说,你选取的解码方式会是ASCII码形式(ASCII码的一个字符是8个比特),接下来,它8个比特8个比特地来解释这个文件流。
记事本无论打开什么文件都按既定的字符编码工作(如ASCII码),所以当他打开二进制文件时,出现乱码也是很必然的一件事情了,解码和译码不对应。
文本文件的存储与其读取基本上是个逆过程。而二进制文件的存取与文本文件的存取差不多,只是编/解码方式不同而已。
二进制文件就是把内存中的数据按其在内存中存储的形式原样输出到磁盘中存放,即存放的是数据的原形式。文本文件是把数据的终端形式的二进制数据输出到磁盘上存放,即存放的是数据的终端形式。
使用二进制文件模式的时候,程序将数据从内存传输给文件时,将不会发生任何隐藏的转换。
但是文本模式确并不是这样:
对于Windows系统来说,它们使用两个字符的组合(回车和换行)表示换行符;
对于Macintosh文本来说,使用回车来表示换行符;
对于UNIX和Linux文件来说,使用换行符来表示换行符;
C++由于是从Linux继承过来的,也使用换行符表示换行;
为了增加可移植性,Windows在写文本模式文件时,自动将C++换行符替换为回车和换行;->对文本做了改动;如果放到Linux下运行时,就会出问题;
对于二进制数据,文本格式会引起问题,
另外,检测文件尾的方式也有所不同;
要以二进制格式存储数据,可以使用write()成员函数。这种方法将指定数目的字节复制到文件中。只逐字节地复制数据,而不进行任何格式转换。
C++中可以将文件模式设置为ios_base::binary常量来使用二进制文件格式;
如果要使用二进制格式保存数据,应使用二进制文件格式;
1 //binary.cpp -- binary file I/O 2 #include<iostream> // not required by most systems 3 #include<fstream> 4 #include<iomanip> 5 #include<cstlib> //for exit() 6 7 inline void eatline() {while (std::cin.get() != ' ') continue;} 8 struct planet 9 { 10 char name[20]; 11 double pupulation; 12 double g; 13 } 14 15 const char * file = "planets.dat"; 16 17 int main() 18 { 19 using namespace std; 20 planet pl; 21 cout<<fixed<<right; 22 23 ifstream fin; 24 fin.open(file, ios_base::in|ios_base::binary); 25 26 if(fin.is_open()) 27 { 28 cout<<"Here are the current constents of the "<<file<<" file: "; 29 while(fin.read(ch *) &pl, sizeof pl) //用户自定义读取的二进制数据的方式;从文件中复制sizeof pl个字节到pl结构中 30 cout<<setw(20)<<pl.name<<" :"<<setprecision(0)<<setw(12)<<pl.population<<setprecision(2)<<setw(6)<<pl.g<<endl; 31 fin.close(); 32 } 33 34 ofstream fout(file, ios::out | ios::app|ios::binary); 35 if(!fout.is_open()) 36 { 37 cerr<<"Can't open "<<file<<" file for for output. "; 38 exit(EXIT_FAILURE); 39 } 40 41 cout<<"Enter planet names(enter a blank line to quit): "; 42 cin.get(pl.name, 20); 43 while(pl.name[0] != '