八、文件操作

文件基本概念和文件流类

文件的概念

  • 从不同的角度来看待文件就可以得到不同的文件分类。C++根据文件数据的编码方式不同分为文本文件二进制文件。根据存取方式不同分为顺序存取文件随机存取文件
  • 所谓文本文件二进制文件是从文件格式的角度进行分类,是约定俗成的、从计算机用户角度出发进行的分类。
  • 所谓的顺序存取文件随机存取文件是根据访问文件中数据的方式来划分的。顺序存取文件就是按照文件中数据存储次序进行顺序操作,为访问第i个数据,就首先要访问第i-1个数据,在整个文件操作过程中,将移动位置指针的工作交给系统自动完成。磁带文件就是一个典型的顺序存取文件。随机访问文件是根据应用的需要,通过命令移动位置指针直接定位到文件内需要的位置并进行数据操作。
  • 对文件的基本操作分为读文件写文件。所谓读文件就是将文件中的数据读入内存之中,也称为输入。所谓写文件就是将内容中的数据存入文件之中,也称为输出

C++文件流类

C++标准类库中有3个流类可以用于文件操作,这3个类同城为文件流类,分别如下:

  1. ifstream:用于从文件中读取数据
  2. ofstream:用于向文件中写入数据
  3. fstream:即可用于从文件中读取数据,又可用于向文件中写入数据

使用这2个流类时,程序中需要包含fstream头文件。

ifstream和类fstream都是从类istream派生而来的,因此类ifstream拥有类istream的全部成员函数。同样,类ofstream和类fstream也拥有类ostream的全部成员函数。这3个类中有一些十分熟悉的成员函数可以使用,如operator<<operator>>peek()ignore()getline()get() 等。

在程序中,要使用一个文件,必须包含3个基本步骤:打开(open)文件----操作文件----关闭(close)文件。操作文件就是对文件进行读/写。

C++文件流类有相应的成员函数来实现打开、读、写、关闭等文件操作。

打开和关闭文件

打开文件

打开文件的方式有以下两种:

  1. 先建立流对象,然后调用open()函数连接外部文件。格式如下:

    流类名 对象名;
    对象名.open(文件名, 模式);
    
  2. 调用流类带参数的构造函数,在建立流对象的同时连接外部文件。格式如下:

    流类名 对象名(文件名, 模式);
    

    其中的“流类”是C++流类库定义的文件流类ifstreamofstreamfstream。若要以读方式打开文件则应使用类ifstream,若以写方式打开文件则应使用类ofstream,若以读/写方式打开文件则应使用类fstream

模式标记 适用对象 作用
ios::in ifstream
fstream
以读方式打开文件。
如果文件不存在,则打开出错
ios::out ofstream
ftream
以写方式打开文件。
如果文件不存在,则新建该文件;
如果文件已经存在,则打开清除原来的内容
ios::app ofstream 以追加方式打开文件,用于在文件尾部添加数据。
如果文件不存在,则新建该文件
ios::ate ofstream 打开一个已有的文件,并将文件读指针指向文件末尾。
如果文件不存在,则打开出错
ios::trunc ofstream 删除文件现有内容。单独使用时与ios::out相同
ios::binary ifstream
ofstream
fstream
以二进制方式打开文件。
若不指定此模式,则以默认的文本模式打开文件
ios::in | ios::out fstream 打开已存在的文件,既可读取其内容,也可向其写入数据。
文件刚打开时,原有内容保持不变。
如果文件不存在,则打开出错
ios::in | ios::out ofstream 打开已存在的文件,可向其写入数据。
文件刚打开时,原有内容保持不变。
如果文件不存在,则打开出错

例如,要从当前文件夹中名为data.txt的文件中读取数据,可以使用如下语句打开文件。

ifstream inFile;//建立输入文件流对象
inFile.open("data.txt", ios::in);//连接文件,指定打开模式

也可以使用第二种方式打开,语句如下:

ifstream inFile("data.txt", ios::in);

调用ifstream类带参数的构造函数,在建立流对象的同时,用参数形式连接外部文件并在指定打开模式。

要以读方式打开文本文件,还可以使用如下语句:

ifstream inFile;//建立输入文件流对象
inFile.open("data.txt");//没有指定打开模式,默认以in方式打开文本文件

再比如,要在c盘的c2020文件夹中打开(创建)一个名为newfile的二进制文件,用于保存程序产生的数据,可以使用如下语句打开文件:

ofstream outFile;//建立输入文件流对象
outFile.open("c:\\c2020\\newfile", ios::out | ios::binary);//连接文件,指定打开方式

也可以使用如下语句打开文件:

ofstream outFile("c:\\c2020\\newfile", ios::out | ios::binary);

