1. 程式人生 > >北京大學MOOC C++學習筆記(六)輸入輸出和檔案操作

北京大學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'