c++ 开发中利用yaml-cpp读写yaml配置文件

2019-05-10 09:55:11 浏览数 (1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1423468

在程序员的开发生涯中,读写配置文件必不可少。

配置文件有利于我们灵活配置工程,解决大量重复劳动,也方便调试。

配置文件的格式有很多,最简单的有一行一行的文本,也有像 json、xml、protocol buffer 这样结构化的格式,当然也有 yaml 这种格式。

今天的博文介绍的是如何在 C 开发中利用 yaml-cpp 开源库读写 yaml 配置文件。

如果有 Python 开发经验的同学,可能知道用 Python 读取 yaml 是再简单不过了,但是 C 麻烦一点,它需要你自己下载源码然后编译生成库文件。

yaml-cpp

yaml-cpp 是一个开源库,地址在 github 上,https://github.com/jbeder/yaml-cpp

yaml-cpp 是通过 CMake 来进行构建和编译的。

在这里假设读者都有 CMake 相关的经验,没有的同学自行百度。我的博文也写过比较简单的几篇,有兴趣的可以去看一看。

首先下载源码。

然后,在源码目录创建一个 build 文件夹。

代码语言:javascript复制
mkdir build

进入到 build 文件夹,然后执行 cmake 命令。

代码语言:javascript复制
cd build

cmake ..

注意的是 cmake 后面是 ..,这代表从 build 上一层目录查找 CMakeLists.txt ,然后编译的文件都会存放在 build 文件夹,如果对编译的效果不满意,只要删除 build 文件就好了,其他源码目录并不受影响,这是 cmake 编译时的基本套路。

yaml-cpp 默认构建的就是静态库,也就是 unix 类系统下的 .a 文件,如果你想构建动态库的话,就需要在 cmake 时指定。

代码语言:javascript复制
cmake ..  -D BUILD_SHARED_LIBS=ON

编译成功后,会生成库文件,你只需要将库文件和头文件拷贝到你自己的工程当中,就可以使用了。

需要处理好头文件。

你如果不想每次都到 copy 头文件到不同的工程中,那么你可以将头文件 copy 到系统默认的头文件目录,比如 ubuntu 的地址是 /usr/local/include,将库文件拷贝到系统默认的 lib 文件就好了,比如 ubuntu 是 /usr/local/lib

有了头文件和库,我们就可以顺利写代码了。

读取 yaml 配置文件

假设我们有这样一个配置文件

config.yaml

代码语言:javascript复制
name: frank
sex: male
age: 18

skills: 
  c  : 1
  java: 1
  android: 1
  python: 1

温馨提示:yaml 中的内容,:后面一定要加空格哦

现在,我们的目标是要把它正确的读取出来。

yaml_test.cpp

代码语言:javascript复制
#include <iostream>
#include "include/yaml-cpp/yaml.h"

using namespace std;

int main(int argc,char** argv)
{
    YAML::Node config = YAML::LoadFile("../config.yaml");

    cout << "name:" << config["name"].as<string>() << endl;
    cout << "sex:" << config["sex"].as<string>() << endl;
    cout << "age:" << config["age"].as<int>() << endl;
    return 0;
}

头文件在 include 目录。

libs 存放 .so 文件。

然后通过 cmake 编译,因为我习惯用 cmake,如果读者喜欢用原始的 g 编译或者 makefile 也是可以的。

我的 CMakeFileLists.txt 如下:

代码语言:javascript复制
cmake_minimum_required(VERSION 3.2)

project(yaml_test)

add_definitions(-std=c  11)


include_directories(include)
set(SRCS yaml_test.cpp)
add_executable(yamltest ${SRCS})

target_link_libraries(yamltest ${CMAKE_HOME_DIRECTORY}/libs/libyaml-cpp.so)

在当前目录创建 build 文件夹,然后进入 build 文件执行 cmake 操作。

代码语言:javascript复制
mkdir build

cd build

cmake ..

最终生成了名为 yamltest 的可执行文件。

执行后,输出的信息如下。

代码语言:javascript复制
name:frank
sex:male
age:18

可以看到,信息都被正常的读取出来了。

Node

Node 是 yaml-cpp 中的核心概念,它用于存储解析后的 yaml 信息。

生成 Node 的形式有很多种, loadFile() 是最常见的一种。

代码语言:javascript复制
Node LoadFile(const std::string& filename)

filename 就是配置文件的路径。

有了 Node 之后,所有的信息都可以检索到。

比如 name.

代码语言:javascript复制
cout << "name:" << config["name"].as<string>() << endl;

as<string>()表示将解析的内容转换成 string 类型。

你也可以转换成其它类型。

它是一个模板方法。

有同学可能会有疑惑。

代码语言:javascript复制
skills:  
  c  : 1
  java: 1
  android: 1
  python: 1

skills 的信息怎么读呢?

其实也非常简单。

代码语言:javascript复制
cout << "skills c  :" << config["skills"]["c  "].as<int>() << endl;
cout << "skills java:" << config["skills"]["java"].as<int>() << endl;
cout << "skills android:" << config["skills"]["android"].as<int>() << endl;
cout << "skills python:" << config["skills"]["python"].as<int>() << endl;
yaml-cpp 中的迭代

yaml-cpp 中也可以通过迭代的方式,访问 Node 中的内容。

比如,访问 skills 下面的各个元素。

代码语言:javascript复制
for(YAML::const_iterator it= config["skills"].begin(); it != config["skills"].end();  it)
{
    cout << it->first.as<string>() << ":" << it->second.as<int>() << endl;
}

用 begin() 获取迭代器,用 end() 判断迭代器是否结束。

NodeType

yaml 支持 Scalar、List、Map 类型,yaml-cpp 通过 NodeType 定义了 Node 的可能类型。

代码语言:javascript复制
namespace YAML {
struct NodeType {
  enum value { Undefined, Null, Scalar, Sequence, Map };
};
}

对应未定义、空、标量、序列、字典。

代码语言:javascript复制
YAML::Node test1 = YAML::Load("[1,2,3,4]");
cout << " Type: " << test1.Type() << endl;

YAML::Node test2 = YAML::Load("1");
cout << " Type: " << test2.Type() << endl;

YAML::Node test3 = YAML::Load("{'id':1,'degree':'senior'}");
cout << " Type: " << test3.Type() << endl;

上面的代码是为了判断 NodeType。

结果如下:

代码语言:javascript复制
 Type: 3
 Type: 2
 Type: 4

分别对应 Sequence、Scalar、Map。

yaml-cpp 写配置文件

日常开发中,除了读取配置参数,我们经常需要保存参数,yaml-cpp 自然也提供了相应的功能。

代码语言:javascript复制
ofstream fout("testconfig.xml");

config["score"] = 99;

fout << config;

fout.close();

前面代码解析成功的 config,现在添加一个 score,然后保存。

运行代码后,发现 build 文件夹下正确保存了 testconfig.xml 文件,score 被正确添加进去了。

代码语言:javascript复制
name: frank
sex: male
age: 18
skills:
  c  : 1
  java: 1
  android: 1
  python: 1
score: 99

到此,yaml-cpp 的简单使用就 OK 了,读者可以查看代码去深入学习。

本篇文章示例代码,目录结构如下图:

完整代码:

yaml_test.cpp

代码语言:javascript复制
#include <iostream>
#include "include/yaml-cpp/yaml.h"
#include <fstream>

using namespace std;

int main(int argc,char** argv)
{
    YAML::Node config = YAML::LoadFile("../config.yaml");

    cout << "Node type " << config.Type() << endl;
    cout << "skills type " << config["skills"].Type() << endl;

    cout << "name:" << config["name"].as<string>() << endl;
    cout << "sex:" << config["sex"].as<string>() << endl;
    cout << "age:" << config["age"].as<int>() << endl;

    cout << "skills c  :" << config["skills"]["c  "].as<int>() << endl;
    cout << "skills java:" << config["skills"]["java"].as<int>() << endl;
    cout << "skills android:" << config["skills"]["android"].as<int>() << endl;
    cout << "skills python:" << config["skills"]["python"].as<int>() << endl;

    for(YAML::const_iterator it= config["skills"].begin(); it != config["skills"].end();  it)
    {
        cout << it->first.as<string>() << ":" << it->second.as<int>() << endl;
    }

    YAML::Node test1 = YAML::Load("[1,2,3,4]");
    cout << " Type: " << test1.Type() << endl;

    YAML::Node test2 = YAML::Load("1");
    cout << " Type: " << test2.Type() << endl;

    YAML::Node test3 = YAML::Load("{'id':1,'degree':'senior'}");
    cout << " Type: " << test3.Type() << endl;

    ofstream fout("testconfig.xml");

    config["score"] = 99;

    fout << config;

    fout.close();


    return 0;
}

0 人点赞