首页 科技文章正文

Cpp浅析系列-STL之fstream

科技 2022年11月29日 02:33 21 cndao

前言

本地每一次测试都要从控制台输入测试数据,久而久之,博主就很烦了。

所以就想着要了解文件的输入输出流,把测试数据放在txt文档里,直接从文档里读取。

Cpp浅析系列-STL之fstream

Iostream

从控制台获取信息并输出到控制台。不是本文的重点,附上例子带过就好。

提一句,一定要写上命名空间,否则不能直接用cincout

#include <iostream>using namespace std;void cinAndCout(){    cout<<"请输入信息:"<<endl;    string a;    cin>>a;    cout<<"你输出的信息为: "<<a;}int main() {    cinAndCout(); return 0;}
Cpp浅析系列-STL之fstream

image-20220828220137609

Fstream

iostream是可以从键盘获取输入信息;而fstream则是可以从TXT等文件中获得信息的。必须要包含下面的两个头文件才行。

#include <iostream>#include <fstream>

建立连接

使用open方法建立链接,文件路径有两个办法。

  • 单个项目内使用相对路径,注意起点是可执行文件所在的位置。后来难免会切换运行环境,甚至是运行机器,所以也会用绝对路径。
  • 绝对路径来指明文件,注意路径中最后不要有空格或中文字符。与宏定义结合起来更为方便。

也定义下列常量,来标明建立的链接是哪个模式的。

常量解释app每次写入前寻位到流结尾binary以二进制模式打开in为读打开out为写打开trunc在打开时舍弃流的内容ate打开后立即寻位到流结尾

后来,为了方便,也可以直接在生成类对象的时候直接传入文件名称和模式。为了方便理解,就直接附在后文的例子中了。

关闭链接

直接在对象上调用close方法就可以了。

常规操作

读取文件

ifstream fin("input.txt");//fstream fin("input.txt", ios::in);//等价于上一行    if (!fin) {        cout << "打开文件出错" << endl;        return ;    }

逐行读入

逐行读入,是需要引入string.h头文件的。而且逐行读取的字符串,是没有换行符的!

string s;while(getline(fin,s)){    cout<<s<<endl;}

逐个字符读入

忽略空格与回车。

char c;while (!fin.eof()){    fin >> c;    cout<<c<<endl;}

逐个字符读入

包括空格与回车。

char c;fin >> noskipws;while (!fin.eof()){    fin>>c;    cout<<c<<endl;}

写入文件

先打开文件流,往流里面插入数据信息,然后关闭文件流。

#include <fstream>#include<iostream>#define filepath "/Volumes/KeenMacPlus/Projects/C++项目/KeenCPPTest-all/STL/fstream/txt/"using namespace std;/* * 向文件中写入字符串,注意模式的不同 * */void write(){    // ofstream fout(filepath"input.txt");写入流,覆盖模式    fstream fin;    // fin.open(filepath"input.txt"); // 覆盖模式    fin.open(filepath"input.txt", ios::app); // 末尾追加模式    if (!fin) {        cout << "打开文件出错" << endl;    }    fin << "这是新加的一行" << endl;    fin.close();}
Cpp浅析系列-STL之fstream

image-20220828191911146

Cpp浅析系列-STL之fstream

image-20220828191919756

应用

读取文件行数

#include<iostream>  #include<fstream>  using namespace std;void read() {    // ifstream fin("input.txt");//等价于下两行    fstream fin;    fin.open(filepath"input.txt", ios::in);    if (!fin) {        cout << "打开文件出错" << endl;    }    char c;    int lineNum = 0;// 统计行数    while (fin.get(c))// 逐字符读入    {        if (c == '\n')            lineNum++;    }    cout << lineNum + 1 << endl;    fin.close();}

重定向

之前是用cincout从控制台获得信息,重定向后可以用这两个对象操作文件写入写出!

#include<fstream>#include <ostream>#include <iostream>#include<string>#define filepath "/Volumes/KeenMacPlus/Projects/C++项目/KeenCPPTest-all/STL/fstream/txt/"using namespace std;void cinAndCout() {    cout << "请输入信息:" << endl;    string a;    cin >> a;    cout << "你输出的信息为: " << a;}int main() {    // cinAndCout();    ifstream fin(filepath"rdbuf.txt");    ofstream fout(filepath"out.txt");    if (!fin || !fout) {        cout << "打开文件出错" << endl;        return 0;    }    // 下面四行是用 rdbuf()  重新定向    streambuf *cinbackup = NULL;    streambuf *coutbackup = NULL;    coutbackup = cout.rdbuf(fout.rdbuf());    cinbackup = cin.rdbuf(fin.rdbuf()); // 用 rdbuf()  重新定向    // 原先这个对象是输出到控制台,限制是输出到文件out.txt    cout << "Hello world" << endl;    string line;    while (cin) {        cin >> line;// 从 rdbuf.txt 文件读入        if (cin)            cout << line << endl;// 写入 out    }    fout.close();    fin.close();    //恢复标准输入输出    // 如果不恢复,即使关闭了文件流,仍然无法输出到控制台!    cin.rdbuf(cinbackup); // 恢复键盘输入    cout.rdbuf(coutbackup); // 恢复控制台输出    cout << "完成操作!";    return 0;}

