C++用fstream读写文件

-- TOC --

这部分属于使用C++的基本功,随时都要能够拿出来使用。

读写文本文件

基本读写

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


int main(void) {
    // ofstream, default is write txt truncate mode
    ofstream fout{"fout.txt"};
    if (!fout.is_open()) {
        cerr << "ofstream open fout.txt error" << endl;
        return 1;
    }
    fout << " 123\n45 67\t890" << endl;
    fout << "ab\rcde hj\fk\vlo " << endl;
    fout.flush();
    fout.close();

    string cont;
    // ifstream, default is read txt mode
    ifstream fin{"fout.txt"};
    if (!fin.is_open()) {
        cerr << "ifstream open fout.txt error" << endl;
        return 2;
    }
    while (fin.peek() != EOF) {
        // space and \n are both seperators
        cont.clear();
        fin >> cont;
        if (cont.size() != 0)
            cout << cont << "(" << cont.size() << ")" << endl;
    }
    fin.close();

    return 0;
}

流式地写入文本,流式地读出文本。

ofstream对象默认就是文本输出模式,并默认带有ios::trunc,这会让此文件原有的内容被清理掉(w)。ifstream对象默认就是本文读取模式(r),每当遇到whitespace符号,算一次读出。读出来的cont,已经做了trim,自动去掉了whilespace符号。peek接口用于探测EOF,不会移动文件读写指针位置。is_open接口用来判断打开文件是否成功。endl用于写入换行。close关闭文件,也能起到flush的效果。

输出如下:

$ g++ -Wall -Wextra fstream.cpp -o fstream
$ ./fstream
123(3)
45(2)
67(2)
890(3)
ab(2)
cde(3)
hj(2)
k(1)
lo(2)

追加写

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


template<typename T>
void write_file(T cont) {
    ofstream fout;
    fout.open("fout.log", ios::app);
    fout << cont << endl;
    fout.close();
}


int main(void) {
    write_file("12345");
    write_file("abcde");
    write_file("https://cs.pynote.net");

    string s{"string object"};
    write_file(s);

    return 0;
}

创建ofstream对象后,用ios::appa)打开这个对象。

上例使用了template function,因此string object和rvalue都可以直接调用write_file接口。

fstream,getline, eof

本文文件还可以用getline来读取,但是传入的line buffer一定要足够长:

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


template<typename T>
void write_file(T cont) {
    fstream fout;
    fout.open("fout.log", ios::out|ios::app);
    fout << cont << endl;
    fout.close();
}


int main(void) {
    // clean file
    fstream fout{"fout.log", ios::out};
    fout.close();

    write_file("12\n345678\t90");
    write_file(" a bcd\re ");
    write_file("https://cs.pynote.net");
    string s{"string\vobject"};
    write_file(s);

    fstream fin("fout.log", ios::in);
    if (!fin.is_open())
        return 1;

    char buf[64]{};
    string cont{};
    int i=0;
    while (!fin.eof()) {  // recommend: fin.peek() != EOF
        fin.getline(buf, 64);
        cont = string{buf};
        cout << ++i << " : " << cont << "|" << cont.size() << endl;
    }

    return 0;
}

如果用fstream创建对象,就要明确ios::inios::out

如果将getline接口64这个参数修改为4,运行就能看到出错了(很严重),getline接口要求line buffer要足够大,大于读取的所有行。除非确定知道最大长度,否则尽量别用getline

只有\n是换行符,因此,getline只对文本中的\n符号敏感,其它whitespace符号都原样读出。(注意看下面输出的第3行,前面的index被读出的\r符号冲掉了,测试代码没有\f符号,输出的时候屏幕会上移,我不喜欢)

使用eof接口,会导致多读一次。eof接口只是判断最后getline的内容,虽然此时已经到了EOF,但是EOF还没有被读取出来,因此eof接口判断为false,继续getline一次。(peek接口真是形象呀)

以上代码的输出为:

1 : 12|2
2 : 345678      90|9
e |9 a bcd
4 : https://cs.pynote.net|21
5 : string
          object|13
6 : |0

打开属性

ios::app:    //以追加的方式打开文件  
ios::ate:    //文件打开后定位到文件尾,ios::app就包含有此属性  
ios::binary:   //以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文  
ios::in:     //文件以输入方式打开(文件数据输入到内存)  
ios::out:    //文件以输出方式打开(内存数据输出到文件)  
ios::nocreate:  //不建立文件,所以文件不存在时打开失败  
ios::noreplace: //不覆盖文件,所以打开文件时如果文件存在失败  
ios::trunc:    //如果文件存在,把文件长度设为0

多个属性的组合,用|,如前代码。

good,fail,bad

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