关闭文件

使用fstream中的成员函数close()关闭文件。

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    //声明对象inFile并调用构造函数
    ifstream inFile("c:\\temp\\test.txt", ios::in);
    if (inFile) {
        cout << "成功打开文件:c:\\temp\\test.txt" << endl;
        inFile.close();
    } else {
        cout << "打开文件失败:c:\\temp\\test.txt" << endl;
    }
    
    //声明对象outFile并调用构造函数
    ofstream outFile("test1.txt", ios::out);
    if (!outFile) {
        cout << "error" << endl;
    } else {
        cout << "成功打开文件:test1.txt" << endl;
        outFile.close();
    }
    
    //声明对象outFile2并调用构造函数
    fstream outFile2("temp\\test2.txt", ios::in | ios::out);
    if (outFile2) {
        cout << "成功打开文件:temp\\test2.txt" << endl;
        outFile2.close();
    } else {
        cout << "error2" << endl;
    }

    return 0;
}

文件读写操作

读写文本文件

示例程序:从键盘输入学生的学号(假设不超过10个字节)、姓名(假设不超过20个字节)和成绩(整型),将它们存入文件score.txt中。可以使用文本文件保存数据,文件中每一行保存一名学生的成绩信息,学生成绩信息的数据项之间通过空格符分隔。

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    char id[11], name[21];
    int score;
    ofstream outFile;
    
    //以写的方式打开文本文件
    outFile.open("score.txt", ios::out);
    if (!outFile) {
        cout << "create file failed" << endl;
        return 0;
    } 
    cout << "请输入:学号 姓名 成绩(以`ctrl+z`结束输入)" << endl;
    
    while (cin >> id >> name >> score) {
        //向流中插入数据
        outFile << id << " " << name << " " << score << endl;
        outFile.close();
    }
    
    return 0;
}
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

int main() {
    char id[11], name[21];
    int score;
    ifstream inFile;
    
    //以读方式打开文本文件
    inFile.open("score.txt", ios::in);
    if (!inFile) {
        cout << "open file failed" << endl;
        return 0;
    } 
    cout << "学号 姓名 成绩" << endl;
    
    while (inFile >> id >> name >> score) {//读入文件
        //向流中插入数据
        cout << left << setw(10) << id << " " << setw(20) << name << " " << setw(3) << score << endl;
        inFile.close();
    }
    
    return 0;
}
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

int main() {
    char ch, filename[20];
    int count = 0;//行号计数器
    bool newline = true;//开始一个新的标志
    
    cout >> filename;
    //以读的方式打开文本文件
    ifstream inFile(filename, ios::in);
    if (!inFile) {
        cout << "open file failed" << endl;
        return 0;
    } 
    //从流inFile中读入一个字符并判断
    while ((ch = inFile.get()) != EOF) {
        if (newline) {//若是新行开始,则显示行号
            cout << setw(4) << ++count << ":";
            newline = false;//清除新行标志
        }
        if (ch == "\n") {//若读入字符为“\n”,则表示将开始一个新行
            newfile = true;//设置新行标志
            cout << ch;
        }
    }
    inFile.close();
    
    return 0;
}

读写二进制文件

对二进制文件进行读写不能使用前面提到的类似于cincout从流中读写数据的方法。C++用binary方式打开二进制文件,调用ifstreamfstreamread()成员函数从文件中读取数据,调用ofstreamfstreamwirite()成员函数向文件中写入数据。

1.用ostream::write()成员函数写文件

ofstreamfstreamwirite()成员函数继承自ostream类,原型如下:

ostream & write(char *buffer, int nCount);

该成员函数将内存中buffer所指向的nCount个字节的内容写入文件,返回值是对函数所作用的对象的引用,如obj.write(...)的返回值就是对obj的引用。该函数是非格式化操作,将buffer所指的数据按字节序列直接存入文件中。

在使用write()read()进行数据读写时,不必在数据之间再额外“插入”分隔符,这是因为它们都要求提供第2个参数来指定读写长度。

#include <iostream>
#include <fstream>
using namespace std;

class CStudent {
public:
    char id[11];
    char name[21];
    int score;
}

int main() {
    CStudent stu;
    //以二进制写方式打开文本文件
    ofsteam outFile("student.dat", ios::out | ios::binary);
    if (!outFile) {
        cout << "创建文件失败" << endl;
        return 0;
    }
    cout << "请输入:学号 姓名 成绩(以`ctrl + z`结束输入)" << endl;
    while (cin >> stu.id >> stu.name >> stu.score) {
        outFile.write((char *)&stu, sizeof(stu));//向文件中写入数据
        outFile.close();
    }

    return 0;
}

