首页 > 程序开发 > 软件开发 > C++ >

[C++]关于数据永久化的思考(不使用数据库)

2016-05-24

关于数据永久化的思考(不使用数据库) ==数据永久化==是一个程序很重要的特性。我们知道使用数据库肯定可以实现数据永久化,但对于新手而言,比较艰难。本文讨论的是,如何不使用数据库来完成数据的保存。

数据库">关于数据永久化的思考(不使用数据库)

==数据永久化==是一个程序很重要的特性。我们知道使用数据库肯定可以实现数据永久化,但对于新手而言,比较艰难。本文讨论的是,如何不使用数据库来完成数据的保存。主要提供两种方法,一种是使用纯粹的文件读写,另一种使用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基类中的枚举类型的值:

\

此外打开方式有几个注意点:<喎"http://www.2cto.com/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, &#39; &#39;);
                File.getline(wep, 100, &#39; &#39;);
                profession = pro;
                weapon = wep;
            }
            File.close();
            return true;
        } else {
            cout << "File doesn&#39;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&#39;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&#39;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, &#39; &#39;);
                File.getline(wep, 100, &#39;\n&#39;);
                //            cout << pro << " " << wep << endl;
                vec_ptr_Sprite.push_back(shared_ptr(new Sprite(pro, wep)));
            }
            File.close();
            return true;
        } else {
            cout << "File doesn&#39;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中存放内容,可以通过键值对来寻找相关信息。

总结

数据永久化对于初学者来说是非常难实现的功能,特别是在没有学会数据库的时候,所以在本文介绍我所能掌握的方法。欢迎互相交流~

相关文章
最新文章
热点推荐