fstraem
C++在fstraem头文件中定义了多个新类,其中包括用于文件输入的ifstream 类和用于文件输出的ofstream 类。还定义了一个fstraem 类,用于同步文件I/O。
这些类都是从头文件iostream中的类派生而来的。
简单的文件I/O
首先包含头文件fstream 。
声明一个ofstream对象:
ofstream fout; // create an ofstraem object named fout
对象名可以是任意有效的C++名称。
将这个对象与特定的文件关联。使用open() 方法:
fout.open("jar.txt"); // associate fout with jar.txt
可以使用另一个构造函数将这两步合并起来:
ofstraem fout("jar.txt");
// create fout object, associate it with jar.txt
以使用cout的方式使用fout对象。
例如,要将Dull Data放到文件中:
fout >> "Dull Data";
以这种方式打开文件来进行输入时,如果文件不存在,将创建一个新文件;如果文件存在,则打开文件并将其清空。
当输入或输出流对象过期时,到文件的连接将自动关闭。
也可以使用close() 方法来显示地关闭到文件的连接:
fout.close();
流状态检查和 is_open()
文件流类从ios_base类继承了流状态成员。
如果一切顺利,则流状态为零。
其他状态都是通过将特定位设置为1来记录的。
文件流类还继承了ios_base类中报告流状态的方法。
关于流状态详情查看<< istream类方法 重载的抽取操作符 和 cin输入 >>
检查文件是否成功打开的一般方法:
if(fin.fail() ) ... // failed to open
if(!fin.good() ) ... // failed to open
if(!fin) ... // failed to open
更好的检查方法—— is_open() :
if ( !fin.is_open() ) // open attempt failed
之所以更好,是因为该方法除了能检测到一般方法所能检测到的错误以外,还能检测到:试图以不合适的文件模式打开文件时失败。
打开多个文件
打开一个文件流,依次关联:
ifstraem fin; // create stream using default constructor
fin.open("fat.dat"); // associate stream with fat.dat file
... // do stuff
fin.close(); // terminate association with fat.dat
fin.clear(); // reset fin (may not be needed)
fin.open("rat.dat"); // associate stream with rat.dat file
...
fin.close();
命令行处理技术
文件处理程序通常使用命令行参数来指定文件。
例如,要在UNIX或Linux系统中计算文件包含的字符,可以在命令行提示符下输入下面的命令:
wc report1 report2 report3
其中,wc是程序名,report1、report2和report3是作为命令行参数传递给程序的文件名。
C++有一种让在命令行环境中运行的程序能够访问命令行参数的机制。方法是使用下面的main()函数:
int main(int argc, char *argv[])
argc为命令行中参数的个数,其中包括命令名本省。
argv为一个指针,它指向一个指向char的指针。可以将其看做一个指针数组,其中的指针指向命令行参数。
例如,argv[0]是一个指针,指向存储第一个命令行参数的字符串的第一个字符。
那么上面的命令行参数存储为: argc为4,argv[0]为wc,argv[1]为report1,依此类推。
文件模式
文件模式描述的是文件将被如何使用: 读、写、追加等。
将流于文件关联时(无论是使用文件名初始化文件流对象,还是使用open()方法),都可以提供指定文件模式的第二个参数:
ifstream fin("banjo", mode1); // constructor with mode argument
ofstream fout();
fout.open("harp", mode2); // open() wiht mode arguments
ios_base类定义了一个openmode 类型,用于表示模式,它是一种bitmask 类型。
可以选择ios_base类中定义的多个常量来指定模式。
下图列出了这些常量及其含义
ifstream open()方法和构造函数用 ios_base::in 作为模式参数的默认值。
ofstream open()方法和构造函数用 ios_base::out | ios_base::trunc (打开文件,以读取并截短文件)作为默认值。
位操作符OR (| )用于将两个位值合并成一个可用于设置两个位的值。
ios_base::out本身将导致文件被截短,但与ios_base::in一起使用时,不会导致文件被截短。
ios_base::in | ios_base::out // 读写模式
ios_base::trunc标记意味着打开已有的文件,以接受程序输出时将被截短;也就是说,其以前的内容将被删除。
如果要保留文件内容,并在文件尾添加新信息,则可以这样编写:
ofstream fout ("bagels", ios_base::out | ios_base::app);
ios_base::ate和ios_base::app都将文件指针指向打开的文件尾。区别在于ios_base::app模式只允许将数据添加到文件尾,而ios_base::ate模式将指针放到文件尾。
fstream类不提供默认的模式值,因此在创建这种类的对象时,必须显示地提供模式。
二进制文件
举例说明,有如下的结构定义和声明:
const int LIM = 20;
struct planet
{ char name[LIM]; // name of planet
double population; // its population
double g; // its acceleration of gravity
};
planet pl;
要用二进制格式存储相同的信息,可以这样做:
ofstream fout ("planets.dat", ios_base::out | ios_base::app | ios_base::binary);
fout.write ((char *) &pl, sizeof pl);
上述代码使用计算机的内部数据表示,将整个结构作为一个整体保持。不能将该文件作为文本读取。
使用write() 成员函数,该方法将内存中指定数目的字节复制到文件中。它只逐字节的复制数据,而不进行任何转换。
必须将地址强制转换为指向char的指针。
sizeof 操作符将获取整个结构的字节数。
要使用文件恢复信息,请通过一个ifstream对象使用相应的read() 方法:
ifstream fin("planets.dat", ios_base::in | ios_base::binary);
fin.read((char *) &pl,sizeof pl);
上述代码将从文件中复制sizeof pl个字节到pl结构中。
同样的方法也适用于不使用虚函数的类。在这种情况下,只有数据成员被保存,而方法不会被保存。
如果类有虚方法,则也将复制隐藏指针(该指针指向虚函数的指针表)。
由于下一次运行程序时,虚函数表可能在不同的位置,因此将文件中的旧指针信息复制到对象中,将可能造成混乱。
注意: 如果将planet结构的字符数组name成员换成string对象。将出现问题。因为string对象本身实际上并没有包含字符串,而是包含一个指向其中存储了字符串的内存单元的指针。因此,将结构复制到文件中时,复制的将不是字符串数据,而是字符串的存储地址。当再次运行程序,读取文件时,该地址将毫无意义。
随机存取
随机存取指的是直接移动(不是依次移动)到文件的任何位置。
可以创建一个fstream对象。fstream类是从iostream类派生而来的,而后者基于istream和ostream两个类,因此它继承了它们的方法。它还继承了两个缓冲区,一个用于输入,一个用于输出,并能同步化这两个缓冲区的处理。
当程序读写文件时,它将协调地移动输入缓冲区的输入指针和输出缓冲区的输出指针。
为了在文件中移动,fstream类继承了两个方法。
1. seekg()
seekg()方法将输入指针移动到指定的文件位置。该方法也可用于ifstream对象。
原型如下:
basic_istream<charT, traits>& seekg (off_type, ios_base::seekdir);
basic_istream<charT, traits>& seekg (pos_type);
它们都是模板,对于char具体化,上面的两个原型等同下面的代码:
istream & seekg (streamoff, ios_base::seekdir);
istream & seekg (streampos);
第一个原型定位到离第二个参数指定的文件位置特定距离的位置,单位为字节。
第二个原型定位到离文件开头特定距离的位置,单位为字节。
第一个原型的参数:
streamoff 值被用来度量相对于文件特定位置的偏移量。该参数表示相对于3个位置之一的偏移量为特定值的文件位置。
seek_dir 参数是ios_base类中定义的另一种整型,有3个可能的值。
常量ios_base::beg指相对于文件开始处的偏移量。
常量ios_base::cur指相对于当前位置的偏移量。
常量ios_base::end指相对于文件尾的偏移量。
范例,假设fin是一个ifstream对象:
fin.seekg(30,ios_base::beg); // 30 bytes beyond the beginning
fin.seekg(-1,ios_base::cur); // back up one byte
fin.seekg(0,ios_base::end); // go to the end of the file
第二个原型的参数:
streampos类型的值定位到文件中的一个位置。
它可以是类,但如果是这样的话,这个类将包含一个接受streampos参数的构造函数和一个接受整型参数的构造函数,以便将两种类型转换为streampos值。
streampos值表示文件中的绝对位置,偏移从文件开始处算起。第一个字节的编号为0 。
例如:
fin.seekg(112);
将文件指针指向第112个字节,这是文件中的第113个字节。
2. seekp()
seekp()方法将输出指针移动到指定的文件位置。实际上,由于fstream类使用缓冲区来存储中间数据,因此指针指向的是缓冲区中的位置,而不是实际的文件。
该方法也可用于ofstream对象。
检查文件指针的当前位置
对于输入流,可以使用tellg() 方法。
对于输出流,可以使用tellp() 方法。
它们都返回一个表示当前位置的streampos值,以字节为单位,从文件开始处算起,第一个字节编号为0 。
创建fstream对象时,输入指针和输出指针将一前一后地移动,因此tellg()和tellp()返回的值相同。
如果使用istream对象来管理输入流,而使用ostream对象来管理同一个文件的输出流,则输入指针和输出指针将彼此独立地移动,因此tellg()和tellp()将返回不同的值。
使用临时文件
cstdio 头文件中声明的tmpnam() 标准函数,可以创建一个临时文件名。
原型如下:
char * tmpnam (char * pszName);
该函数将创建的临时文件名放在pszName指向的C-风格字符串中。
常量 L_tmpnam 和 TMP_MAX (二者都在cstdio中定义的)限制了文件名包含的字符数以及在确保当前目录中不生产重复文件名的情况下tmpnam()可被调用的最多次数。