2.用istream::read()成员函数读文件

ifstreamfstreamread()成员函数实际上继承自类istream,原型如下:

istream &read(char *buffer, int nCount);

该成员函数从文件中读取nCount个字节的内容,存放到buffer所指向的内存缓冲区中,返回值是对函数所作用的对象的引用。该函数时非格式化操作,对读取的字节序列不进行处理,直接存入buffer中,由程序的类型定义解释。

3.用ostream::gcount()成员函数得到读取字节数

如果要知道每次读操作成功读取了多少个字节,可以在read()函数执行后立即调用文件流对象的成员函数gcount(),其返回值就是最近一次read()函数执行时成功读取的字节数。gcount()成员函数原型如下:

int gcount();

用成员函数put()get()读写文件

函数get()有3种主要形式

  1. int get();
    不带参数的get()函数从指定的输入流中提取一个字符(包含空白字符),函数的返回值即为该字符。当遇到文件结束符时,返回系统常量EOF

  2. istream &get(char &rch);
    从指定输入流这种提取一个字符(包含空白字符),将该中字符只作为rch引用的对象。当遇到文件结束符时,函数返回0;否则返回对istream对象的引用。

  3. istream &get(char *pch, int nCount, char delim = '\n');
    从流的当前字符开始,读取nCount - 1个字符,或遇到指定的分隔符delim结束。函数把读取的字符(不包括分隔符)写入数组pch中,并在字符串后添加结束符\0

函数put()的语法格式如下

ostream &put(char ch);

函数的功能是向输出流中插入一个字节。

成员函数get()put()常用于读写字符或文本文件,但他们不仅仅可用于对字符的处理,而且对于二进制值文件同样可以进行有效的处理。

文本文件与二进制文件的异同

  • 在输入/输出过程中,系统要对内外存的数据格式进行相应转换,文本文件是以文本形式存储数据,其优点是具有较高的兼容性。缺点是存储一批纯数值信息时,要在数据之间人为的添加分隔符。另一个缺点是不便于对数据进行随机访问
  • 二进制文件是以二进制形式存储数据,其优点是便于对数据实行随机访问(相同数据类型的数据所占空间的大小均是相同的,不必在数据之间人为地添加分隔符)。在输入/输出过程中,系统不需要对数据进行任何转换。缺点是数据兼容性差。
  • 通常将纯文本信息(如字符串)以文本文件形式存储,而将数值信息以二进制文件形式存储。

随机访问文件

  • 如果一个文件只能进行顺序存取操作,则称为顺序文件。典型的顺序文件(设备)是键盘、显示器和保存在磁带上的文件。如果一个文件可以在文件的任意位置进行存取操作,则成为随机文件。磁盘文件就是典型的随机文件。
  • 在访问文件的过程中,若严格按照数据保存的次序从头到尾访问文件,则称为顺序访问。
  • 在访问文件的过程中,若不必按照数据的存储次序访问文件,而是要根据需要在文件的不同位置进行访问,则称为随机访问。
  • 显然,对于顺序文件只能进行顺序访问;对于随机文件既可以进行顺序访问,也可以进行随机访问。

istream中与位置指针相关的函数如下

  1. 移动读指针函数

    istream &seekg(long pos);
    

    该函数的功能是将渎职者指针设置为pos,即将读指针移动到文件的pos字节处。

    istream &seekg(long offset, ios::seek_dir dir);
    

    该函数的功能是将读指针按照seek_dir的指示(方向)移动offset个字节,其中seek_dir是在类ios中定义的一个枚举类型。

    enum seek_dir {
        beg = 0,
        cur,
        end
    };
    

    seek_dir的常量值含义如下:

    • ios::beg:表示流的开始位置。此时,offset应为非负正整数。
    • ios::cur:表示流的当前位置。此时,offset为正数则表示向后(文件尾)移动,为负数则表示向前(文件头)移动。
    • ios::end:表示流的结束位置。此时,offset应为非正整数。
  2. 返回写指针当前位置的函数

    long tellg();
    

    函数返回值为流这种读指针的当前位置。

ostream中与位置指针相关的函数如下

  1. 移动写指针函数

    ostream &seekp(long pos);
    

    改函数的功能是将写指针设置为pos,即将写指针移动到文件的pos字节处。

    ostream &seekp(long offset, ios::seek_dir dir);
    

    该函数的功能是将写指针按seek_dir指示的方向移动offset个字节。

  2. 返回写指针当前位置的函数

    long tellp();
    

    函数的返回值为流中写指针的当前位置。

    注意:在类fstream中既提供了操作读指针函数seekg()tellg(),又提供了操作写指针的函数seekp()tellp(),实际上在文件中这两个指针是同一个指针。

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