二進位檔案操作(read、write)

在寫入或讀取檔案時,也可以用read()與write()函式以區塊的方式寫入,這兩個函式通常用二進位模式來操作檔案,函式型態如下,num是寫入資料的位元組數目:
istream &read(char buf, streamsize num);
ostream &write(const char
buf, streamsize num);

下面程式將陣列資料寫入檔案,然後再將資料讀出,.dat檔案格式是我們隨意寫的格式,有沒有寫或名稱為何都不影響結果:

#include <fstream> 
using namespace std;

int main() {
    ofstream fout("test.dat", ios::out | ios::binary);
    if (!fout) {
        return 1;
    }

    int arr[3] = {1,2,3};
    fout.write((char*)arr, sizeof(arr));
    fout.close();

    ifstream fin("test.dat", ios::in | ios::binary);
    if (!fin) {
        return 1;
    }

    int arr2[3] = {0,0,0};
    fin.read((char*)arr2, sizeof(arr2));
    fin.close();

    return 0;
}

檔案操作(ifstream、fstream、getline)

C++與檔案處理相關的有ifstream、ofstream及fstream這三個串流,使用時必須先函入fstream標頭,ifstream物件處理檔案輸入,ofstream物件處理檔案輸出,fstream物件處理檔案輸入輸出,建立串流物件之後,可以使用open()函式來連結串流,以下為讀入一個txt檔,並將檔案內容寫到另一個txt檔上。

#include<iostream>
#include<fstream>
using namespace std; 

int main(){ 
    ifstream fin;
    ofstream fout;
    fin.open("src.txt");     
    fout.open("dst.txt");   

    if(!fin){ 
        return 1; 
    } 

    char ch; 
    //用!fin.eof()來判斷,最後會多讀一次
    while(fin.peek()!=EOF) {   
        fin.get(ch); 
        fout.put(ch); 
    } 

    fin.close(); 
    fout.close();
    return 0; 
}

我們也可以一次讀取一行,下面程式從檔案一次讀取一行,接著再將檔案一行一行輸出到txt檔上。

#include <string>
#include <vector>
#include <fstream>
using namespace std;

int main(){
    ifstream in("test.txt");
    string inputStr;
    vector inputContent;
    while(getline(in, inputStr)){
        inputContent.push_back(inputStr);
    }
    in.close();

    ofstream out("output.txt");
    for(int i=0; i < inputContent.size(); i++){
        out << inputContent[i] << endl;
    }
    out.close();

    return 0;
}

重載<<、>>運算子

重載<<、>>運算子,可以讓<<、>>擁有輸出、輸入自定物件資料的能力,其方法與重載其它運算子類似,重載<<運算子時,第一個參數是ostream型態的參數,第二個參數是要輸出的物件,實作時指定輸出的資訊,重載>>運算子的方式也類似,第一個參數是istream型態的參數,第二個參數是要輸入的物件,實作時指定輸入的資訊。

通常重載<<、>>運算子時,會使用friend函式,這是因為它不是一個成員函式,使用此方式可以讀取private的成員變數,假使不使用friend函式重載也可,但是只能操作public的成員變數。

#include <iostream> 
using namespace std;

class Point {
private:
    int m_x;
    int m_y;
public:
    Point();
    Point(int x, int y);

    friend ostream &operator<<(ostream &s, Point p);
    friend istream &operator>>(istream &s, Point &p);
};
Point::Point(){
    m_x = 0;
    m_y = 0;
}
Point::Point(int x, int y){
    m_x = x;
    m_y = y;
}

ostream &operator<<(ostream &s, Point p) {
    s << "(" << p.m_x << ", " << p.m_y << ")";
    return s;
}
istream &operator>>(istream &s, Point &p) {
    cout << "輸入點座標: ";
    s >> p.m_x >> p.m_y;
    return s;
}

int main() {
    Point p1(1, 3);
    cout << p1 << endl;

    Point p2;
    cin >> p2;
    cout << p2 << endl;

    system("PAUSE");
    return 0;
}

格式控制器、格式旗標(cout)

cout有預設的輸出格式,也可以自行指定格式,像endl就是格式控制器的一種,它會輸出new line字元至串流中,格式控制器只會影響目前正在處理的串流,串流處理結束後即回復成預設的格式,格式控制器也可以指定參數,如果要使用具有參數的格式控制器,必須含入iomanip這個標頭檔案,下面為簡單的使用。

#include <iostream> 
#include <iomanip>
using namespace std;

int main() {
    cout << oct << 30 << endl;    //8進位顯示 
    cout << hex << 30 << endl;    //16進位顯示 
    cout << setw(2) << 5 << endl; //寬度為2
    system("PAUSE");
    return 0;
}

