前言
本地每一次測(cè)試都要從控制臺(tái)輸入測(cè)試數(shù)據(jù),久而久之,博主就很煩了。
所以就想著要了解文件的輸入輸出流,把測(cè)試數(shù)據(jù)放在txt文檔里,直接從文檔里讀取。

Iostream
從控制臺(tái)獲取信息并輸出到控制臺(tái)。不是本文的重點(diǎn),附上例子帶過就好。
提一句,一定要寫上命名空間,否則不能直接用cin和cout!
#include?<iostream>
using?namespace?std;
void?cinAndCout(){
????cout<<"請(qǐng)輸入信息:"<<endl;
????string?a;
????cin>>a;
????cout<<"你輸出的信息為:?"<<a;
}
int?main()?{
????cinAndCout();
?return?0;
}
Fstream
iostream是可以從鍵盤獲取輸入信息;而fstream則是可以從TXT等文件中獲得信息的。必須要包含下面的兩個(gè)頭文件才行。
#include?<iostream>
#include?<fstream>建立連接
使用open方法建立鏈接,文件路徑有兩個(gè)辦法。
-
單個(gè)項(xiàng)目?jī)?nèi)使用相對(duì)路徑,注意起點(diǎn)是可執(zhí)行文件所在的位置。后來難免會(huì)切換運(yùn)行環(huán)境,甚至是運(yùn)行機(jī)器,所以也會(huì)用絕對(duì)路徑。
-
絕對(duì)路徑來指明文件,注意路徑中最后不要有空格或中文字符。與宏定義結(jié)合起來更為方便。
也定義下列常量,來標(biāo)明建立的鏈接是哪個(gè)模式的。
| 常量 | 解釋 |
|---|---|
app |
每次寫入前尋位到流結(jié)尾 |
binary |
以二進(jìn)制模式打開 |
in |
為讀打開 |
out |
為寫打開 |
trunc |
在打開時(shí)舍棄流的內(nèi)容 |
ate |
打開后立即尋位到流結(jié)尾 |
后來,為了方便,也可以直接在生成類對(duì)象的時(shí)候直接傳入文件名稱和模式。為了方便理解,就直接附在后文的例子中了。
關(guān)閉鏈接
直接在對(duì)象上調(diào)用close方法就可以了。
常規(guī)操作
讀取文件
ifstream?fin("input.txt");
//fstream?fin("input.txt",?ios::in);//等價(jià)于上一行
????if?(!fin)?{
????????cout?<<?"打開文件出錯(cuò)"?<<?endl;
????????return?;
????}逐行讀入
逐行讀入,是需要引入string.h頭文件的。而且逐行讀取的字符串,是沒有換行符的!
string?s;
while(getline(fin,s))
{
????cout<<s<<endl;
}逐個(gè)字符讀入
忽略空格與回車。
char?c;
while?(!fin.eof())
{
????fin?>>?c;
????cout<<c<<endl;
}逐個(gè)字符讀入
包括空格與回車。
char?c;
fin?>>?noskipws;
while?(!fin.eof())
{
????fin>>c;
????cout<<c<<endl;
}寫入文件
先打開文件流,往流里面插入數(shù)據(jù)信息,然后關(guān)閉文件流。
#include?<fstream>
#include<iostream>
#define?filepath?"/Volumes/KeenMacPlus/Projects/C++項(xiàng)目/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?<<?"打開文件出錯(cuò)"?<<?endl;
????}
????fin?<<?"這是新加的一行"?<<?endl;
????fin.close();
}

