• C++入门 -- 文件流操作


    参考

    计算机科学的角度来看,所有的文件都是由二进制位组成的,都是二进制文件。文本文件和其他二进制文件只是格式不同而已。

    C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:

    • ifstream:专用于从文件中读取数据;
    • ofstream:专用于向文件中写入数据;
    • fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。

    以上三个类都在<fstream>头文件中,三个类的继承关系如下:

      <iostream> 头文件中定义有 ostream 和 istream 类的对象 cin 和 cout 不同,<fstream> 头文件中并没有定义可直接使用的 fstream、ifstream 和 ofstream 类对象。

      因此,如果我们想使用该类操作文件,需要自己创建相应类的对象。

    为什么 C++ 标准库不提供现成的类似 fin 或者 fout 的对象呢?其实很简单,文件输入流和输出流的输入输出设备是硬盘中的文件,硬盘上有很多文件,到底应该使用哪一个呢?所以,C++ 标准库就把创建文件流对象的任务交给用户了。

    fstream 类拥有 ifstream 和 ofstream 类中所有的成员方法,以下罗列了 fstream 类一些常用的成员方法。

    示例:

     1 #include <iostream>
     2 #include <fstream>
     3 
     4 using namespace std;
     5 
     6 int main() {
     7     const char *url = "tianyu";
     8     //创建一个fstream对象-文件流
     9     fstream f1;
    10     //将test.txt文件与f1文件流关联
    11     f1.open("test.txt", ios::out);
    12     //向test.txt文件中写入url字符串
    13     f1.write(url, 10);
    14     f1.close();
    15     return 0;
    16 }

    执行程序,该程序同目录下会生成一个 test.txt 文件,该文件的内容为:

    tianyu

    open打开文件

    void open(const char* szFileName, int mode)
    //参数1:指向文件名的指针
    //参数2:打开模式标记

    文件打开模式标记“:

     示例:流对象打开文件

    #include <iostream>
    #include <fstream>
    
    using namespace std;
    
    int main() {
        //使用流对象的open成员函数打开文件
        ifstream inFile("inFile.txt", ios::in);
        if(inFile) 
            inFile.close();
        else
            cout << "inFile.txt doesn't exist" << endl;
        //使用流对象的构造函数打开文件
        ofstream oFile("inFile.txt", ios::out);
        if(!oFile)
            cout << "error 1" << endl;
        else
            oFile.close();
        
        fstream oFile2("test1.txt", ios::in|ios::out|ios::trunc);
        oFile2.close();
    
        return 0;
    }

    close() 关闭文件流

    在实际进行文件操作的过程中,对于打开的文件,要及时调用 close() 方法将其关闭,否则很可能会导致读写文件失败。

    示例:

     1 #include <iostream>     //std::cout
     2 #include <fstream>      //std::ofstream
     3 using namespace std;
     4 
     5 int main()
     6 {
     7     const char * url = "http://c.biancheng.net/cplus/";
     8     //以文本模式打开out.txt
     9     ofstream destFile("out.txt", ios::out);
    10     if (!destFile) {
    11         cout << "文件打开失败" << endl;
    12         return 0;
    13     }
    14     //向out.txt文件中写入 url 字符串
    15     destFile << url;
    16     //程序抛出一个异常
    17     throw "Exception";
    18     //关闭打开的 out.txt 文件
    19     destFile.close();
    20     return 0;
    21 }

    17行抛出一个异常,若不处理即崩溃,导致没有close。

    对于 destFile << url ,会先将字符串写到输出流缓冲区,待缓冲区满或者关闭文件时,数据才会写入到文件。直到崩溃,close都没执行

    导致字符串一直在缓存区没有写入到文件

    flush()刷新缓冲区

     在很多实际场景中,即便已经对文件执行了写操作,但后续还可能会执行其他的写操作。对于这种情况,我们可能并不想频繁地打开/关闭文件,可以使用 flush() 方法及时刷新输出流缓冲区,也能起到防止写入文件失败的作用。

     1 #include <iostream>     //std::cout
     2 #include <fstream>      //std::ofstream
     3 using namespace std;
     4 
     5 int main()
     6 {
     7     const char * url = "http://c.biancheng.net/cplus/";
     8     //以文本模式打开out.txt
     9     ofstream destFile("out.txt", ios::out);
    10     if (!destFile) {
    11         cout << "文件打开失败" << endl;
    12         return 0;
    13     }
    14     //向out.txt文件中写入 url 字符串
    15     destFile << url;
    16     //刷新输出流缓冲区
    17     destFile.flush();
    18     //程序抛出一个异常
    19     throw "Exception";
    20     //关闭打开的 out.txt 文件
    21     destFile.close();
    22     return 0;
    23 }

    在抛出异常崩溃前,刷新流缓冲区,会将字符串写入到文件。

    C++ 中使用 open() 打开的文件,在读写操作执行完毕后,应及时调用 close() 方法关闭文件,或者对文件执行写操作后及时调用 flush() 方法刷新输出流缓冲区。

    文件读写方法

    1、使用 << 与 >> 实现读写文件:使用于文本形式读写文件;

    2、使用read() 和 write() 成员方法读写文件:适用于二进制读写文件。

     ostream::write()方法写文件

    1 ostream & write(char* buffer, int count);
    2 //buffer 用于指定要写入文件的二进制数据的起始位置;
    3 //count 用于指定写入字节的个数

    函数返回的是引用形式的对象,obj.write() 方法的返回值就是对 obj 对象的引用

    注意:write方法会从文件写指针的位置将二进制数据写入,默然指向开头,若以 ios::app方式打开,则指向文件末尾

     istream::read()方法读文件

    1 istream & read(char* buffer, int count);、
    2 //buffer 用于指定读取字节的起始位置
    3 //count 指定读取字节的个数

    移动和获取文件读写指针(seekp、seekg、tellg、tellp)

    ostream & seekp (int offset, int mode);
    istream & seekg (int offset, int mode);

    mode 代表文件读写指针的设置模式,有以下三种选项:

    • ios::beg:让文件读指针(或写指针)指向从文件开始向后的 offset 字节处。offset 等于 0 即代表文件开头。在此情况下,offset 只能是非负数。
    • ios::cur:在此情况下,offset 为负数则表示将读指针(或写指针)从当前位置朝文件开头方向移动 offset 字节,为正数则表示将读指针(或写指针)从当前位置朝文件尾部移动 offset字节,为 0 则不移动。
    • ios::end:让文件读指针(或写指针)指向从文件结尾往前的 |offset|(offset 的绝对值)字节处。在此情况下,offset 只能是 0 或者负数
    int tellg();
    int tellp();

    得到当前读写指针的具体位置:

    • ifstream 类和 fstream 类还有 tellg 成员函数,能够返回文件读指针的位置;
    • ofstream 类和 fstream 类还有 tellp 成员函数,能够返回文件写指针的位置。

    要获取文件长度,可以用 seekg 函数将文件读指针定位到文件尾部,再用 tellg 函数获取文件读指针的位置,此位置即为文件长度。

    示例:

     1 std::vector<std::vector<char>> Avs::adjustLUT(
     2     const std::string& calFileURL, const std::vector<std::string>& maskFileURLs,
     3     const std::vector<AVS_ADJUST_S>& adjustParams) {
     4     assert(maskFileURLs.size() == adjustParams.size());
     5 
     6     std::ifstream calFile(calFileURL, std::ios::binary);  //binary:以二进制方式打开文件
     7     assert(calFile.is_open());
     8 
     9     std::vector<char> calBuffer(AVS_CALIBRATION_FILE_LENGTH);
    10     calFile.read(calBuffer.data(), calBuffer.size());  //从calFile读取size()大小的数据到calBuffer
    11 
    12     //将数个maskFile存入到maskBuffer容器中
    13     std::vector<std::vector<char>> maskBuffers;
    14     for (auto& url : maskFileURLs) {
    15         std::ifstream file(url, std::ios::binary);  //将maskFile与file对象绑定
    16         assert(file.is_open());
    17         file.seekg(0, file.end);  //将文件指针指向文件尾
    18 
    19         //创建一个大小为文件长度大小的容器buffer
    20         std::vector<char> buffer(file.tellg());  //tellg 函数获取文件读指针的位置,此位置即为文件长度
    21         file.seekg(0, file.beg);  //文件指针指向文件开始处
    22         file.read(buffer.data(), buffer.size());  //从file(maskFile)读取size大小的数据到buffer
    23         maskBuffers.push_back(std::move(buffer));  //每循环一次,在maskBuffer容器后加入一个Buffer
    24     }
    25     
    26     ...
    27 }

    注意

    1. C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作。
    2. std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.。
    3. 对指针类型的标准库对象并不需要这么做.

    原lvalue值被moved from之后值被转移,所以为空字符串.。一般在代码段的末尾使用move(value),被转移所有权的value不可以再次被使用

    move用法示例:

    //摘自https://zh.cppreference.com/w/cpp/utility/move
    #include <iostream>
    #include <utility>
    #include <vector>
    #include <string>
    int main()
    {
        std::string str = "Hello";
        std::vector<std::string> v;
        //调用常规的拷贝构造函数,新建字符数组,拷贝数据
        v.push_back(str);
        std::cout << "After copy, str is "" << str << ""
    ";
        //调用移动构造函数,掏空str,掏空后,最好不要使用str
        v.push_back(std::move(str));
        std::cout << "After move, str is "" << str << ""
    ";
        std::cout << "The contents of the vector are "" << v[0]
                                             << "", "" << v[1] << ""
    ";
    }

    输出:

    After copy, str is "Hello"
    After move, str is ""
    The contents of the vector are "Hello", "Hello"
  • 相关阅读:
    arcgis10寻宝 使用一个图层切割一个数据集下所有数据
    ArcGIS 图框生成,批量打印,mxd和jpg图片一气呵成,一劳永逸
    思想>人生
    MySql MediumBlob——MySql的Bolb四种类型
    黄金连分数(高精度除法)
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'
    第九学 linux内核——内存寻址——分页机制(1)
    ACID——数据库事务正确执行的四个基本要素
    10位创始人的寄语
    Zookeeper(八)分布式队列
  • 原文地址:https://www.cnblogs.com/y4247464/p/13967523.html
Copyright © 2020-2023  润新知