格式控制器可以改變目前的串流格式,如果想要讓所有的串流都維持特定格式,可以使用格式化旗標,使用setf()與unsetf()來設定與取消格式化旗標,下面為簡單的示範。

#include <iostream> 
using namespace std;

int main() {
    cout.unsetf(ios::dec);              
    cout.setf(ios::hex); 
    cout << 120 << endl;
    cout << 50 << endl;

    cout.setf(ios::dec);
    cout.setf(ios::scientific);
    cout << 50.25 << endl;

    system("PAUSE");
    return 0;
}

friend函式重載運算子

使用類別成員來重載二元運算子時,可以具有以下的功能:
Point p1(10, 10);
Point p2;
p2 = p1 + 10;

但是使用類別成員重載,運算子的左邊一定要是原物件,無法具有以下的功能:
Point p1(10, 10);
Point p2;
p2 = 10 + p1;

這時可以使用friend函式來重載運算子,使用friend函式重載二元運算子,指定兩個參數型態,分別表式運算子左右邊的運算元型態,藉由兩個函式來解決以上的問題。

<

pre>class Point{
public:
int m_x;
int m_y;

Point();
Point(int x, int y);
friend Point operator+(const Point&, int); 
friend Point operator+(int, const Point&);  

};
Point::Point(){
m_x = 0;
m_y = 0;
}
Point::Point(int x, int y){
m_x = x;
m_y = y;
}
Point operator+(const Point &p, int i) {
Point tmp(p.m_x + i, p.m_y + i);
return tmp;
}
Point operator+(int i, const Point &p) {
Point tmp(i + p.m_x, i + p.m_y);
return tmp;
}

int main(){
Point point1(5, 3);
Point point2 = point1 + 2;
return 0;
}

類別範本(Class template)

C++除了函示範本(Function template)還有類別範本(Class template),假使我們自訂一個陣列類別,除了儲存int資料型態,還想要此類別儲存double、char等其它資料型態,這時可以宣告類別為類別範本,讓我們不必撰寫功能雷同的程式碼。

以下為類別範本(Class template)的宣告方式:

template <class 型態名稱> 
class 類別名稱{ 
    // ........ 
};

以下為類別範本的使用方式,MyArray是個自訂的類別。

template<class T> 
class MyArray { 
private:
    int m_length;
    T *m_data; 
public: 
    MyArray(int);  
    ~MyArray();
    T get(int); 
    void set(int, T);
};

template<class T> 
MyArray<T>::MyArray(int len) {
    m_length = len;
    m_data = new T[m_length];
}

template<class T> 
T MyArray<T>::get(int i) {
    return m_data[i]; 
}

template<class T> 
void MyArray<T>::set(int i, T value) {
    m_data[i] = value;
}

template<class T> 
MyArray<T>::~MyArray() {
    delete [] m_data;
}

int main() {
    MyArray<int>iArray(5);
    MyArray<double>dArray(5);

    iArray.set(2,3);
    dArray.set(2,0.7);
    int temp1 = iArray.get(2);     //temp1=3
    double temp2 = dArray.get(2);  //temp2=0.7

    return 0;
}

命名空間(namespace)

隨著程式內容越來越大,可能發生類別同名的問題,例如在程式某個地方定義Point類別代表空間的點,也許在其他地方,Point類別有不同的意義,或者是函式、變數發生同名的情況,當這種情況發生時,其中一個定義可能被另一個給覆寫掉了。

C++提供名稱空間的概念,它就像是一個名稱管理容器,像假如定義了2d與3d的名稱空間,雖然它們之下都有一個Point類別,但由於屬於不同的名稱空間,所以這兩個名稱並不會衝突。

C++使用namespace關鍵字來定義一個名稱空間,名稱空間中的成員,可以直接使用當中所宣告的識別字,但如果要在名稱空間之外使用這些成員,則要指定名稱空間,以下為名稱空間的使用。

namespace 2d { 
    class Point{
    public: 
        int m_x;
        int m_y; 
        Point(){
            m_x = 0; 
            m_y = 0;
        } 
        Point(int x, int y){
            m_x = x; 
            m_y = y;
        }
    };
};

int main(){
    2d::Point myPoint(3,7);
    int a = myPoint.m_x;     //a=3
    return 0;
}

可以使用using關鍵字指明所要使用的名稱空間或其下的成員,則被指定的名稱空間或包含的成員就可以直接被使用,有以下兩種用法:

using 名稱空間::成員 
using namespace 名稱空間

C++的標準函式庫皆定義在std名稱空間中,所以想使用的話,必須在檔案的最前頭加上using namespace std;,或者是在函式前加上std::。