應(yīng)用
讀取文件行數(shù)
#include<iostream>??
#include<fstream>??
using?namespace?std;
void?read()?{
????//?ifstream?fin("input.txt");//等價(jià)于下兩行
????fstream?fin;
????fin.open(filepath"input.txt",?ios::in);
????if?(!fin)?{
????????cout?<<?"打開文件出錯(cuò)"?<<?endl;
????}
????char?c;
????int?lineNum?=?0;//?統(tǒng)計(jì)行數(shù)
????while?(fin.get(c))//?逐字符讀入
????{
????????if?(c?==?'\n')
????????????lineNum++;
????}
????cout?<<?lineNum?+?1?<<?endl;
????fin.close();
}
重定向
之前是用cin和cout從控制臺(tái)獲得信息,重定向后可以用這兩個(gè)對(duì)象操作文件寫入寫出!
#include<fstream>
#include?<ostream>
#include?<iostream>
#include<string>
#define?filepath?"/Volumes/KeenMacPlus/Projects/C++項(xiàng)目/KeenCPPTest-all/STL/fstream/txt/"
using?namespace?std;
void?cinAndCout()?{
????cout?<<?"請(qǐng)輸入信息:"?<<?endl;
????string?a;
????cin?>>?a;
????cout?<<?"你輸出的信息為:?"?<<?a;
}
int?main()?{
????//?cinAndCout();
????ifstream?fin(filepath"rdbuf.txt");
????ofstream?fout(filepath"out.txt");
????if?(!fin?||?!fout)?{
????????cout?<<?"打開文件出錯(cuò)"?<<?endl;
????????return?0;
????}
????//?下面四行是用?rdbuf()??重新定向
????streambuf?*cinbackup?=?NULL;
????streambuf?*coutbackup?=?NULL;
????coutbackup?=?cout.rdbuf(fout.rdbuf());
????cinbackup?=?cin.rdbuf(fin.rdbuf());?//?用?rdbuf()??重新定向
????//?原先這個(gè)對(duì)象是輸出到控制臺(tái),限制是輸出到文件out.txt
????cout?<<?"Hello?world"?<<?endl;
????string?line;
????while?(cin)?{
????????cin?>>?line;//?從?rdbuf.txt?文件讀入
????????if?(cin)
????????????cout?<<?line?<<?endl;//?寫入?out
????}
????fout.close();
????fin.close();
????//恢復(fù)標(biāo)準(zhǔn)輸入輸出
????//?如果不恢復(fù),即使關(guān)閉了文件流,仍然無法輸出到控制臺(tái)!
????cin.rdbuf(cinbackup);?//?恢復(fù)鍵盤輸入
????cout.rdbuf(coutbackup);?//?恢復(fù)控制臺(tái)輸出
????cout?<<?"完成操作!";
????return?0;
}按行號(hào)修改數(shù)據(jù)
一共有兩種實(shí)現(xiàn)邏輯,分別是針對(duì)大文件和小文件的。
不論是修改還是刪除,其實(shí)底層邏輯其實(shí)是一樣的。
第一步是讀取原來的文件,一般是for循環(huán)逐行讀取。
第二步是在循環(huán)的過程中匹配行號(hào)或者關(guān)鍵字,當(dāng)匹配成功就對(duì)內(nèi)容進(jìn)行修改或者舍棄
第三步,則是將讀到內(nèi)存的信息流寫入文件,目標(biāo)可以是新的文件,也可以是原來的文件。
按照緩存數(shù)據(jù)的方式可以分為兩種,一種是針對(duì)大文件,短時(shí)間內(nèi)無法完成全部操作,這時(shí)候我建議使用中間文件做緩存,即使出現(xiàn)意外也有之前留下的內(nèi)容;而對(duì)于小文件,則是可以選擇將文件內(nèi)容全部存到內(nèi)存里面來,然后再一次性覆蓋回原來的文件。
其實(shí)刪除某一行內(nèi)容也是屬于這種操作,整體文件流區(qū)別變化是不大的。
中間文件緩存
/*
?*?修改指定行號(hào)的數(shù)據(jù)為新的內(nèi)容
?*?三個(gè)參數(shù)分別是文件名,行號(hào),要修改的內(nèi)容
?*?底層邏輯是把文件信息全存儲(chǔ)到中間文件里,然后再把中間文件的內(nèi)容覆蓋源文件的內(nèi)容
?*
?*?好處是即使出錯(cuò)了或者斷電了,那么之前的數(shù)據(jù)仍然是保留在中間文件中的
?*?壞處是要讀寫量增大了,很耗費(fèi)時(shí)間,適合大文件?*?*/
void?modiFile(string?filename,?int?lineNum,?string?str)?{
????ifstream?file(filename);
????string?line;?//?臨時(shí)變量,讀取文件的一行內(nèi)容
????int?num?=?0;//?統(tǒng)計(jì)行號(hào)
????ofstream?outfile("test.txt",?ios::out?|?ios::trunc);//用中間文件,記錄整個(gè)文件的信息
????//將信息從源文件寫到中間文件
????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
}用變量緩存
/*
?*?修改指定行號(hào)的數(shù)據(jù)為新的內(nèi)容
?*?三個(gè)參數(shù)分別是文件名,行號(hào),要修改的內(nèi)容
?*?底層邏輯是把文件信息全存儲(chǔ)到字符串變量outStr里,然后再覆蓋原先的內(nèi)容
?*
?*?好處是不需要生成另一個(gè)臨時(shí)文件來存儲(chǔ)內(nèi)容,適合小文件。
?*?缺點(diǎn)就是一旦在過程中出錯(cuò)了或者斷電了,那么之前的數(shù)據(jù)就全沒有了!?*?*/
void?modiFile2(string?filename,?int?lineNum,?char?*content)?{
????ifstream?in(filename);
????string?line;?//?臨時(shí)變量,讀取文件的一行內(nèi)容
????int?num?=?0;//?統(tǒng)計(jì)行號(hào)
????string?outStr;//?記錄整個(gè)文件的數(shù)據(jù)流
????while?(getline(in,?line))?{//?逐行讀取,是沒有換行符的!
????????num++;
????????if?(lineNum?!=?num)?{
????????????outStr?+=?line;
????????}?else?{
????????????outStr?+=?content;
????????}
????????outStr?+=?'\n';
????}
????in.close();
????//新建這個(gè)文件的寫入流對(duì)象,調(diào)用flush函數(shù)來清空文件流內(nèi)的內(nèi)容。
????ofstream?out;
????out.open(filename);
????out.flush();
????out?<<?outStr;
????out.close();
}文件轉(zhuǎn)碼
從GBK轉(zhuǎn)為UTF8
因?yàn)橐郧暗?cpp文件很多都是GBK編碼的,現(xiàn)在為了統(tǒng)一稱為UTF8編碼的。在Linux系統(tǒng)上面有一個(gè)iconv命令。
:?切換到指定路徑
cd?/Volumes/KeenMacPlus/Projects/C++項(xiàng)目/KeenCPPTest-all
:?find命令,在Convert_UTF8的文件夾中生成相同名稱的路徑樹
find?./KeenCPPTest-all?-type?d?-exec?mkdir?-p?Convert_UTF8/{}?\;
:?find命令?-exec?是找到文件后運(yùn)行腳本命令
:?雙引號(hào)中的腳本內(nèi)容,是將文件轉(zhuǎn)碼后放到指定的文件夾下
:?最后?\;?是find?-exec的終止標(biāo)志
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批量轉(zhuǎn)碼