1. 安装yaml-cpp
安装yaml-cpp库
git clone https://github.com/jbeder/yaml-cpp.git
cd yaml-cpp
mkdir build && cd build
cmake .. && make -j
sudo make install
2. yaml语法
YAML 是专门用来写配置文件的语言,非常简洁和强大,比 JSON和xml格式要方便很多。
YAML 语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写。它实质上是一种通用的数据串行化格式。它的基本语法规则如下。
- 大小写敏感
- 使用缩进表示层级关系
- 缩进时不允许使用Tab键,只允许使用空格。
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
#
表示注释,从这个字符一直到行尾,都会被解析器忽略。
YAML 支持的数据结构有三种。
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值
对象
对象的一组键值对,使用冒号结构表示,注意冒号后面有一个空格。
animal: pets
yaml 也允许另一种写法,将所有键值对写成一个行内对象。
hash: { name: Steve, foo: bar }
数组
一组连词线开头的行,构成一个数组,注意连词线仍旧有一个空格。
- Cat
- Dog
- Goldfish
相当于python列表
[ 'Cat', 'Dog', 'Goldfish' ]
数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。
-
- Cat
- Dog
- Goldfish
相当于Python列表
[ [ 'Cat', 'Dog', 'Goldfish' ] ]
数组也可以采用行内表示法。
animal: [Cat, Dog]
相当于Python字典
{ animal: [ 'Cat', 'Dog' ] }
复合结构
对象和数组可以结合使用,形成复合结构。
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org
相当于Python字典
{ languages: [ 'Ruby', 'Perl', 'Python' ],
websites:
{ YAML: 'yaml.org',
Ruby: 'ruby-lang.org',
Python: 'python.org',
Perl: 'use.perl.org' } }
纯量
纯量是最基本的、不可再分的值
- 字符串
- 布尔值
- 整数
- 浮点数
- Null
- 时间
- 日期
数值直接以字面量的形式表示。
number: 12.30
布尔值用true
和false
表示。
isSet: true
null
用~
表示。
parent: ~
时间采用 ISO8601 格式。
iso8601: 2001-12-14t21:59:43.10-05:00
日期采用复合 iso8601 格式的年、月、日表示。
date: 1976-07-31
YAML 允许使用两个感叹号,强制转换数据类型。
e: !!str 123
f: !!str true
字符串
字符串是最常见,也是最复杂的一种数据类型。字符串默认不使用引号表示。
str: 这是一行字符串
如果字符串之中包含空格或特殊字符,需要放在引号之中。
str: '内容: 字符串'
单引号和双引号都可以使用,双引号不会对特殊字符转义。
s1: '内容\n字符串'
s2: "内容\n字符串"
单引号之中如果还有单引号,必须连续使用两个单引号转义。
str: 'labor''s day'
字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。
str: 这是一段
多行
字符串
多行字符串可以使用|保留换行符,也可以使用>折叠换行。
this: |
Foo
Bar
that: >
Foo
Bar
+
表示保留文字块末尾的换行,-
表示删除字符串末尾的换行。
s1: |
Foo
s2: |+
Foo
s3: |-
Foo
字符串之中可以插入 HTML 标记。
message: |
段落
引用
锚点&
和别名*
,可以用来引用。
defaults: &defaults
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaults
test:
database: myapp_test
<<: *defaults
等同于下面的代码。
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
test:
database: myapp_test
adapter: postgres
host: localhost
&
用来建立锚点(defaults
),<<
表示合并到当前数据,*
用来引用锚点。
下面是另一个例子。
- &showell Steve
- Clark
- Brian
- Oren
- *showell
3. yaml的解析
3.1 Node
Node 是 yaml-cpp 中的核心概念,是最重要的数据结构,它用于存储解析后的 yaml 信息。Node一共有以下几种type:
Null 空节点
Sequence 序列,类似于一个Vector,对应YAML格式中的数组
Map 类似标准库中的Map,对应YAML格式中的对象
Scalar 标量,对应YAML格式中的常量
生成 Node 的形式有很多种, loadFile() 是最常见的一种。
Node LoadFile(const std::string& filename)
filename 就是yaml文件的路径。
有了 Node 之后,所有的信息都可以检索到。比如 name.
cout << "name:" << config["name"].as() << endl;
as
你也可以转换成其它类型。
它是一个模板方法。
3.2 yaml文件的解析
比如这样一个配置文件config.yaml
name: frank
sex: male
age: 18
skills:
c++: 1
java: 1
android: 1
python: 1
注意yaml 中的内容 ","":"后面一定要加空格
解析代码yaml_test.cpp
#include
#include "yaml-cpp/yaml.h"
#include
using namespace std;
int main(int argc,char** argv)
{
YAML::Node config;
try{
config = YAML::LoadFile("../config.yaml");
}catch(YAML::BadFile &e){
std::cout<<"read error!"<() << endl;
cout << "name:" << config["name"].as() << endl;
cout << "sex:" << config["sex"].as() << endl;
cout << "age:" << config["age"].as() << endl;
//读取不存在的node值,报YAML::TypedBadConversion异常
try{
string label = config["label"].as();
}catch(YAML::TypedBadConversion &e){
std::cout<<"label node is NULL"<() << endl;
cout << "skills java:" << config["skills"]["java"].as() << endl;
cout << "skills android:" << config["skills"]["android"].as() << endl;
cout << "skills python:" << config["skills"]["python"].as() << endl;
for(YAML::const_iterator it= config["skills"].begin(); it != config["skills"].end();++it)
{
cout << it->first.as() << ":" << it->second.as() << 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.yaml"); //保存config为yaml文件
config["score"] = 99;//添加新元素
fout << config;
fout.close();
return 0;
}
4. node的增改查删
以下直接上代码,详细的情况请看注释。
#include
#include
#include
#include
int main()
{
YAML::Node node;
assert(node.IsNull()); //初始化的节点是Null类型
node["key"] = "value"; //当你给它赋值键值对,它转变为Map类型
//node.force_insert("key", "value");//这个操作和上面等价,但是它不会检查是否存在"key"键,不推荐使用
if(node["mascot"])
std::cout << node["mascot"].as() << "\n";//单纯的查询操作不会增加一个key,当然上面的if不会执行
node["number"] = 255;
assert(node.IsMap()); //node是一个Map
node["seq"].push_back("first element");
node["seq"].push_back("second element");//node的seq下是Sequence类型,有两个参数
YAML::Node node_2;
node_2.push_back("first item");//如果你不给node_2键值对,它是一个sequence类型
node_2.push_back("second_item");
node_2.push_back("third_item");
std::vector v = {1,3,5,7,9};//给node_2插入了一个Sequence
node_2.push_back(v);
assert(node_2.IsSequence());//当然,node_2仍然是一个Sequence
assert(node_2[0].as() == "first item");
//对于Sequence类型,你可以使用它的下标来访问
//注意这里as是一个模板转换,node_2[0]的type是NodeType::Scalar
auto it = node_2.begin();
for(; it != node_2.end(); it++)
std::cout << *(it) << std::endl;
//当然,你也可以用迭代器来访问
//他们的类型分别是NodeType::Scalar,NodeType::Scalar,NodeType::Scalar,NodeType::Sequence
//取值时记得使用as进行模板转换
node_2["key"] = "value";
assert(node_2.IsMap());//一旦node_2接收到键值对,它转变为Map类型
assert(node_2[0].as() == "first item");//此时,Sequence时的下标变为它的key值
node["node_2"] = node_2;//将node_2作为node的一个子项
node["pointer_to_first_element"] = node["seq"][0];//你也可以给已有的node设置一个别名,类似于一个指针
assert(node["pointer_to_first_element"].as() == "first element");//你可以通过这个指针访问那个node
node.remove(node["seq"][0]);//你可以通过指定一个node来删除它
node.remove("pointer_to_first_element");//你也可以通过指定key来删除它
}
如果你执行std::cout << node << endl;
应该会得到以下结果:
key: value
number: 255
seq:
- first element
- second element
node_2:
0: first item
1: second_item
2: third_item
3:
- 1
- 3
- 5
- 7
- 9
key: value
5. yaml-cpp 中的迭代
yaml-cpp 中也可以通过迭代的方式,访问 Node 中的内容。
比如,访问 skills 下面的各个元素。
for(YAML::const_iterator it= config["skills"].begin(); it != config["skills"].end();++it)
{
cout << it->first.as() << ":" << it->second.as() << endl;
}
用 begin() 获取迭代器,用 end() 判断迭代器是否结束。
6. NodeType类型
yaml 支持 Scalar、List、Map 类型,yaml-cpp 通过 NodeType 定义了 Node 的可能类型。
namespace YAML {
struct NodeType {
enum value { Undefined, Null, Scalar, Sequence, Map };
};
}
对应未定义、空、标量、序列、字典。
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。
结果如下:
Type: 3
Type: 2
Type: 4
分别对应 Sequence、Scalar、Map。
7. yaml-cpp 写配置文件(yaml文件的保存与读取)
日常开发中,经常用yaml文件来做配置文件,除了读取配置参数,我们经常需要保存参数,yaml-cpp 自然也提供了相应的功能。
Node可以使用文件流的方式进行读写,前面已经使用过了,保存一个node可以用下面的方法:
std::ofstream fout("config.yaml");
...//设置配置文件node数据
fout << node <
这样,上面打印到cout的内容会被输出到config.yaml文件。
为了读取一个node,你可以这么做:
std::ifstream file("config.yaml");
YAML::Node node = YAML::Load(file);//读取来自test.yaml的node文件
std::cout << node <first << it->second << std::endl;//也可以用迭代器访问
8. 在ROS中使用yaml
在ROS中使用,需要对CMakeLists.txt修改,增加yaml-cpp库,且使用一个roslib的package,设置yaml文件的路径。
link_libraries(yaml-cpp)
find_package(catkin REQUIRED COMPONENTS
roslib
)
源文件中添加头文件#include
#include
......
std::string dir_package, dir_package_file;
dir_package = ros::package::getPath("xxx");
dir_package_file = dir_package + "/config/test.yaml";
loadYamlFile(dir_package_file);
参考:
https://en.wikipedia.org/wiki/YAML
https://yaml.org/spec/1.2/spec.html
https://www.ruanyifeng.com/blog/2016/07/yaml.html
https://www.cnblogs.com/huodaozhe/p/12026327.html
https://blog.csdn.net/briblue/article/details/89515470