程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> [C++]關於數據永久化的思考(不使用數據庫)

[C++]關於數據永久化的思考(不使用數據庫)

編輯:關於C++

關於數據永久化的思考(不使用數據庫)

==數據永久化==是一個程序很重要的特性。我們知道使用數據庫肯定可以實現數據永久化,但對於新手而言,比較艱難。本文討論的是,如何不使用數據庫來完成數據的保存。主要提供兩種方法,一種是使用純粹的文件讀寫,另一種使用json。首先,我們先來復習一下文件操作的基本信息。

C++文件操作

文件指存放在外部介質上的數據的集合。大家都知道操作系統是以文件為單位來對數據進行管理的。因此如果你要查找外部介質的數據,則先要按文件名找到指定文件,然後再從文件中讀取數據,如果要把數據存入外部介質中,如果沒有該文件,則先要建立文件,再向它輸入數據。由於文件的內容千變萬化,大小各不相同,為了統一處理,在C++中用文件流的形式來處理,文件流是以外存文件為輸入輸出對象的數據流。輸出文件流表示從內存流向外存文件的數據,輸入文件流則相反。根據文件中數據的組織形式,文件可分為兩類:文本文件和二進制文件。文本文件又稱為ASCII文件,它的每個字節存放一個ASCII碼,代表一個字符。二進制文件則是把內存中的數據,按照其在內存中的存儲形式原樣寫在磁盤上存放。比如一個整數20000,在內存中在兩個字節,而按文本形式輸出則占5個字節。因此在以文本形式輸出時,一個字節對應一個字符,因而便於字符的輸出,缺點則是占用存儲空間較多。用二進制形式輸出數據,節省了轉化時間和存儲空間,但不能直接以字符的形式輸出。
\

1. 打開文件

在C++中對文件進行操作分為以下幾個步驟:

(1)建立文件流對象;
(2)打開或建立文件; (3)進行讀寫操作;
(4)關閉文件;

用於文件IO操作的流類主要有三個

fstream(輸入輸出文件流) ifstream(輸入文件流) ofstream(輸出文件流);

而這三個類都包含在頭文件==fstream==中,所以程序中對文件進行操作必須包含該頭文件。首先建立流對象,然後使用文件流類的成員函數open打開文件,即把文件流對象和指定的磁盤文件建立關聯。成員函數open的一般形式為:

文件流對象.open(文件名,使用方式);

其中文件名可以包括路徑(如:e:\c++\file.txt),如果缺少路徑,則默認為當前目錄。使用方式則是指文件將被如何打開。以下就是文件的部分使用方式,都是ios基類中的枚舉類型的值:

\

此外打開方式有幾個注意點:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCigxKdLyzqpub2NyZWF0ZbrNbm9yZXBsYWNlo6zT68+1zbPGvcyoz+C52MPcx9CjrMv50tTU2kMrK7Hq17zW0MiltfTBy7bUy/y1xNans9ahoyAoMinDv9K7uPa08r+qtcTOxLz+trzT0NK7uPbOxLz+1rjV66Os1rjV67XEv6rKvM671sPTybTyv6q3vcq91ri2qKOsw7+0zrbB0LS2vLTTzsS8/ta41eu1xLWxx7DOu9bDv6rKvKGjw7+2wdK7uPbX1r3ao6zWuNXrvs2689LG0ru49tfWvdqho7WxzsS8/ta41evSxrW91+6686Osu+HT9rW9zsS8/r3hyvi3+0VPRqOstMvKscH3ttTP87XEs8nUsbqvyv1lb2a1xNa1zqq3xzDWtaOsse3Kvs7EvP694cr4oaMgKDMp08Npbre9yr208r+qzsS8/ta7xNzTw9PayuTI68r9vt2jrLb4x9K4w87EvP6x2NDr0tG+rbTm1NqhoyAoNCnTw2FwcLe9yr208r+qzsS8/qOstMvKsc7EvP6x2NDrtObU2qOstPK/qsqxzsS8/ta41eu0ptPaxKnOsqOsx9K4w7e9yr3Wu8Tc08PT2srks/ahoyAoNSnTw2F0Zbe9yr208r+q0ru49tLRtObU2rXEzsS8/qOszsS8/ta41evX1Lav0sa1vc7EvP7Eqc6yo6zK/b7dv8nS1NC0yOu1vcbk1tChow0KPHA+yOe5+87EvP7Q6NKq08PBvdbWu/K24NbWt73KvbTyv6qjrNTy08MmcmRxdW87"”來分隔組合在一起。除了用open成員函數打開文件,還可以用文件流類的構造函數來打開文件,其參數和默認值與open函數完全相同。比如:文件流類 stream(文件名,使用方法);如果文件打開操作失敗,open函數的返回值為0,用構造函數打開的話,流對象的值為0。所以無論用哪一種方式打開文件,都需要在程序中測試文件是否成功打開。

