• C++中使用boost库存取ini结构化文本文件


    包含如下内容的文件dora.ini存储了学号为20210426的某同学的姓名、年龄、以及已修三门课程的名称和分数。这种名为ini的文件格式可以很方便地存储结构化的对象信息。相较于自行设计文本文件的内容结构,直接使用ini格式既方便,扩展性又好。本实践中,我们借助于大名鼎鼎的boost库来解析ini文件。

    [basic]
    sNo=20210426
    sName=Dora Chen
    iAge=17
    [scores]
    size=3
    sName_0=C++
    iScore_0=97
    sName_1=Calculus
    iScore_1=70
    sName_2=Economics
    iScore_2=65
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
    叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
    1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
    2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
    3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

    在实践中,我们经常需要借助类和对象来表示一个个的实体,例如学籍管理系统中的学生、医疗档案管理系统中的病人。请看如下数据结构:

    class Score {
    public:
        string sName;            //课程名称
        int iScore;              //分数
    };
    
    class Student {
        string sNo;              //学号
        string sName;            //姓名
        int iAge;                //年龄
        vector<Score> scores;    //成绩表
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这个数据结构中,一个Student对象代表一个学生,其有学号、姓名、年龄等属性;另外还有一个类型为向量的属性scores,该属性存储了学生0到多门已修课程的成绩对象,该对象有课程名称及分数两个属性。

    现在考虑将Student对象序列化(保存)到一个文本文件里。在这个数据结构里,一个学生有多少门已修课程是不确定的。对于这种带有不确定性的甚至预期可能发生改变(比如增加性别属性)的数据结构,编程者自行组织文件的存储格式面临诸多不便:①繁琐;②未来数据结构改变时,调整困难。

    有一种称之为ini的文本文件结构特别适合存储此种数据结构。ini是initialization(初始化)的简写,这种文件本来的用途是用于存储软件的配置信息,但有也人(比如作者)喜欢借用这个结构来序列化对象。

    接下来,我们通过boost库的ini_parser模块来完成ini文件的存储和解析。在介绍C++程序StudentInfo之前,我们先展示StudentInfo所保存出来的dora.ini文件的内容。

    [basic]
    sNo=20210426
    sName=Dora Chen
    iAge=17
    [scores]
    size=3
    sName_0=C++
    iScore_0=97
    sName_1=Calculus
    iScore_1=70
    sName_2=Economics
    iScore_2=65
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    容易看出,ini文件最基本的信息形式为key=value。等号左边为键(key),右边为值(value)。dora.ini分为两个部分,[basic]部分用于存储学号、姓名和年龄,[scores]部分则用于存储全部已修课程的成绩信息。键size=3表明存储了三门课的成绩,由于每门课都有课程名称和分数,为消除歧义,故使用sName_i来表示第i门课的课程名称,iScore_i来表示第i门课的分数。

    C++程序StudentInfo先是创建了用于表示Dora Chen的学生对象dora1,并为其添加了C++、微积分、经济学三门课程的成绩;然后将该对象序列化存储至文件dora.ini;然后再从dora.ini读取其内容至学生对象dora2并打印出来。完整代码如下:

    //Project - StudentInfo
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    class Score {
    public:
        string sName;           //课程名称
        int iScore;             //分数
        Score(const string& name, const int score){
            sName = name;
            iScore = score;
        }
    };
    
    class Student {
        string sNo;             //学号
        string sName;           //姓名
        int iAge;               //年龄
        vector<Score> scores;   //成绩表
    
    public:
        Student(){}
        Student(const string& no, const string& name, const int age){
            sNo = no; sName = name; iAge = age;
        }
    
        void addScore(const string& name, const int score){
            scores.emplace_back(name,score);
        }
    
        void save(const string& sFile){
            boost::property_tree::ptree s;
    
            s.put("basic.sNo",sNo);
            s.put("basic.sName",sName);
            s.put("basic.iAge",iAge);
    
            s.put("scores.size",scores.size());
            for (unsigned int i=0;i<scores.size();i++){
                auto& r = scores[i];
                s.put(string("scores.sName_")+std::to_string(i),r.sName);
                s.put(string("scores.iScore_")+std::to_string(i),r.iScore);
            }
    
            boost::property_tree::ini_parser::write_ini(sFile,s);
        }
    
        void load(const string& sFile){
            boost::property_tree::ptree s;
            boost::property_tree::ini_parser::read_ini(sFile,s);
    
            sNo = s.get("basic.sNo","");
            sName = s.get("basic.sName","");
            iAge = s.get("basic.iAge",0);
    
            scores.clear();
            auto size = s.get("scores.size",0);
            for (auto i=0;i<size;i++){
                auto sName = s.get(string("scores.sName_")+std::to_string(i),"");
                auto iScore = s.get(string("scores.iScore_")+std::to_string(i),0);
                scores.emplace_back(sName,iScore);
            }
        }
    
        void output(ostream& o){
            o << left;
            o << setw(10)<<"No."<<setw(15)<<"Name"<<setw(6)<<"Age"<<endl;
            o << "-------------------------------" << endl;
            o << setw(10)<<sNo<<setw(15)<<sName<<setw(6)<<iAge<<endl;
            o << "-------------------------------" << endl;
            for (auto& s:scores)
                o << setw(25) << s.sName << setw(6) << s.iScore << endl;
        }
    };
    
    int main() {
        Student dora1("20210426","Dora Chen",17);
        dora1.addScore("C++",97);
        dora1.addScore("Calculus",70);
        dora1.addScore("Economics",65);
        dora1.save("dora.ini");             //保存对象dora1至文件dora.ini
    
        Student dora2;
        dora2.load("dora.ini");             //从文件dora.ini读取内容至dora2
        dora2.output(cout);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    上述代码的执行结果为:

    No.       Name           Age
    -------------------------------
    20210426  Dora Chen      17
    -------------------------------
    C++                      97
    Calculus                 70
    Economics                65
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    C++的标准模板库并不提供解析ini文件的能力,本着“不要重新发明轮子”的原则,我们引用了大名鼎鼎的boost库才完成相应任务。

    首先作者下载了当前最新版本(v1.78.0)的boost库压缩包并将其解压缩至D:/C2Cpp目录下,如图20-6所示。
    图20-6 解压缩后的boost库
    接下来,作者在Qt Creator中编辑了项目文件StudentInfo.pro,增加了下述内容中的第6行。该行内容将boost库目录纳入项目的头文件包含目录中。这样,当cpp文件通过#include宏指令引入boost中的头文件时,编译器里的预处理器可以在相应的目录中找到它们。

    注意:在Qt Creator中创建项目时,其中的Build System项有cmake和qmake两种,请务必选择qmake,否则会找不到下述StudentInfo.pro文件。

    TEMPLATE = app
    CONFIG += console c++11
    CONFIG -= app_bundle
    CONFIG -= qt
    
    INCLUDEPATH += D:/C2Cpp/boost_1_78_0
    
    SOURCES += \
            main.cpp
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    🚩第5 ~ 6行:引入boost库中的属性树(ptree)以及ini解析器(ini_parser)头文件。属性树是一种树形数据结构,对其内部工作原理的探讨超出来本书的范围,在本书中,我们将其视为提供了可用功能接口的黑盒,而忽视其内部结构。

    🚩第80 ~ 91行:程序主体部分。main()首先构造了一个名为dora1的Student对象,随后的三行通过addScore()成员函数为dora1添加了C++、微积分、经济学三门课程的成绩。接下来,执行dora1的save()函数将对象内容序列化并存储至ini格式的文件dora.ini。然后,程序创建了一个新的Student对象dora2,通过执行dora2的load()函数从dora.ini读取数据至dora2,最后通过dora2的output()函数将信息打印至屏幕,以便确认dora2与dora1在内容上的一致性。

    本程序中Score、Student类型的数据成员声明、构造函数定义等部分并无特别之处,我们重点解释Student类型的save()和load()函数。

    35       void save(const string& sFile){
    36           boost::property_tree::ptree s;
    37   
    38           s.put("basic.sNo",sNo);
    39           s.put("basic.sName",sName);
    40           s.put("basic.iAge",iAge);
    41   
    42           s.put("scores.size",scores.size());
    43           for (unsigned int i=0;i<scores.size();i++){
    44               auto& r = scores[i];
    45               s.put(string("scores.sName_")+std::to_string(i),r.sName);
    46               s.put(string("scores.iScore_")+std::to_string(i),r.iScore);
    47           }
    48   
    49           boost::property_tree::ini_parser::write_ini(sFile,s);
    50       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    🚩第35 ~ 50行:Student的save()函数负责将Student对象内容序列化并存储至ini格式文件sFile中,sFile为指定文件名。

    🚩第36行:函数构造了一个空的属性树(ptree)对象s,类型ptree位于boost::property_tree名字空间之下。

    🚩第38 ~ 40行:接下来,通过s的put函数往属性树中添加键值对。如第38行所示,put()函数的第一个参数为键,第二个参数为值,其中键以S.K的形式提供,S表示分区(Section),K表示分区下的健。具体到本例,s.put(“basic.sNo”,sNo)的执行结果对应dora.ini中的下述内容:

    [basic]
    sNo=20210426
    
    • 1
    • 2

    读者应注意到,属性树的put()函数是函数名重载的,因为其第2个参数既可以是字符串,也可以是整数或者其他类型的对象。

    🚩第42行:在scores分区下添加名为size的键,表示scores向量的元素数量。具体到本例,执行结果对应dora.ini中的下述内容:

    [scores]
    size=3
    
    • 1
    • 2

    🚩第43 ~ 47行:对scores向量进行遍历,将课程名称和分数逐一加入属性树s。为了区分不同序号的课程,在键名后附加整数序号。具体到本例,执行结果对应dora.ini中的下述内容:

    [scores]
    ...
    sName_0=C++
    iScore_0=97
    sName_1=Calculus
    iScore_1=70
    sName_2=Economics
    iScore_2=65
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    🚩第49行:通过boost::property_tree::ini_parser名字空间下的write_ini()函数将属性树s中的信息写入文件sFile,文件格式为ini。

    🚩第52 ~ 67行:Student的load()函数负责从ini格式文件sFile读取内容并填入内部数据结构,其中,sFile为指定文件名。

    🚩第54行:使用boost::property_tree::ini_parser名字空间下的read_ini()函数将指定的ini格式文件sFile的全部内容读入属性树s。

    🚩第56 ~ 66行:通过get()函数从属性树s获取属性并填入内部数据结构。属性树的get()函数用于读取其内部的键值对。函数的第一个参数为形如S.K的键,S表示分区(Section),K表示分区下的健。第二个参数则为默认值,即当指定的键不存在时,直接返回默认值。

    容易看出,同put()函数一样,get()函数也有多个函数名重载的版本,其第2个参数(默认值)的类型间接决定了get()函数的返回值类型。

    除ini格式之外,boost库还支持对xml、json等结构化文本文件的读取。作者的建议是,对于那些结构化的数据,尽量使用现成的结构化的文本文件格式来存取。除boost外,大部分第三方C++库,比如Qt,也提供对ini等结构化文本文件的直接支持,没有必要设计“个性化”的文本文件存储结构。

    练习巩固 👣

    20-3(json文件)修改20.3节中的示例程序,使用json格式存储学生及成绩信息。

    为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!

    简洁的C及C++
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
    Python编程基础及应用
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造

    如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。

    Python编程基础及应用

    Python编程基础及应用实验教程
    在这里插入图片描述

  • 相关阅读:
    C++继承
    C#设置数据库索引
    Linux共享内存创建和删除
    这个面试官真烦,问完合并又问拆分。
    函数的节流和防抖?节流和防抖的区别及实现
    Vulkan SDK 中的 demo 编译配置 win10 vs2019
    【操作系统】自旋锁实现&&自旋锁原理(亲测可用)
    vue2.0 使用可选链操作符
    二叉树模板套题——相同的树的应用
    浅谈快速开发平台:突破系统开发边界,赋能企业数字化!
  • 原文地址:https://blog.csdn.net/SeaBiscuitUncle/article/details/127441070