1. 程式人生 > >c++筆記08---I/O 流,格式化 I/O,非格式化 I/O,隨機 I/O,二進位制 I/O

c++筆記08---I/O 流,格式化 I/O,非格式化 I/O,隨機 I/O,二進位制 I/O

1.    輸入輸出 I/O 流        
    C: fopen/fclose/fread/fwrite/fprintf/fscanf/fseek/ftell/fput/fget...
    C++: 對基本的 I/O 操作做了類的封裝,其功能沒有任何差別,用法也相似;
    
2.    格式化 I/O 流:<< / >>
        #include <iostream>
        #include <fstream>                        // 開啟檔案
        using namespace std;
        int main() {
            // 寫檔案:
            ofstream ofs("hello.txt");            // 建立檔案,類似於 fopen 用 w 模式,只讀且清空
            // ofs 在 ofstream 的建構函式裡面被構造,開啟成功返回 ture,失敗返回 false,是 bool 型;
            if(! ofs) {
                perror("error");                    // perror 打印出錯資訊;
                return -1;
            }
            ofs << "hello word!"  << endl;        // hello word! 被輸入檔案 hello.txt 裡;
            ofs.close();

            ofs.open("hello.txt", ios::app);    // 開啟檔案,追加內容,類似於 fopen 的 a 模式
            if(! ofs) {
                perror("error");
                return -1;
            }
            ofs << "another\n";
            ofs.close();
            // 讀檔案:
            ifstream ifs("hello.txt");
            if(! ifs) {
                perror("error");
                return -1;
            }
            steing s1, s2;
            ifs >> s1 >> s2;                        // 讀入上面輸入的兩個字串;以空白字元作為分隔(空格、換行、製表);
            return 0;
        }
        
        ios::app    以追加的方式開啟檔案;
        ios::ate    檔案開啟後定位到檔案尾;
        ios::binary    以二進位制方式開啟檔案;預設是文字方式;
        ios::in        以輸入方式開啟;
        ios::out        以輸出方式開啟;
        ios::nocreate    不建立檔案,所以檔案不存在時,開啟失敗;
        ios::noreplace    不覆蓋檔案,所以開啟檔案時,如果檔案存在則失敗;
        ios::trunc        如果檔案存在,把檔案長度設為 0;
        ios::beg            移動到檔案頭;
        ios::cure            移動到檔案當前位置;
        ios::end            移動到檔案尾;
        可以用 或 把以上檔案屬性連線起來:
        
    
3.    非格式化 I/O 流:put/get
        #include <iostream>
        #include <fstream>
        using namespace std;
        int mian() {
            // 輸入
            ofstream ofs("putget.txt");
            if(! ofs) {
                perror("error");
                return -1;
            }
            for(char c = ' '; c <= '~'; ++c)    // 把 ASIIC 表裡的字元從第一個到最後一個寫入;
            // 前++返回引用,後++返回副本,所以建議用前++,效率高;
                if(! ofs.put(c)) {
                    preeor("error");
                    return -1;
                }
            ofs.close();
            // 輸出
            ifstream ifs("putget.txt");
            if(! ifs) {
                perror("error");
                return -1;
            }
            char c;
            while((c = ifs.get()) != EOF)        // 因為 ASIIC 裡沒有 EOF,所以用 EOF 作判斷;
            // 無論是讀取失敗或者讀完檔案,都返回 EOF,所以下面加 if 語句判斷是否出錯;
                cout << c;
            if(ifs.error()) {                        // error 判斷讀取是否出錯
            // 相當於:if(! ifs.eof()) {            // eof 判斷是否到檔案尾;        
                perror("error");
                return -1;
            }
            ifs.close();
            return 0;
        }
        
        EOF:成員函式 eof();用來檢測是否到達檔案尾;
        如果到達檔案尾,返回非 0;否則返回 0;

4    隨機 I/O 流:seekp/seekg, tellp/tellg(檔案指標)
        #include <iostream>
        #include <fstream>
        using namespace std;
        int mian() {
            // 輸入
            fstream fs("seek.txt", ios::in | ios::out);    // 可讀可寫,相當於:w+
            if(! ofs) {
                perror("error");
                return -1;
            }
            fs << "0123456789";
            cout << fs.tellp() << endl;
            cout << fs.tellg() << endl;
            fs.seekp(-3, ios::cur);                // 當前位置往前挪 3 個位置
            fs << "xyz";                            // 0123456xyz(檔案預設操作只有覆蓋,沒有插入)
            fs.seekg(4, ios::beg);                // 從檔案頭開始挪;
            int i;
            fs >> i;
            cout << i << endl;
            cout << fs.tellg() << endl;
            cout << fs.tellp() << endl;
            fs.seekg(-6, ios::end);                // 從檔案尾開始
            fs << "abc";
            return 0;
        }
        這裡只打開一次檔案,可以不用 fs.close(),解構函式會自己析構;
        但如果向上面的例子,多次開啟同一個檔案,則必須手動關閉;
        
        seekg ()    設定讀位置;
        seekp ()    設定寫位置;
        tellg ()    返回流中 get 指標當前位置;
        tellp ()    返回流中 put 指標當前位置;
        
