本篇会讲述文件流的详细操作和实际开发中碰到的一些典型问题
文件写入操作
-
append写入模式
以下示例打开一个文件且向文件写操作.若文件不存在,会创建一个参数指定的文
件.ofstream默认的构造函数会清空文件的内容。
假设需要向文件中以append的方式在文件的末尾写入内容,可以向ofstream 构造函数 传入ofstream::app,意味着将output指针移动文件的末尾.#include <iostream> #include <fstream> #include <bitset> #include <complex> int main(int argc, char const *argv[]) { ofstream of("../files/itdog.txt"); of << "Experience is the mother of wisdom" << endl; of << 234 << endl; of << 2.3 << endl; }
string filename("../file/itdog.txt"); ofstream of(filename,ofstream::app); of<<"Hello World!!"<<endl; of<<bitset<8>(1723)<<endl;
-
操作文件指针
下面的示例是向文件的中间部分执行写操作,可以向ofstream构造函数传入ofstream::in | ofstream::out,因为我们需要移动文件的output指针到文件指定的位置,所以我需要读取文件,因此我们需要传入ofstream::in。值得注意的是,从文件中间位置插入字节数据通常会破坏文件的原有的内容.ofstream of("../files/itdog.txt",ofstream::in | ofstream::out); of.seekp(4, ios::beg); of << "Peter Yang!!" << endl; of << 44.32 << endl; of << bitset<8>(1734)<< endl;
我们不妨先看看我们原本的itdog.txt里面的文本数据.在执行以下代码示例代码之前,itdog.txt文件内容有何变化?
-
写入文件之前
-
写入文件之后
上面示例中我们调用ofstream的成员方法 seekp从文件的开头将output指针向由移动4个字节,从第五字节的位置开始写入指定的字节数据并覆盖原有的字节数据。这个示例说明,滥用文件指针会给你的实际应用带来很大的麻烦.
seekp操作指针示意图
在C++中,每个文件都维护两个名为get_pointer(在输入模式文件中)和put_pointer(在输出模式文件中)的指针,它指示文件中将要进行读或写的当前位置.(此上下文中的文件指针不像C++指针,但它的作用就像书中的书签一样。)。 这些指针有助于在文件中获得随机访问。 这意味着直接移动到文件中的任何位置,而不是顺序移动它。这就是C++的文件随机访问技术.
备注1:有些英文资料将get_pointer或者input pointer,这里中文译成get指针,为了便于记忆,我这里叫"读指针"
备注2:有些英文资料将put_pointer或者output pointer,这里中文译成put指针,为了便于记忆,我这里叫"写指针"
一般来说结合易于识别的占位字符和文件随机访问技术一起使用是最好文件读写技术。 例如,在一个以文本形式写一个简单的员工数据库中,修改ID号为112号记录中的值,然后使用随机访问技术,则可以将文件指针放在记录112号的开头,然后直接处理记录。 如果使用顺序访问,则您必须不必要的前111项记录才能达到112号的记录。
在C ++中,通过操纵seekg(),seekp(),tellg()和tellp()函数来实现随机访问。 seekg()和tellg()函数允许您设置和检查get_pointer(读指针),而seekp()和tellp()函数在put_pointer(写指针)上执行这些操作。
seekg()和seekp()以及tellg()和tellp()的工作原理是一样的,只是seekg()和tellg()适用于ifstream对象,seekp()和tellp()适用于ofstream对象。 在下表中,seek_dir采用定义enum seek_dir {ios::beg,ios::cur,ios::end};。
文件指针常见函数
seekg()或seekp(),当根据Form 1使用时,它分别将get_pointer或put_pointer移动到绝对位置.
当根据上表中的形式B使用seekg()或seekp()函数时,它会根据seek_dir的定义将get_pointer或put_pointer移动到相对于当前位置的位置。 因为,seek_dir是头文件iostream.h中定义的枚举,它具有以下值:
- ios::beg:执行文件的开头
- ios::cur :文件当前位置
- ios::end :文件的未端
函数tellg()和tellp()分别在输出文件和输入文件中返回put_pointer和get_pointer的字节数的位置。
以下是以上文件指针函数的常见的使用范例.
string filename
ifstream inf(filename);
ofstream ouf(filename);
of.seekp(30,ios::beg); //从文件开端算起,前进30个字节
of.seekp(-5,ios::end); //从文件末尾算起,倒数第5个字符开始
inf.seekg(-3,ios:cur); //从文件当前位置算起,get_pointer()/读指针后退三个字节
inf.seekg(0,ios::end); //读指针移动到文件的末端
检测文件exist问题
这是在进行文件I/O读写前,检测文件是否存在是一个非常好的习惯,这样有助于我们是新建一个指定名称的文件还是已经存在的文件上进行读写,Unix/Linux的系统调用API中提供了高性能的stat()函数,如果你的开发环境并非Unix/Linux的系统,那么退而求次的方案就是C的stdio库中fopen调用
值得注意的是:stat和fopen分别有各自扩展版本,分别是stat64,fopen64.如果你读写的文件是超过2GB的话,那么可以是64的版本.
- Unix/Linux的封装实现
inline bool exists(const std::string& filename) {
struct stat64 buf;
return (stat64(filename.c_str(), &buf) == 0);
}
- Windows的封装实现
inline bool exists(const std::string& filename) {
if (FILE* tmp = fopen64(filename.c_str(), "r")) {
fclose(tmp);
return true;
}
return false;
}
我们会将上面的exist函数放到一个tool.cpp文件中,作为一个工具函数使用.
文件写入的问题
如果是对一个不存在的文件执行读/写操作,来考虑下面的代码会如你所愿运行吗?
这段代码根本没有都filename指定的文件创建和写入数据。
string filename("../files/emp.db");
fstream dbf;
dbf.open(filename,ios::in | ios::out);
.....
class Emp{
.....
}
.....
#emp实例
Emp em=Emp();
dbf.write((char*)&emp,sizeof(emp));
dbf.close();
如果你的意图是对一个指定的名称的文件在不存在的情况下需要先创建的话,正确的步骤是对fstream构造函数只传入ios::out)告诉程序你先filename指定的文件执行只写,然后该块保存然后在关闭该文件.
一定要牢记fstream(filename,ios::out);的默认行为是
- 如果filename不存在会创建一个空白的文件,然后对该文件执行写入.
事实上,它会告诉系统(以Linux系统的文件系统为例)为filename指定的文件在超级块(Super Block)中分配一个块,这个块记录了filename的文件信息,例如:文件的创建日期,修改日期,文件尺寸等信息,只不过这里的文件是一个空文件.更加贴切的例子,超级块比喻图书管理员的图书索引,图书管理员预先在索引库中为即将到来的一批新书预先作一个备案.,然后这个备案信息会指向哪一个图书柜存放这批新书 ,只不过这个柜子目前是空的。- 如果filename已经存在的话,那么会覆盖文件原先的内容.
那么正确的文件写入流程是如下代码所示.
string filename("../files/emp.db");
fstream dbf;
if(!exist(filename){
dbf.open(filename,ios::out);
dbf.close();
}else{
dbf.open(filename,ios::in | ios::out);
}
.....
class Emp{
.....
}
.....
#emp实例
Emp em=Emp();
dbf.write((char*)&emp,sizeof(emp));
dbf.close();
这篇未完,由于篇幅有限,完整的文件I/O例子放到下一篇.
网友评论