按行号修改数据

一共有两种实现逻辑,分别是针对大文件和小文件的。

不论是修改还是删除,其实底层逻辑其实是一样的。

第一步是读取原来的文件,一般是for循环逐行读取。

第二步是在循环的过程中匹配行号或者关键字,当匹配成功就对内容进行修改或者舍弃

第三步,则是将读到内存的信息流写入文件,目标可以是新的文件,也可以是原来的文件。

按照缓存数据的方式可以分为两种,一种是针对大文件,短时间内无法完成全部操作,这时候我建议使用中间文件做缓存,即使出现意外也有之前留下的内容;而对于小文件,则是可以选择将文件内容全部存到内存里面来,然后再一次性覆盖回原来的文件。

其实删除某一行内容也是属于这种操作,整体文件流区别变化是不大的。

中间文件缓存

/* * 修改指定行号的数据为新的内容 * 三个参数分别是文件名,行号,要修改的内容 * 底层逻辑是把文件信息全存储到中间文件里,然后再把中间文件的内容覆盖源文件的内容 * * 好处是即使出错了或者断电了,那么之前的数据仍然是保留在中间文件中的 * 坏处是要读写量增大了,很耗费时间,适合大文件 * */void modiFile(string filename, int lineNum, string str) {    ifstream file(filename);    string line; // 临时变量,读取文件的一行内容    int num = 0;// 统计行号    ofstream outfile("test.txt", ios::out | ios::trunc);//用中间文件,记录整个文件的信息    //将信息从源文件写到中间文件    while (!file.eof()) {        getline(file, line);        if (num != lineNum - 1)            outfile << line << endl;        else            outfile << str << endl;        num++;    }    outfile.close();    file.close();    // 将信息从中间文件写入到源文件    ofstream outfile1(filename, ios::out | ios::trunc);    fstream file1("test.txt");    while (!file1.eof()) {        getline(file1, line);        if (line != "") {            outfile1 << line << endl;        }    }    outfile1.close();    file1.close();    //最后删除中间文件    // system("del test.txt");//Windows    system("rm -f  test.txt");// mac和linux}

用变量缓存

/* * 修改指定行号的数据为新的内容 * 三个参数分别是文件名,行号,要修改的内容 * 底层逻辑是把文件信息全存储到字符串变量outStr里,然后再覆盖原先的内容 * * 好处是不需要生成另一个临时文件来存储内容,适合小文件。 * 缺点就是一旦在过程中出错了或者断电了,那么之前的数据就全没有了! * */void modiFile2(string filename, int lineNum, char *content) {    ifstream in(filename);    string line; // 临时变量,读取文件的一行内容    int num = 0;// 统计行号    string outStr;// 记录整个文件的数据流    while (getline(in, line)) {// 逐行读取,是没有换行符的!        num++;        if (lineNum != num) {            outStr += line;        } else {            outStr += content;        }        outStr += '\n';    }    in.close();    //新建这个文件的写入流对象,调用flush函数来清空文件流内的内容。    ofstream out;    out.open(filename);    out.flush();    out << outStr;    out.close();}

文件转码

从GBK转为UTF8

因为以前的.cpp文件很多都是GBK编码的,现在为了统一称为UTF8编码的。在Linux系统上面有一个iconv命令。

: 切换到指定路径cd /Volumes/KeenMacPlus/Projects/C++项目/KeenCPPTest-all: find命令,在Convert_UTF8的文件夹中生成相同名称的路径树find ./KeenCPPTest-all -type d -exec mkdir -p Convert_UTF8/{} \;: find命令 -exec 是找到文件后运行脚本命令: 双引号中的脚本内容,是将文件转码后放到指定的文件夹下: 最后 \; 是find -exec的终止标志find ./KeenCPPTest-all -exec sh -c "iconv -f GBK -t utf-8 {} > Convert_UTF8/{}" \;

感谢

源码文件:

gitee:https://gitee.com/JunKuangKuang/KeenCPPTest-all/tree/main/STL/fstream

github.com:https://github.com/JunKuangKuang/KeenCPPTest-all/blob/main/STL/fstream

参考我以前存留的cpp文件,感谢以前努力的自己。

参考张成的博客

使用iconv批量转码

勉驰网