int main(void) {
    ofstream fout{"fout.txt"};
    if (!fout.is_open()) {
        cerr << "ofstream open fout.txt error" << endl;
        return 1;
    }
    fout << "12345" << endl;
    fout << "abcde" << endl;
    fout << "67890" << endl;
    fout.close();

    ifstream fin{"fout.txt"};
    if (!fin.is_open()) {
        cerr << "ifstream open fout.txt error" << endl;
        return 2;
    }

    int cont;
    while (fin.peek() != EOF) {  // never use fin.eof(), dead loop
        fin >> cont;
        if (fin.fail())
            cout << "fail..\n";
        else
            cout << cont << endl;
        /*if (fin.good())  // no error
            cout << cont << endl;
        else
            cout << "not good...\n";*/
    }
    fin.close();

    return 0;
}

cont的类型是int,读取文件的第2行是字符串,因此触发fail或not good。但不会触发bad接口。

get,put

按单个字符读写文件,使用get和put接口。

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


int main(void) {
    ofstream fout{"fout.txt"};
    if (!fout.is_open()) {
        cerr << "ofstream open fout.txt error" << endl;
        return 1;
    }
    fout.put('a');
    fout.put('b');
    fout.put('c');
    fout.close();

    ifstream fin{"fout.txt"};
    if (!fin.is_open()) {
        cerr << "ifstream open fout.txt error" << endl;
        return 2;
    }

    char c;
    while (fin.peek() != EOF) {
        c = fin.get();
        cout << c << endl;
    }
    fin.close();

    return 0;
}

部分接口末尾有g和p。

读写位置操作

// seek接口需要用到的第2个参数:
ios::beg   //从流开始位置计算的位移
ios::cur   //从流指针当前位置开始计算的位移
ios::end   //从流末尾处开始计算的位移
#include <iostream>
#include <fstream>
using namespace std;


int main(void) {
    ofstream fout{"fout.txt"};
    if (!fout.is_open()) {
        cerr << "ofstream open fout.txt error" << endl;
        return 1;
    }
    fout.put('a');
    cout << fout.tellp();
    fout.put('b');
    fout.seekp(0, ios::beg);
    fout.put('c');
    cout << fout.tellp();
    fout.close();

    ifstream fin{"fout.txt"};
    if (!fin.is_open()) {
        cerr << "ifstream open fout.txt error" << endl;
        return 2;
    }

    char c;
    while (fin.peek() != EOF) {
        c = fin.get();
        cout << c << fin.tellg() <<endl;
    }
    fin.seekg(0, ios::beg);
    while (fin.peek() != EOF) {
        c = fin.get();
        cout << c << fin.tellg() <<endl;
    }
    fin.close();

    return 0;
}

得到输入流的总长度

fin.seekg(0, ios::end);
length = fin.tellg();
fin.seekg(0, ios::beg);

读写二进制文件

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


int main(void) {
    char cont[5] = {'a','b','c','d','e'};

    ofstream fout("flog", ios::binary);
    if (!fout.is_open())
        return 1;
    fout.write(cont, 5);
    fout.close();

    ofstream bout("flog", ios::binary | ios::app);
    if (!bout.is_open())
        return 2;
    bout.write("12345", 5);
    bout.close();

    char rout[1]{};
    ifstream fin("flog", ios::binary);
    if (!fin.is_open())
        return 3;
    while (fin.peek() != EOF) {
        fin.read(rout, 1);
        cout << rout[0] << "|";
    }
    cout << endl;
    fin.close();

    return 0;
}

二进制文件的读写,流式符号以及getline就不好用了,而要使用writeread接口,并用ios::binary创建fstream对象。

以上测试代码输入如下:

$ g++ -Wall -Wextra fstream.cpp -o fstream
$ ./fstream
a|b|c|d|e|1|2|3|4|5|

文本文件,也可以使用read和write接口!

gcount,获取read的真实大小

虽然read接口可以填一个希望读取的大小,但有时真正读取的大小与希望值有差距,此时要用gcount接口来获取真实读取的大小。

摘取一段代码作为示例:

ifstream fin{ fname, ios::binary };
if (!fin.is_open()) {
    cerr << "open output file error" << endl;
}

while (1) {
    fin.read(inbuf, INBUF_SIZE);
    if (fin.bad()) {
        cout << "read file error" << endl;
        break;
    }
    data_size = fin.gcount();
    eof = !data_size;
    data = inbuf;
    while (data_size > 0 || eof) {
        ret = av_parser_parse2(parser, dec_context, &pkt->data, &pkt->size,
                    (const uint8_t*)data, (int)data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0) {
            fprintf(stderr, "Error while parsing\n");
            exit(1);
        }
        data += ret;
        data_size -= ret;

        if (pkt->size)
            decode(dec_context, frame, pkt);

        if (eof)
            break;
    }
    if (eof)
        break;
}
fin.close();

正好文章写道这里,也在调试av_parser_parse2接口使用的bug。这段代码读取文件没有错,错在当gcount返回0的时候,还要再调用一次前述的接口,否则最后一帧出不来。

本文链接:https://cs.pynote.net/sf/c/cpp/202209241/

-- EOF --

-- MORE --