前言
本地每一次测试都要从控制台输入测试数据,久而久之,博主就很烦了。
所以就想着要了解文件的输入输出流,把测试数据放在txt文档里,直接从文档里读取。
Iostream
从控制台获取信息并输出到控制台。不是本文的重点,附上例子带过就好。
提一句,一定要写上命名空间,否则不能直接用cin
和cout
!
#include <iostream>
using namespace std;
void cinAndCout(){
cout<<"请输入信息:"<<endl;
string a;
cin>>a;
cout<<"你输出的信息为: "<<a;
}
int main() {
cinAndCout();
return 0;
}
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();
}
应用
读取文件行数
#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();
}
重定向
之前是用cin
和cout
从控制台获得信息,重定向后可以用这两个对象操作文件写入写出!
#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批量转码