在每次對文件IO操作結束後,都需要把文件關閉,那麼就需要用到文件流類的成員函數close,一般調用形式:流對象.close();關閉實際上就是文件流對象和磁盤文件失去關聯。

2. 文件的讀寫

我將分別從文本文件讀寫和二進制文件的讀寫來介紹。其實文件的讀寫是十分容易的。流類庫中的IO操作==<<、>>、put、get、getline、read和write==都可以用於文件的輸入輸出。

寫文件:

#include 
#include 

using namespace std;
int main(int argc, const char * argv[]) {
    fstream File("/Users/yanzexin/Desktop/programming code/file_operation/input.txt", ios::app);
    if (File.is_open()) {
        char str[100];
        cin.getline(str, 100);
        File.write(str, sizeof(str));
        File.write("\n", 1);
    }
    File.close();
    return 0;
}

讀文件:

#include 
#include 

using namespace std;
int main(int argc, const char * argv[]) {
    fstream File("/Users/yanzexin/Desktop/programming code/file_operation/input.txt", ios::in);
    if (File.is_open()) {
        char str[100];
        File.getline(str, 100); //  遇到/n就結束
        char ch;
        while (File.get(ch)) {  //  返回/n的下一個字符,能夠完整得到所有信息。
            cout.put(ch);
        }
    }
    File.close();
    return 0;
}

綜合運用:

//
//  main.cpp
//  file_operation
//
//  Created by 顏澤鑫 on 5/23/16.
//  Copyright ? 2016 顏澤鑫. All rights reserved.
//


