北京大學MOOC C++學習筆記(六)輸入輸出和檔案操作
輸入輸出相關的類
與輸入輸出流操作相關的類:
- istream是用於輸入的流類,cin就是該類的物件。
- ostream是用於輸出的流類,cout就是該類的物件。
- ifstream是用於從檔案讀取資料的類。
- ofstream是用於向檔案寫入資料的類。
- iostream是既能用於輸入,又能用於輸出的類。
- fstream 是既能從檔案讀取資料,又能向檔案寫入資料的類。
標準流物件:
- 輸入流物件: cin 與標準輸入裝置相連
- 輸出流物件:cout 與標準輸出裝置相連 cerr 與標準錯誤輸出裝置相連 clog 與標準錯誤輸出裝置相連
預設情況下,cerr << "Hello,world" << endl; clog << "Hello,world" << endl; cout << “Hello,world” << endl; 一樣
- cin對應於標準輸入流,用於從鍵盤讀取資料,也可以被重定向為從檔案中讀取資料。
- cout對應於標準輸出流,用於向螢幕輸出資料,也可以被重定向為向檔案寫入資料。
- cerr對應於標準錯誤輸出流,用於向螢幕輸出出錯資訊,
- clog對應於標準錯誤輸出流,用於向螢幕輸出出錯資訊,
- cerr和clog的區別在於cerr不使用緩衝區,直接向顯示器輸出資訊;而輸出到clog中的資訊先會被存放在緩衝區,緩衝區滿或者 重新整理時才輸出到螢幕。
判斷輸入流結束:
可以用如下方法判輸入流結束:
int x;
while( cin>>x ){
…..
} }
return 0;
其實現的原理是:
istream &operator >>(int & a) { ……. return *this ; }
- 如果是從檔案輸入,比如前面有freopen(“some.txt”,”r”,stdin);那麼,讀到檔案尾部,輸入流就算結束。
- 如果從鍵盤輸入,則在單獨一行輸入 Ctrl+Z 代表輸入流結束
istream類的成員函式
istream & getline(char * buf, int bufSize); 從輸入流中讀取bufSize-1個字元到緩衝區buf,或讀到碰到‘\n’為止(哪個先到算哪個)。 istream & getline(char * buf, int bufSize,char delim); 從輸入流中讀取bufSize-1個字元到緩衝區buf,或讀到碰到delim字元為止(哪個先到算哪個)。
兩個函式都會自動在buf中讀入資料的結尾新增\0’。,‘\n’或delim都不會被讀入buf,但會被從輸入流中取走。如果輸入流中 ‘\n’或delim之前的字元個數達到或超過了bufSize個,就導致讀入出錯,其結果就是:雖然本次讀入已經完成,但是之後的讀入就 都會失敗了。 可以用 if(!cin.getline(…)) 判斷輸入是否結束
bool eof(); 判斷輸入流是否結束 int peek(); 返回下一個字元,但不從流中去掉. istream & putback(char c); 將字元ch放回輸入流 istream & ignore( int nCount = 1, int delim = EOF );從流中刪掉最多nCount個字元,遇到EOF時結束。
輸出重定向
#include <iostream>
using namespace std;
int main() {
int x,y;
cin >> x >> y;
freopen("test.txt","w",stdout); //將標準輸出重定向到 test.txt檔案
if( y == 0 ) //除數為0則在螢幕上輸出錯誤資訊
cerr << "error." << endl;
else
cout << x /y ; //輸出結果到test.txt
return 0;
}
輸入重定向
#include <iostream >
using namespace std;
int main() {
double f; int n;
freopen(“t.txt”,“r”,stdin); //cin被改為從 t.txt中讀取資料
cin >> f >> n;
cout << f << "," <<n << endl;
return 0;
}
流操縱運算元
- 整數流的基數:流操縱運算元dec,oct,hex,setbase
- 浮點數的精度(precision,setprecision)
- 設定域寬(setw,width)
- 使用者自定義的流操縱運算元
使用流操縱運算元需要 #include <iomanip>
- 整數流的基數:流操縱運算元
int n = 10; cout << n << endl; cout << hex << n << “\n”<< dec << n << “\n”<< oct << n << endl; 輸出結果: 10 a 10 12
- precision, setprecision
precision是成員函式,其呼叫方式為: cout.precision(5); setprecision 是流操作運算元,其呼叫方式為: cout << setprecision(5); // 可以連續輸出 它們的功能相同。 指定輸出浮點數的有效位數(非定點方式輸出時) 指定輸出浮點數的小數點後的有效位數(定點方式輸出時) 定點方式:小數點必須出現在個位數後面
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double x = 1234567.89,y = 12.34567;
int n = 1234567;
int m = 12;
cout << setprecision(6) << x << endl<< y << endl << n << endl << m;
}
浮點數輸出最多6位有效數字
輸出: 1.23457e+006 12.3457 1234567 12
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double x = 1234567.89,y = 12.34567;
int n = 1234567;
int m = 12;
cout << setiosflags(ios::fixed) <<setprecision(6) << x << endl<< y << endl << n << endl << m;
}
以小數點位置固定的方式輸出
輸出: 1234567.890000 12.345670 1234567 12
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double x = 1234567.89;
cout << setiosflags(ios::fixed) <<setprecision(6) << x << endl <<
resetiosflags(ios::fixed) << x ; //取消以小數點位置固定的方式輸出
}
輸出: 1234567.890000 1.23457e+006
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
int n = 141;
//1) 出 分別以十六進位制、十進位制、八進位制先後輸出 n
cout << "1) " << hex << n << " " << dec << n << " " << oct << n << endl;
double x = 1234567.89,y = 12.34567;
//2) 保留5 位有效數字
cout << "2) " << setprecision(5) << x << " " << y << " " << endl;
//3) 保留小數點後面5 位
cout << "3) " << fixed << setprecision(5) << x << " " << y << endl ;
//4) 科學計數法輸出,且保留小數點後面5 位
cout << "4) " << scientific << setprecision(5) <<x << " " << y << endl ;
//5) 非負數要顯示正號,輸出寬度為12 字元,寬度不足則用'*' 填補
cout << "5) " << showpos << fixed << setw(12) << setfill('*') << 12.1
<< endl;
//6) 非負數不顯示正號,輸出寬度為12 字元,寬度不足則右邊用填充字元填充
cout << "6) " << noshowpos << setw(12) << left << 12.1 << endl;
//7) 輸出寬度為12 字元,寬度不足則左邊用填充字元填充
cout << "7) " << setw(12) << right << 12.1 << endl;
//8) 寬度不足時,負號和數值分列左右,中間用填充字元填充
cout << "8) " << setw(12) << internal << -12.1 << endl;
cout << "9) " << 12.1 << endl;
return 0;
}
1) 8d 141 215 2) 1.2346e+006 12.346 3) 1234567.89000 12.34567 4) 1.23457e+006 1.23457e+001
5) ***+12.10000 6) 12.10000**** 7) ****12.10000 8) -***12.10000 9) 12.10000
檔案讀寫
可以將順序檔案看作一個有限字元構成的順序字元流,然後像對cin, cout 一樣的讀寫。
建立檔案
• #include <fstream> // 包含標頭檔案 • ofstream outFile(“clients.dat”, ios::out|ios::binary); //建立檔案 – clients.dat” 要建立的檔案的名字 – ios::out 檔案開啟方式 • ios:out 輸出到檔案, 刪除原有內容 • ios::app 輸出到檔案, 保留原有內容,總是在尾部新增 – ios::binary 以二進位制檔案格式開啟檔案
1 也可以先建立ofstream物件,再用 open函式開啟 ofstream fout; fout.open("test.out",ios::out|ios::binary); 2 判斷開啟是否成功: if(!fout){ cout << “File open error!”<<endl; } 3 檔名可以給出絕對路徑,也可以給相對路徑。沒有交代路徑資訊,就是在當前資料夾下找檔案
檔案的讀寫指標
- 對於輸入檔案,有一個讀指標;
- 對於輸出檔案,有一個寫指標;
- 對於輸入輸出檔案,有一個讀寫指標;
- 標識檔案操作的當前位置, 該指標在哪裡,讀寫操作就在哪裡進行。
ofstream fout("a1.out",ios::app); //以新增方式開啟 long location = fout.tellp(); //取得寫指標的位置 location = 10; fout.seekp(location); // 將寫指標移動到第10個位元組處 fout.seekp(location,ios::beg); //從頭數location fout.seekp(location,ios::cur); //從當前位置數location fout.seekp(location,ios::end); //從尾部數location
//location 可以為負值
ifstream fin(“a1.in”,ios::ate); //開啟檔案,定位檔案指標到檔案尾 long location = fin.tellg(); //取得讀指標的位置 location = 10L; fin.seekg(location); // 將讀指標移動到第10個位元組處 fin.seekg(location,ios::beg); //從頭數location fin.seekg(location,ios::cur); //從當前位置數location fin.seekg(location,ios::end); //從尾部數location // location 可以為負值
顯式關閉檔案
ifstream fin(“test.dat”,ios::in); fin.close(); ofstream fout(“test.dat”,ios::out); fout.close();
字元檔案讀寫
因為檔案流也是流,所以流的成員函式和流操作運算元也同樣適用於檔案流。
寫一個程式,將檔案 in.txt 裡面的整數排序後,輸出到out.txt 例如,若in.txt 的內容為: 1 234 9 45 6 879 則執行本程式後,生成的out.txt的內容為: 1 6 9 45 234 879
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v;
ifstream srcFile("in.txt",ios::in);
ofstream destFile("out.txt",ios::out);
int x;
while( srcFile >> x )
v.push_back(x);
sort(v.begin(),v.end());
for( int i = 0;i < v.size();i ++ )
destFile << v[i] << " ";
destFile.close();
srcFile.close();
return 0;
}
二進位制檔案讀寫
二進位制讀檔案:
ifstream 和 fstream的成員函式: istream& read (char* s, long n); 將檔案讀指標指向的地方的n個位元組內容,讀入到記憶體地址s,然後將檔案讀指標向後移動n位元組 (以ios::in方式開啟檔案時,檔案讀指標開始指向檔案開頭) 。
二進位制寫檔案:
ofstream 和 fstream的成員函式: istream& write (const char* s, long n); 將記憶體地址s處的n個位元組內容,寫入到檔案中寫指標指向的位置,然後將檔案寫指標向後移動n位元組(以ios::out方式開啟檔案時,檔案寫指標開始指向檔案開頭, 以ios::app方式開啟檔案時,檔案寫指標開始指向檔案尾部 ) 。
在檔案中寫入和讀取一個整數
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream fout("some.dat", ios::out | ios::binary);
int x=120;
fout.write( (const char *)(&x), sizeof(int) );
fout.close();
ifstream fin("some.dat",ios::in | ios::binary);
int y;
fin.read((char * ) & y,sizeof(int));
fin.close();
cout << y <<endl;
return 0;
}
從鍵盤輸入幾個學生的姓名的成績,並以二進位制檔案形式儲存
#include <iostream>
#include <fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main() {
Student s;
ofstream OutFile( "c:\\tmp\\students.dat",ios::out|ios::binary);
while( cin >> s.name >> s.score )
OutFile.write( (char * ) & s, sizeof( s) );
OutFile.close();
return 0;
}
將 students.dat 檔案的內容讀出並顯示
#include <iostream>
#include <fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main() {
Student s;
ifstream inFile("students.dat",ios::in | ios::binary );
if(!inFile) {
cout << "error" <<endl;
return 0;
}
while( inFile.read( (char* ) & s, sizeof(s) ) ) {
int readedBytes = inFile.gcount(); // 看剛才讀了多少位元組
cout << s.name << " " << s.score << endl;
}
inFile.close();
return 0;
}
將 students.dat 檔案的Jane的名字改成Mike
#include <iostream>
#include <fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main()
{
Student s;
fstream iofile( "c:\\tmp\\students.dat",ios::in|ios::out|ios::binary);
if( !iofile) {
cout << "error" ;
return 0;
}
iofile.seekp( 2 * sizeof(s),ios::beg); // 定位寫指標到第三個記錄
iofile.write("Mike",strlen("Mike")+1);
iofile.seekg(0,ios::beg); // 定位讀指標到開頭
while( iofile.read( (char* ) & s, sizeof(s)) )
cout << s.name << " " << s.score << endl;
iofile.close();
return 0;
}
檔案拷貝程式mycopy 示例
/*用法示例: mycopy src.dat dest.dat 即將 src.dat 拷貝到 dest.dat 如果 dest.dat 原來就有,則原來的檔案會被覆 蓋 */
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char * argv[])
{
if( argc != 3 ) {
cout << "File name missing!" << endl;
return 0;
}
ifstream inFile(argv[1],ios::binary|ios::in); // 開啟檔案用於讀
if( ! inFile ) {
cout << "Source file open error." << endl;
return 0;
}
ofstream outFile(argv[2],ios::binary|ios::out); // 開啟檔案用於寫
if( !outFile) {
cout << "New file open error." << endl;
inFile.close(); // 開啟的檔案一定要關閉
return 0;
}
char c;
while( inFile.get(c)) // 每次讀取一個字元
outFile.put(c); // 每次寫入一個字元
outFile.close();
inFile.close();
return 0;
}
二進位制檔案和文字檔案的區別
Linux,Unix下的換行符號:‘\n’ (ASCII碼: 0x0a) Windows 下的換行符號:‘\r\n’ (ASCII碼: 0x0d0a) endl 就是 '\n' Mac OS下的換行符號: ‘\r’ (ASCII碼:0x0d) 導致 Linux, Mac OS 文字檔案在Windows 記事本中開啟時不換行
Unix/Linux下開啟檔案,用不用 ios::binary 沒區別
Windows下開啟檔案,如果不用 ios::binary,則: 讀取檔案時,所有的 '\r\n’會被當做一個字元'\n'處理,即少讀了一個字 符'\r'。 寫入檔案時,寫入單獨的'\n'時,系統自動在前面加一個'\r',即多寫了一 個'\r'