5.    二進位制 I/O 流:read/write

    read 讀取資料,成功返回讀取的位元組數;出錯返回 -1 並設定 error;
    如果在呼叫 read 之前已經到檔案尾,則返回 0;
    fread 和 read 區別:
    1)fread 是 c 語言的庫,而 read 是系統呼叫;
    2)read 每次讀取的資料是使用者要求的大小,fread 為了加快讀取速度,會讀取一個緩衝區大小,讀到的內容放入緩衝區;
    在 32 位機器下,一般是 4096 個位元組;
    
    write 寫入資料,成功返回寫入的位元組數;出錯返回 -1 並設定 error;

    實現檔案加解密:
    原理:運用異或實現加解密
    
        #include <iostream>
        #include <fstream>
        #include <stdexcept>
        #include <cstdlib>
        using namespace std;
        #define BUFSIZE (1024*10)                // 緩衝區
        int _xor(const char* src, const char* dst, unsigned char key) {    // 加解密
            ifstream ifs(src, ios::binary);        // 二進位制方式建立,window 有區別,linux 無區別
            if(! ifs) {
                perror("write error");
                return -1;
            }
            ofstream ofs(dst, ios::binary);
            if(! ofs) {
                perror("read error");
                return -1;
            }
            char* buf = NULL;
            try {
                buf = new char[BUFSIZE];            // 放入緩衝區,日後作比較用
            }
            catch (bad_alloc& ex) {
                cout << ex.what() << endl;
                return -1;
            }
            while(ifs.read(buf, BUFSIZE)) {
            // BUFSIZE 期望值,如果讀到的比期望位元組少返回 false,一樣返回 ture
                for(size_t i = 0; i < BUFSIZE; ++i)
                    buf[i] ^= key;
                ofs.write(buf, BUFSIZE);
                if(! ofs.write(uf, BUFSIZE)) {
                    perror("write error");
                    return -1;
                }
            }
            if(! ifs.eof()) {(char*)&dog
                perror("read error");
                return -1;
            }
            for(size_t i = 0; i < ifs.gcount(); ++i)
                buf[i] ^= key;
            if(! ofs.write(uf, ifs.gcount()) {
                    perror("write error");
                    return -1;
            }
        }
        int enc(const char* plain, const charI cipher) {
            srand(time(NULL));                            // 隨機因子
            unsigned char key = rand() % 256;            // 取出隨機數
            if(_xor(plain, cipher,key) == -1)
                return -1;
            cout << "祕匙 :" << (unsigned int)key << endl;
            return 0;
        }
        int dec(const char* cipher, const char* plain, unsigned char key) {
            return _xor(cipher,plain,key);
        }
        int main(int argc, char* argv[]) {
            if(argc < 3) {
                cerr << "用法:" << argv[0]
                    << "明文檔案/密文檔案" >> endl;
                cerr << "用法:" << argv[0]
                    << "密文檔案/明文檔案/祕匙" >> endl;
                return -1;
            }
            if(argc < 4)
                return enc(argv[1], argv[2]);
            else
                return dec(argv[1], argv[2], atoi[3])
            return 0;
        }

6.    格式控制 I/O 流
    方法一:通過流函式實現;
    方法二:流控制符;需要包含標頭檔案 #include <iomanip>
    #include <iomanip>    
    #include <cmath>
    cout << sqrt(2);                                        // 輸出 2 的平方根;預設輸出六位;
    cout.precision(10);                                    // 流函式實現輸出 10 位有效數字;對下面的所有語句都有作用;
    cout << sqrt(3);                                        // 輸出 10 位有效數字;
    cout << setprecision(5) << sqrt(2);                    // 改變精度,輸出 5 位有效數字;
    cout << cout.precision();                            // 返回當前精度;輸出:5
    cout << setprecision(2) << 1.25 << 1.26;            // 前者輸出:1.2,後者輸出:1.3
    cout << showbase << hex << 127;                        // 十六進位制
    cout << oct << 127;                                    // 八進位制
    cout << dec << 127;                                    // 十進位制
    cout << noshowbase << hex << 127 << dec;            // 不顯示 0x
    cout << setw(12) << 127;                                // 佔用12個字元,127靠右顯示,空格填充
    cout << setw(12) << 127 << 99;                        //          12799(前面 9 個空格)
    cout << setfill('&') << left << setw(12) << 127;    // 輸出:127&&&&&&&&&
    cout << setw(12) << showpos << internal << 4;        // 輸出:+          4
    cout << setw(12) << showpos << internal << -4;    // 輸出:-          4
    cout.precision(10);
    cout.setf(ios::scientific);                            // 科學計數法
    cout << sqrt(2);
    cout.setf(ios::fixed);                                // 普通
    cout << sqrt(2);
    cout << 12.00;                                        // 輸出:12
    cout << showpoint << 12.00;                            // 輸出:12.00000000(因為前面設定精度為 10 )
    cout << noshowpoint << 12.00;                        // 輸出:12
    bool b = 1;
    cout << boolalpha << b;                                // 輸出:true
    小結:
    cout.setw(4);                // 對其後所有行都有影響;
    cout << set(4) << b;        // 對其後一行有影響;
    
    舉例:
    假設 hello.txt 內容為:a    b    c
    #include <fstream>
    ifstream ifs("hello.txt");
    char c;
    while(ifs >> c)                // 用格式化 >> 讀取
        cout << c;                // 輸出:abc(中間的空格不見了)
    ifs.close();
    ------------解決辦法----------------
    #include <fstream>
    ifstream ifs("hello.txt");
    ifs.unsetf(ios::skipws);        // 取消空格、製表符、回車等符號標誌
    char c;
    while(ifs >> c)
        cout << c;                // 輸出:a        b        c
    ifs.clear();                    // 清除之前的流狀態,否則無法再次讀出;
    ifs.setf(ios::skipws);        // 恢復
    ifs.seekg(ios::beg);            // 指標移到檔案頭
    while(ifs >> c)
        cout << c;
    ifs.close();
    
7.    字串 I/O 流
    #include <sstream>
    #include <fstream>
    int i = 1234;
    double d = 26.45;
    string s = "tarena";
    ostringstream oss;
    oss << i << d << s;            // 自動把上面的 i d s 三個變數值裝入 oss,保存於記憶體中;
    string str = oss.str();
    cout << str;
    // 讀取字串
    str = "hello 1234";
    istringstream iss;
    iss.str(str);
    iss << s << i;                // 把 hello 放入 s,1234 放入 i
    cout << s << i;                // 輸出:hello 1234
    
    舉例:輸入輸出檔案對類型別的影響
    #include <fstream>
    class Dog {
        public:
            Dog(const string& name = "", int age = 0) : m_name(name), m_age(age) {}
            void print() { cout << "hello" << endl; }
        private:
            string m_name;
            int m_age;
    };
    int main() {
        ofstream ofs("dog.dat");
        Dog dog("bai", 25);
        ofs.write((char*)&dog, sizeof(dog));
        // 首先:write 要求傳入的是 T* 格式,所以這裡強制型別轉換為 char*
        // 其次:傳入的是 dog 地址,也就傳入了 m_name 的首地址,而 m_name 的內容沒有傳入;
        ofs.close();
        // 讀取檔案:
        ifstream ifs("dog.dat");
        Dog dog2;
        ifs.read((char*)&dog2, sizeof(dog2));    // 也讀取 m_name 首地址;
        dog2.print();
        ifs.close();
        return 0;
    }
    執行會提示吐核錯誤(兩次釋放記憶體);
    -----------------解決辦法-----------------------
    #include <fstream>
    #include <cstring>
    class Dog {
        public:
            Dog(const string& name = "", int age = 0) : m_age(age)
            { strcpy(m_name, name.c_str); }                // 利用 strcpy 函式賦值
            void print() { cout << "hello" << endl; }
        private:
            char m_name[128];                                // 首先把名字字串放到數組裡
            int m_age;
    };
    int main() {
        ofstream ofs("dog.dat");
        Dog dog("bai", 25);
        ofs.write((char*)&dog, sizeof(dog));
        ofs.close();
        
        ifstream ifs("dog.dat");
        Dog dog2;
        ifs.read((char*)&dog2, sizeof(dog2));
        dog2.print();
        ifs.close();
        return 0;
    }