第三篇:戏说C++文件I/O (前篇)

本篇会讲述文件流的详细操作和实际开发中碰到的一些典型问题

文件写入操作

  • append写入模式

    以下示例打开一个文件且向文件写操作.若文件不存在,会创建一个参数指定的文
    件.ofstream默认的构造函数会清空文件的内容。
     #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;
     }
    
    假设需要向文件中以append的方式在文件的末尾写入内容,可以向ofstream 构造函数 传入ofstream::app,意味着将output指针移动文件的末尾.
    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文件内容有何变化?

  • 写入文件之前
2019-08-29 14-08-40 的螢幕擷圖.png
  • 写入文件之后
2019-08-29 14-46-39 的螢幕擷圖.png

上面示例中我们调用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例子放到下一篇.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容