#include 
#include 
using namespace std;
class Sprite {
private:
    std::string profession;//職業
    std::string weapon;//武器
    int count;//個數
public:
    Sprite(std::string profession = "",std::string weapon = ""):profession(profession),weapon(weapon) {
        count = 0;
    }
    void set(std::string _profession = "",std::string _weapon = "") {
        profession = _profession;
        weapon = _weapon;
    }
    bool read(string &File_loc) {
        fstream File(File_loc);
        if (File.is_open()) {
            while (!File.eof()) {
                char pro[100];
                char wep[100];
                File.getline(pro, 100, ' ');
                File.getline(wep, 100, ' ');
                profession = pro;
                weapon = wep;
            }
            File.close();
            return true;
        } else {
            cout << "File doesn't open!" << endl;
            return false;
        }
    }
    bool save(string &File_loc) {
        fstream File(File_loc);
        if (File.is_open()) {
            const char* pro = profession.c_str();
            const char* wep = weapon.c_str();
            File.write(pro, sizeof(pro));
            File.write(" ", 1);
            File.write(wep, sizeof(wep));
            File.close();
            return true;
        } else {
            cout << "File doesn't open!" << endl;
            return false;
        }
    }
    void showSprite() {
        std::cout <<"職業:"<

一開始我嘗試著把函數參數設置為fstream,後來發現出現了很多問題(亂碼)。所以實際上更合適的方法是給一個string地址,每次操作時才打開文件,結束操作後就關閉文件。

狀態標志符的驗證(Verification of state flags)

除了eof()以外,還有一些驗證流的狀態的成員函數(所有都返回bool型返回值):

bad()如果在讀寫過程中出錯,返回 true 。例如:當我們要對一個不是打開為寫狀態的文件進行寫入時,或者我們要寫入的設備沒有剩余空間的時候。? fail()除了與bad() 同樣的情況下會返回 true 以外,加上格式錯誤時也返回true ,例如當想要讀入一個整數,而獲得了一個字母的時候。? eof()如果讀文件到達文件末尾,返回true。? good()這是最通用的:如果調用以上任何一個函數返回true 的話,此函數返回 false 。? clear()重置以上成員函數所檢查的狀態標志,沒有參數。

獲得和設置流指針(get and put stream pointers)

所有輸入/輸出流對象(i/o streams objects)都有至少一個流指針:

ifstream, 類似istream, 有一個被稱為get pointer的指針,指向下一個將被讀取的元素。 ofstream, 類似 ostream, 有一個指針 put pointer ,指向寫入下一個元素的位置。

fstream, 類似 iostream, 同時繼承了get 和 put

tellg() 和 tellp()

這兩個成員函數不用傳入參數,返回pos_type 類型的值(根據ANSI-C++ 標准) ,就是一個整數,代表當前get 流指針的位置 (用tellg) 或 put 流指針的位置(用tellp).?
* seekg() 和seekp()這對函數分別用來改變流指針get 和put的位置。兩個函數都被重載為兩種不同的原型:
seekg ( pos_type position );

seekp ( pos_type position );?

使用這個原型,流指針被改變為指向從文件開始計算的一個絕對位置。要求傳入的參數類型與函數 tellg 和tellp 的返回值類型相同。?
seekg ( off_type offset, seekdir direction );

seekp ( off_type offset, seekdir direction );?

使用這個原型可以指定由參數direction決定的一個具體的指針開始計算的一個位移(offset)。它可以是:

ios::beg 從流開始位置計算的位移 ios::cur 從流指針當前位置開始計算的位移 ios::end 從流末尾處開始計算的位移
int main(int argc, const char * argv[]) {
    fstream File("/Users/yanzexin/Desktop/programming code/file_operation/input.txt");
    File.seekg(ios::beg);
    long beg = File.tellg();
    File.seekg(ios::beg, ios::end);
    long end = File.tellg();
    cout << "size is " << (end - beg) << " bytes" << endl;
    File.close();
    return 0;
}

大量數據的永久化

從前文的討論中,我們知道如果純粹使用文件讀寫是很難完成要求的,特別是存在大量對象,大量關聯信息的時候。我們預期能夠在文件中寫入信息並且給出信息的關聯性(簡單的說,就是模擬數據庫)!

於是乎我想到了之前學到了的json。使用json,可以把很多相關的信息關聯起來,而且json從本質上來說就是string,所以可以輕松地寫入文件中,並且從文件中讀出。

關於json的介紹和相關庫的下載,見前文:Json總結

//
//  main.cpp
//  file_operation
//
//  Created by Stary on 5/23/16.
//  Copyright ? 2016 Stary. All rights reserved.
//

/*
[{"profession":"Mage","weapon":"truncheon"},{"profession":"Bushido","weapon":"sword"}]
*/

#include 
#include 
#include 
#include 
#include "json.hpp"
using json = nlohmann::json;
using namespace std;
namespace Sprite_domain {
    class Sprite {
    public:
        std::string profession;//職業
        std::string weapon;//武器
        int count;//個數
        Sprite(std::string profession = "",std::string weapon = ""):profession(profession),weapon(weapon) {
            count = 0;
        }
        void set(std::string _profession = "",std::string _weapon = "") {
            profession = _profession;
            weapon = _weapon;
        }
        bool save(string &File_loc) {
            fstream File(File_loc, ios::app);
            if (File.is_open()) {
                const char* pro = profession.c_str();
                const char* wep = weapon.c_str();
                File.write(pro, sizeof(pro));
                File.write(" ", 1);
                File.write(wep, sizeof(wep));
                File.write("\n", 1);
                File.close();
                return true;
            } else {
                cout << "File doesn't open!" << endl;
                return false;
            }
        }
        void showSprite() {
            std::cout <<"職業:"<> &vec_ptr_Sprite) {
        fstream File(File_loc, ios::in);
        if (File.is_open()) {
            while (!File.eof()) {
                char pro[100];
                char wep[100];
                File.getline(pro, 100, ' ');
                File.getline(wep, 100, '\n');
                //            cout << pro << " " << wep << endl;
                vec_ptr_Sprite.push_back(shared_ptr(new Sprite(pro, wep)));
            }
            File.close();
            return true;
        } else {
            cout << "File doesn't open!" << endl;
            return false;
        }
    }
    bool log(string &File_loc, json &temp) {
        fstream File(File_loc, ios::out);
        if (File.is_open()) {
            File.write(temp.dump().c_str(), strlen(temp.dump().c_str()));
            File.close();
            return true;
        } else {
            cout << "error!" << endl;
            File.close();
            return false;
        }
    }
    bool input(string &File_loc, json& temp) {
        fstream File(File_loc, ios::in);
        if (File.is_open()) {
            char ch;
            string str;
            while (File.get(ch)) {  //  返回/n的下一個字符,能夠完整得到所有信息。
                str += ch;
            }
            temp = json::parse(str);
            return true;
        } else {
            return false;
        }
    }
}

int main(int argc, const char * argv[]) {
    string File_Json("/Users/yanzexin/Desktop/programming code/file_operation/source.json");
    json Sprite_json;
    vector> vec_ptr_Sprite;
    Sprite_domain::input(File_Json, Sprite_json);
    for (auto sprite : Sprite_json) {
        vec_ptr_Sprite.push_back(shared_ptr(new Sprite_domain::Sprite(sprite["profession"], sprite["weapon"])));
    }
    for (auto ptr : vec_ptr_Sprite) {
        cout << ptr->profession << " " << ptr->weapon << endl;
    }
    return 0;
}

值得注意的是,我在vector中存放了shard_ptr,沒有使用原生態指針,從而簡化問題,避免了自己調用內存釋放的麻煩。同時在json中存放內容,可以通過鍵值對來尋找相關信息。

總結

數據永久化對於初學者來說是非常難實現的功能,特別是在沒有學會數據庫的時候,所以在本文介紹我所能掌握的方法。歡迎互相交流~

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved