1.文件分類
按照文件的存取方式分
順序文件:結構簡單,文件中的數據按順序存放。在順序文件中,只知道第一條記錄的存放位置。當要查找某條記錄時,只能從文件頭開始,按順序查找,直到找到為止。
隨機文件:又稱直接存取文件,簡稱隨機文件或直接文件。隨機文件的每條記錄都 有一個記錄號,即在寫入數據時,只要指定記錄號,就可以把數據寫到指定的位置。在讀取文件時,只要給出記錄號,就可以直接讀取。
按照數據編碼方式分
文本文件:是按ASCII碼進行編碼的文件,可以用字處理軟件建立和修改(必須以純文本文件保存)
二進制文件:顧名思義,以按二進制編碼的文件,不能用普通的字處理軟件編輯,占空間較小。
1.1 文件操作的過程
用戶程序在處理文件時,只需遵從如下的步驟即可:
1)打開文件。
2)讀取文件。
3)處理數據。
4)寫回(根據需要)
5)關閉文件。
1.2 處理文件流的類
在C中,處理文件必須通過一個結構體FILE。這個結構體的主要內容就是文件名、文件起始位置、大小、當前讀寫位置信息等信息。在處理文件的過程中,用戶程序必須要通過這個結構體進行。如果打開文件 用fopen()函數、讀數據的fprintf()函數、關閉文件的fclose()函數等。
例如:
[cpp]
#include<iostream>
using namespace std;
int main()
{
FILE *pFile=fopen("a.txt","r+");//以讀寫方式打開文件
if(NULL == pFile)//文件是否存在
{
cout<<"打開文件失敗"<<endl;
system("pause");
return 0;
}
char str[256];
while(EOF!=fscanf(pFile,"%s",str))//讀取文件內容,以空格或者回車分隔
{
cout<<str<<endl;//輸出一行
}
fprintf(pFile,"END");//在文件尾部寫入"END"
fclose(pFile);//關閉文件
system("pause");
return 0;
}
寫完後文檔內容如下:
事實上,通過FILE結構對文件進行操作並不是很理想。主要是因為這麼做並不符合面向對象的思想。因此,c++提供了一系列專門用於處理文件的文件流類。這些類包括專門用於從文件中輸入數據的ifstream類,專門用於向文件輸出數據的ofstream類,以及即可輸入也可以輸出的fstream類。
需要注意的是,文件編碼有ASCII碼和UNICODE編碼之分,相應的處理文件的類也有這樣的分別。上述的ifstream、ofstream和fstream類都是用於處理ASCII編碼的文件。如果要處理UNICODE編碼的文件,則應當使用對應的wifstream、wofstream和wfstream。以下只針對ASCII碼格式的文件流類進行說明。
文件流例1:
[cpp]
#include<iostream>
#include<fstream>
//#include<cstdlib>
//#include<cstdio>
//#include<stdio.h>
using namespace std;
int main()
{
fstream file;
file.open("a.txt");//沒有指定打開方式,默認是讀寫。
if ( file.fail() )
{
cout<<"打開文件失敗"<<endl;
system("pause");
return 0;
}
char str[256];
while(file>>str)
{
cout<<str<<endl;
}
file << "fstream";//沒有寫進去,為什麼????
file.close();
system("pause");
return 0;
}
2.文件的打開與關閉
首先是創建流,然後把流和文件關聯,才能進行輸入、輸出操作,完成後要關閉文件。
三個輸入輸出流類:ofstream,ifstream,fstream.
2.1打開文件
打開文件就是用函數open()把某一個流與文件建立聯系。open()函數是上述三個流類的成員函數,定義在fstream.h中,它的原型為
void open(const unsigned char * ,int mode,int access=filefuf::openprot);
其中,第一個參數為字符串類型,其用來傳遞文件名。第二個參數是int型,其值決定文件撕開的方式,用數據流基類ios_base的數據成員指定。一般來講每種流都有固定的打開模式。如輸入文件流ifstream就是ios_base::in,輸出文件流ofstream就是ios_base::out。即便是編程時給錯了打開方式也不會出問題。
例如:構造輸入文件流對象時,用的打開模式卻是ios_base::out。這種情況下並不會影響文件的讀入。同時也不要企圖向文件中寫入數據,因為輸入文件流ifstream沒有輸出函數。第三個參數的值決定文件的訪問方式及文件的類別,默認方式是filebuf::openprot。在DOS/Windows環境中,access的值分別對應DOS/Windows的文件屬性代碼如下:
[cpp]
0 普通文件
1 只讀文件
2 隱含文件
3 系統文件
8 備份文件
例2:在當前文件夾下創建一個文本文件,並向其中寫入一個字條串。
[cpp]
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
ofstream sfile("sssfile.txt");
sfile<<"Hello world"<<endl;
system("pause");
}
例3:創建一個文件,然後打開該文件,如果文件不存在,則返回錯誤信息;否則提示文件已打開的信息。
[cpp]
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
ofstream ofile("file1.txt");
ifstream ifile;
ofile<<"welcome to China";
ifile.open("file1.txt");
if(ifile.fail())
cout<<"文件不存在,打開失敗!"<<endl;
else
cout<<"文件已打開,可以進行讀寫操作"<<endl;
ifile.close();
system("pause");
return 0;
}
2.2關閉文件
文件使用後,必須將其關閉,否則可能導致數據丟失。關閉文件就是將文件與流的聯系斷開,用函數close()完成,它也是流類中的成員函數,沒有參數,沒有返回值,其參數原型為:
對象名.close()
例:下面程序用open()函數打開一個文件,並判斷其是否打開,但不管其是否打開成功,在程序結束時均需要關閉文件。
[cpp]
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
ifstream in;
in.open("file1.txt");
if (in.fail())
cout<<"文件不存在,打開失敗"<<endl;
else
cout<<"文件已打開,可以進行讀寫操作"<<endl;
in.close();
cout<<"文件已關閉"<<endl;
system("pause");
return 0;
}
fail()函數是流類中的成員函數,可用該函數測試文件是否存在。若存在,返回0,否則返回非0。
在程序語句中,可將定義流與打開文件用一條語句完成,如:
[cpp]
#include <fstream.h>
#include <iostream>
using namespace std;
int main()
{
// fstream f("testf.txt",ios::in|ios::out);//如果只是fstream f("testf.txt");不會創建文件。要如此行代碼或者fstream f("testf.txt",ios::out)
ofstream f("testf.txt",ios::out);//ofstream默認就是out.所以ofstream f("testf.txt")與本行代碼一個效果
f<<"helloworld"<<endl;
system("pause");
}
一般情況下,ifstream和ofstream流類的析構函數就可以自動關閉已打開的文件,但若需要使用同一個流對象打開的文件,則需要首先用close()函數關閉當前文件。
3.文件的順序讀寫
3.1讀寫文本文件
對文本文件進行讀寫時,先要以某種方式打開文件,然後使用運算符“<<”和">>"進行操作,同時必須將運算符"<<"和">>"前的cin和cout與文件關聯的流代替。
下面程序先向文件test.txt中寫入三行數據,再將該文件打開,並將寫入的數據依次輸出到屏幕。
[cpp]
#include<fstream.h>
#include<iostream>
using namespace std;
int main()
{
ofstream fout("test.txt");
if (!fout)//創建失敗
{
cout<<"不能打開輸出文件。"<<endl;
return 1;
}
fout<<"HelloWorld"<<endl;//注意這HelloWorld中間沒有空格
fout<<10<<endl;
fout<<hex<<10<<endl;
fout.close();
ifstream fin("test.txt");
if (!fin)
{
cout<<"不能打開輸入文件"<<endl;
return 1;
}
char c[20];
int i;
char ch;
fin>>c;
fin>>i;
fin>>ch;
cout<<c<<endl;
cout<<i<<endl;
cout<<ch<<endl;
fin.close();
system("pause");
return 0;
}
例:下面程序實現對一個文本文件的多種操作,包括求文件長度、統計單詞長度等功能。
[cpp]
#include<iostream>
#include<fstream>
using namespace std;
void open(char str[])//打開文件函數,把文件內容傳給str;
{
int i=0;
ifstream f;
f.open("test.txt",ios::in);//ifstream默認就是ios::in,此處可以省略
if (!f)
{
cout<<"not open"<<endl;
return ;
}
while (f)
{
f.get(str[i]);//一個一個取字符,包括空格。可參見myblog:cin\cin.get()\cin.getline()\getline()\gets()\getchar()用法集錦
i++;
}
str[i]='\0';//在讀到的字符串最末加上'\0'。
f.close();
cout<<str<<endl;
}
int len(char str[])//求字符串長度函數
{
int temp=0;
for(int i=0;'\0'!=str[i];i++)
temp++;
return temp;
}
int index(char a[],char b[])//找子串函數,返回子串首次出現的位置。
{
int i,j,temp;
for(i=0;i<len(a)-len(b);i++)
{
temp=i;
j=0;
while(j<=len(b)&&a[temp]==b[j])//對i位置及後面的字符,逐個字符判斷是否字串
{
temp++;
j++;
}
if(j==len(b))//找到
return i;
}
return -1;
}
int count(char str[])//統計單詞個數
{
int i,c=0;
for(i=0;i<len(str);i++)
if(' '==str[i])
c++;
return c+1;//以單詞之間空格個數+1表示單詞個數。比如3個單詞間有2個空格。
}
void output(char a[])//輸出當前文本
{
for(int i=0;i<len(a);i++)
cout<<a[i];
cout<<endl;
}
void output(char a[],int)//輸出第一個單詞
{
for(int i=0;i<len(a);i++)
if(' '==a[i])
break;
else
cout<<a[i];
cout<<endl;
}
int main()
{
char m[100];
char n[]="ok";
open(m);
cout<<"字符串長度:"<<len(m)-1<<endl;
cout<<"目標單詞首次出現的位置:"<<index(m,n)<<endl;
cout<<"單詞數:"<<count(m)<<endl;
cout<<"當前文本是:"<<endl;
output(m);//輸出當前文本
cout<<"第一個單詞是:";
output(m,1);//輸出第一個單詞
system("pause");
return 0;
}
3.2讀寫二進制文件
二進制文件是一種不能用普通的字處理軟件進行編輯、占空間較小的文件。二進制文件與文本文件的區別有以下幾點:
1)文本文件是字符流,二進制文件是字節流。
2)文本文件在輸入時,將回車和換行兩個字符轉換為字符"\n",輸出是將字符"\n"轉換為回車和換行兩個字符,二進制文件不做這種轉換。
3)文本文件遇到文件結束符時,用get()函數返回一個文件結束標志EOF,該標志的值為-1。二進制文件用成員函數eof()判斷文件是否結束。其中,eof()函數的原型為:int eof();
4)當文件達到末尾時,返回一個非零值;未達到末尾時返回零值。當從鍵盤輸入字符時,結束符為<Ctrl>+z組合鍵。
任何文件,都能以文本方式或者二進制方式打開。對以二進制方式打開的文件,有兩種方式進行讀寫操作:一種是使用函數get()和put(),另一種是使用函數read()和write()。
3.2.1使用get()和put()讀寫二進制文件
get()函數是輸入流類istream中定義的成員函數,作用是從與流對象連接的文件中讀出數據,get()實現的功能是從流中每讀出一個字節或一個字符放入引用&ch中,返回一個流輸入對象值。在c++中,get()函數原型為:
istream &get(unsigned char &ch);
put()函數是輸出流類ostream中定義的成員函數,作用是向與流對象連接的文件中寫入數據,put()實現的功能是每次將一個字節或一個字符寫入流中,同時返回一個流輸入對象值。在c++中,put()函數的原型為:
istream &put(char ch);
get()函數和put()函數都只能對單個字符或者單個字節進行操作,如果需要實現多字符的操作,可通過循環語句來實現。
例如,下面程序用函數get()和put()讀寫二進制文件。該程序定義一個命令,在DOS下調用該命令可以實現將文件1的內容拷貝到文件2中,相當於DOS命令中的copy命令。
[cpp]
#include<fstream>
#include<iostream>
using namespace std;
int main(int arg,char *argv[])
{
char ch;
if(arg!=3)
{
cout<<"命令行輸入錯誤!"<<endl;
return 1;
}
ifstream fin(argv[1]);//定義輸入流對象,打開第二個參數中的文件
if(!fin)
{
cout<<"不能打開源文件"<<endl;
return 1;
}
ofstream fout(argv[2]);
if(!fout)
{
cout<<"不能打開目標文件"<<endl;
return 1;
}
while(fin)//循環寫入
{
fin.get(ch);//從源文件中讀出字符
fout.put(ch);//寫入到目標文件
}
fin.close();
fout.close();
system("pause");
return 0;
}
在DOS界面下輸入:XX.exe test1.txt test2.txt就能找test1.txt中的內容復制到test2.txt中。前提是test1.txt文件存在,test2.txt如果不存在會自動創建。
3.2.2使用函數read()和write()讀寫二進制文件
除了上述內容中講解的使用函數get()和put()讀寫二進制文件外,c++還支持使用函數read()和write()讀寫二進制文件。
read()函數是輸入流類istream中定義的成員函數,其最常用的原型為:
istream &read(unsigned char *buf,int num);
該函數的作用是從相應的流中讀出num個字節或字符的數據,把它們放入指針所指向的緩沖區中。第一個參數buf是一個指向讀入數據存放空間的指針,它是讀入數據的起始地址;第二個參數num是一個整數值,該值說明要讀入數據的字節或字符數。該函數的調用格式為:
read(緩沖區首地址,讀入的字節數);
需要注意的是,“緩沖區首地址”的數據類型為“ unsigned char* ”,當輸入其他類型數據時,必須進行類型轉換。
write()函數是輸出流類ostream中定義的成員函數,其最常用的原型為:
ostream &write(const unsigned char *buf,int num);
該函數的作用是從buf所指向的緩沖區把num個字節的數據寫到相應的流中。其參數的含義、調用及注意事項與read()相同。
例如:下面程序用函數read()和write()讀寫二進制文件,實現向文件test.txt寫入一個雙精度數據類型的數值和一個字符串,並將該文件中的內容讀取出顯示到屏幕中。
[cpp]
#include<fstream>
#include<string.h>
#include<iostream>
using namespace std;
int main()
{
ofstream outf("test.txt");
if (!outf)
{
cout<<"不能打開輸出文件"<<endl;
return 1;
}
double n=123.4;
char str[]="向文件寫入雙精度數和字符串";//13個中文,每個中文占2字節。相當於26個字節,也相當於26個char。
outf.write((char *)&n,sizeof(double));
outf.write(str,strlen(str));
outf.close();
ifstream inf("test.txt");
if (!inf)
{
cout<<"不能打開輸入文件!"<<endl;
return 1;
}
inf.read((char *)&n,sizeof(double));//讀取文件中的內容
inf.read(str,26);
cout<<n<<" "<<str<<endl;
inf.close();
system("pause");
return 0;
}
文件裡顯示的內容如下:
3.3 文件的隨機讀寫
隨機讀寫是通過使用輸入或輸出流中與隨機移動文件指針相關的成員函數,通過隨機移動文件指針而達到隨機訪問的。移動文件指針的成員函數主要有 seekg()和seekp(),它們的常用原型為:
istream &seekg(streamoff offset,seek_dir origin);
osream &seekp(streamoff offset,seek_dir origin);
其中,參數origin表示文件指針的起始位置,offset表示相對於這個起始位置的位移量。seek_dir是系統定義的枚舉名,origin是枚舉變量。origin的取值有以下三種情況。
ios::beg 從文件頭開始,把文件指針移動由offset指定的距離。
ios::cur 從文件當前位置開始,把文件指針移動offset指定的距離。
ios::end 從文件尾開始,把文件指針移動由offset指定的距離。
顯然,當origin的值為ios::beg時,offset為正;當origin的值為ios::end時,offset的值為負;當origin的值為ios::cur時,offset的值可正可負。offset的值 為正數時從前向後移動文件指針,為負數時從後向前移動文件指針。位移量offset的類型是streamoff,該類型為一個long型數據,它在頭文件iostream.h中定義如下:
typedef streamoff long;
此外,移動文件指針的成員函數seekg()和seekp()使用范圍和功能為:
函數seekg()用於輸入文件,將文件的讀指針從origin說明的位置移動offset個字節。
函數seekp()用於輸出文件,將文件的寫指針從origin說明的位置移動offset個字節。
進行文件隨機讀寫時,可以用下列函數確定文件當前指針的位置:
streampos tellg();
streampos tellp();
其中,streampos是在頭文件iostream.h中定義的類型;是long型的;函數tellg()用於輸入文件;函數tellp()用於輸出文件。
[cpp]
#include<fstream.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
int main(int argc,char *argv[])
{
char ch;
if(3!=argc)
{
cout<<"命令行輸入錯誤"<<endl;
return 1;
}
ifstream fin(argv[1]);
if (!fin)
{
cout<<"不能打開輸入文件"<<endl;
return 1;
}
fin.seekg(atoi(argv[2]),ios::beg);//從源文件的頭開始讀取參數3指定的距離
while(!fin.eof())
{
fin.get(ch);
cout<<ch;
}
fin.close();
system("pause");
return 0;
}
用dos打開程序後,輸入xxx.ext test.txt 5。表示從text.txt的第3個位置開始讀取數據。
綜合例子:讀寫不同操作系統中文件,首先創建文件,寫入數據後讀取並顯示在屏幕上。
[cpp]
#include<iostream>
#include<fstream>
#ifdef WIN32
#define TEST_FILE "test.txt"
#else
#define TEST_FILE "/tmp/test.txt" //如果win32路徑為"c:\\tmp\\test.txt"
#endif
using namespace std;
int test()
{
{
fstream sfs("test.txt",ios_base::out);//文件是寫入方式,會覆蓋原來的內容
char buf[]="1234567890";
sfs.write(buf,sizeof(buf));
sfs.close();
}
{
int len;
char* buf;
fstream sfs("test.txt");
sfs.seekg(0,ios::end);//定位到文件末尾
len =sfs.tellg();//返回地址,相當於獲取文件的長度
sfs.seekg(0,ios::beg);//獲取完之後,將指針提到文件前面
buf = new char[len];
sfs.read(buf,len);//讀取文件內容到buf
cout<<buf<<endl;
delete []buf;
sfs.close();
}
}
int main()
{
test();
system("pause");
return 0;
}
例:模擬一個郵件發送端。在DOS命令行下輸入XXX.exe mail.txt。相應的信息會寫入mail.txt這個文本中。
[cpp]
#include<iostream>
#include<cstdlib>
#include<fstream>
#include<string>
using namespace std;
int main(int argc,char *argv[])
{
assert(argc>1);
ofstream file;
file.open(argv[1]);
if (!file)
{
cout<<"打開文件失敗"<<endl;
system("pause");
return 0;
}
int count=0;
file.write((char*)&count,sizeof(int));
string str;
cout<<"——請輸入收件人郵箱,以-1結束——"<<endl;
while(cin>>str && str!="-1")
{
file<<str<<' ';
count++;
}
long pos = file.tellp();//獲取當前的輸出位置
file.seekp(ios_base::beg);//重定位到文件開頭
file.write((char*)&count,sizeof(int));//寫入收信人信息數目
cout<<"——請輸入發件人郵箱——"<<endl;
file.seekp(pos);//重定位
if (cin>>str)
{
file<<str<<' ';
}
cout<<"——請輸入主題——"<<endl;
if(cin>>str)
{
file<<str<<' ';
}
cout<<"——郵件內容以-1結束——"<<endl;
while(cin>>str && str!="-1")
{
file<<str<<' ';
}
file.close();
system("pause");
return 0;
}
例:模擬一個郵件讀取端。讀取郵件顯示的屏幕。
[cpp]
#include<cstdlib>
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main(int argc,char* argv[])
{
assert( argc > 1);
ifstream file;
file.open(argv[1]);
if (!file)
{
cout<<"打開文件失敗"<<endl;
system("pause");
return 0;
}
string str;
int count = 0;
file.read((char*)&count,sizeof(int));//讀取收件人信箱個數
cout<<"——收件人信箱——"<<endl;
for( int i=0;i<count;i++)
{
file>>str;
cout<<str<<endl;
}
cout<<"——發件人信箱——"<<endl;
file>>str;
cout<<str<<endl;
cout<<"——主題——"<<endl;
file>>str;
cout<<str<<endl;
cout<<"——內容——"<<endl;
while (file>>str)
{
cout<<str<<endl;
}
file.close();
system("pause");
return 0;
}