protobuf学习日记 | 认识protobuf中的类型

目录

前言

一、标量数据类型

二、protobuf中的 “数组” 

三、特殊类型

1、枚举类型

(1)类型讲解 

(2)升级通讯录

2、Any类型

(1)类型讲解 

(2)升级通讯录

3、oneof类型

(1)类型讲解

(2)升级通讯录

4、map类型

(1)类型讲解

(2)升级通讯录


前言

        本文为protobuf系列的第二期,本文主要介绍protobuf中的数据类型,初步认识这些数据类型后,在来通过这些类型来不断完善我们通讯录的小项目;

一、标量数据类型

        protobuf中将类型分为标量数据类型与特殊类型(枚举等),下面为常见的标量数据类型,与在C++中对应类型;

.proto type Notes C++ type
double double
float float
int32 使用变长编码。负数的编码效率较低,若字段可能为负值,应用 sint32 代替。 int32
int64 使用变长编码。负数的编码效率较低,若字段可能为负值,应用 sint64 代替。 int64
uint32 使用变长编码。 uint32
uint64 使用变长编码。 uint64
sint32 使用变长编码。符号整型。负值的编码效率高于常规的 int32 类型。 int32
sint64 使用变长编码。符号整型。负值的编码效率高于常规的 int64 类型。 int64
fixed32 定长4字节。若值常大于 2^28 则会比 uint32 更高效。 uint32
fixed64 定长8字节。若值常大于 2^56 则会比 uint64 更高效。 uint64
sfixed32 定长4字节。 int32
sfixed64 定长8字节。 int64
bool bool
string 包含 UTF-8 和 ASCII 编码的字符串,长度不能超过 2^32 。 string
bytes 可包含任意的字节序列但长度不能超过 2^32 。 string

        上述资料来自 protobuf 官网。如下图所示;protobuf官网

protobuf学习日记 | 认识protobuf中的类型_第1张图片

        这里对上述表格内容进行解释补充;

1、所谓变长编码指的是protobuf在序列化中,不是按照固定字节进行序列化,而采用根据具体数据变化而不定长的编码。

2、定长编码则与变长为相反概念,为固定字节编码。

二、protobuf中的 “数组” 

        假若我们想让message中字段中有数组类型的字段我们应该如何处理呢?实际上,这里是通过字段规则来控制的,我们通过给字段声明额外属性来达到数组的效果;

singular:消息中包含该字段零次或一次。默认使用该字段。

repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。

        我们继续完善通讯录小项目(没看上篇文章的不要急,这里仍可以看懂),我们分析一下我们PeopleInfo结构中所需字段,我们需要一个联系人姓名,一个联系人年龄,接着我们需要联系人电话,此时我们会发现联系人电话可能不止一个,所以我们应该将电话号码设置成一个类似数组的字段,这里由于后面每个电话可能还有电话类型,如座机、移动电话等,所以这里我们将电话单独放在一个message中;具体代码如下;

syntax="proto3";
package contacts;
// 非嵌套
message Phone
{
    string phone_num = 1;
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄

    // 可嵌套
    // message Phone
    // {
    //     string PhoneNum = 1;
    // }

    repeated Phone phones = 3; // 电话
}

        注意上述中,我们展示message可以进行嵌套,甚至可以写在别的proto文件中,我们通过import 导入那个文件就可以使用了,这里就不展示了;

        我们通过protoc编译器编译这个proto文件,如下所示;

        我们查看.h文件来看一看proto帮我们生成序列化、反序列化相关方法;如下所示;

protobuf学习日记 | 认识protobuf中的类型_第2张图片

        由于环境原因,我的机器上会显示报错,但实际上没有问题的,如上图,我们可以找到一个contacts的命名空间,这个命名空间就是我们在proto文件通过package生成的命名空间;

protobuf学习日记 | 认识protobuf中的类型_第3张图片

protobuf学习日记 | 认识protobuf中的类型_第4张图片

        仔细翻阅,我们就能找到两个类,一个是Phone。一个是PeopleInfo,这两个就是由我们在proto文件中的message通过proto编译器编译来的;一般来说,都会给每个message中每个字段生成一个查询字段的接口,和一个设置字段的接口,查询字段以字段名为接口名,设置字段前带前缀set;我们继续阅读代码;

protobuf学习日记 | 认识protobuf中的类型_第5张图片

protobuf学习日记 | 认识protobuf中的类型_第6张图片

 protobuf学习日记 | 认识protobuf中的类型_第7张图片

        上面为message中三个普通字段的相关接口,我们仔细想一下,其中phone_num为嵌套message中的字段,这个嵌套message是一个数组,那么这个数组又如何的接口呢?实际上,我们也可以找到,只不过我把他单独拎出来了,如下图;

protobuf学习日记 | 认识protobuf中的类型_第8张图片

        我单独标出来了四个接口,其中第一个接口就是数组中元素个数;第二个接口是我们传入数组的一个下标,该接口返回该下标下的地址,我们这个地址我们再仔细观察发现就是Phone*的,也就是这个数组存储数据的类型,我们通过这个地址可更改对应下标下元素的值;第三个接口是查询指定下标下的元素值;第四个接口与第二个接口类似,我们不关心将值加入哪个下标下,我们就指向增加要给元素,此时调用该接口返回一个地址,我们将数据放在这个地址下即可;

        基于上面的proto代码,我们现在想实现一个写程序和一个读程序,读程序要求我们从用户输入中获取联系人的姓名,年龄。电话号等信息;然后将该信息通过protobuf进行序列化,存入文件中;我们的读程序则是从文件中读取程序,然后打印出来;代码如下;

        首先更新proto文件,我们引入一个message叫做contact,该message中只有一个字段,就是PeopleInfo,如下所示(记得重新编译proto文件哦);

// contacts.proto文件
syntax="proto3";
package contacts;
// 非嵌套
message Phone
{
    string phone_num = 1;
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄

    repeated Phone phones = 3; // 电话
}

message Contact
{
    PeopleInfo people = 1;
}

        接着我们编写write程序,如下所示;

// write.cc文件
#include 
#include 
#include 
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void AddPeopleInfo(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、获取文件中联系人相关信息,若有读进contact中
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();

    // 2、向通讯录中添加联系人
    
    AddPeopleInfo(con);

    // 3、将通讯录重新写入文件中
    fstream output(FILE_NAME, ios::out | ios::trunc | ios::binary);
    if(!output)
    {
        output.close();
        cout << "写文件时,文件打开失败, 程序退出!" << endl;
        exit(2);
    }
    else if(!con.SerializeToOstream(&output))
    {
        output.close();
        cout << "序列化失败, 程序退出!" << endl;
        exit(3);
    }
    cout << "写入成功!" << endl;
    output.close();
    return 0;
}

void AddPeopleInfo(contacts::Contact& con)
{
    cout << "--------------- 添加联系人 ---------------" << endl;
    // 新增一个联系人
    contacts::PeopleInfo* people = con.add_people();
    // 从用户输入中获取联系人姓名
    string name;
    cout << "请输入联系人姓名# ";
    getline(cin, name);
    // 设置姓名
    people->set_name(name);
    // 获取联系人年龄
    cout << "请输入联系人年龄# ";
    int age;
    cin >> age;
    cin.ignore(256, '\n'); // 清空输入缓冲区中换行
    // 设置年龄
    people->set_age(age);
    // 获取联系人号码
    for(int i = 1; ; i++)
    {
        string phone_num_str;
        cout << "联系人手机号" << i << "(只输入回车表示完成电话新增)# ";
        getline(cin, phone_num_str);
        if(phone_num_str.empty()) break;
        // 新增一个联系人号码类
        contacts::Phone* phone = people->add_phones();
        // 设置联系人号码类中的号码
        phone->set_phone_num(phone_num_str);
    }
}

        我们运行该程序,并输入数据,此时会生成一个二进制文件,其中存放的就是我们序列化后的结果;如下图所示;

protobuf学习日记 | 认识protobuf中的类型_第9张图片

        我们看不懂这个二进制序列,但是我们可以通过protoc编译器给我们提供的一个命令来解析这个二进制文件,具体用法如下图所示;

protobuf学习日记 | 认识protobuf中的类型_第10张图片

        补充一下,图上的第一个参数为我们要按照哪个message的格式进行解析,因此这个参数为指定的message,第二个参数为指定message所在文件;由于decode指令默认从标准输入获取数据,所以这里进行重定向,让这条指令从指定文件读取数据;最后结果与我们输入结果一模一样,接下来我们可以再编写一个read程序完成反序列化的工作,我们通过这个read程序进一步来验证write程序是否正确;

// read.cc文件
#include 
#include 
#include 
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void PrintContacts(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、 读取文件中数据
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();
    // 2、打印contact内容
    PrintContacts(con);
    return 0;
}

void PrintContacts(contacts::Contact& con)
{
    for(int i = 0; i < con.people_size(); i++)
    {
        cout << "------------------- 联系人" << i + 1 << " -------------------" << endl;
        // 获取联系人信息
        contacts::PeopleInfo* people = con.mutable_people(i);
        cout << "联系人姓名: " << people->name() << endl;
        cout << "联系人年龄: " << people->age() << endl;
        for(int j = 0; j < people->phones_size(); j++)
        {
            const contacts::Phone& phone = people->phones(j);
            cout << "联系人号码" << j + 1 << ": " << phone.phone_num() << endl;
        }
    }
}

        为了方便这两个文件的编译,我们编写了一份makefile文件,如下所示;

# makefile文件
.PHONY:all
all:read write

read:read.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
write:write.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:
	rm -f write read

        我们运行read程序,结果如下所示;

protobuf学习日记 | 认识protobuf中的类型_第11张图片

        为了进一步验证我们再次调用write,接着再调用read,如下图所示;

protobuf学习日记 | 认识protobuf中的类型_第12张图片

        两个联系人都打印出来了,程序完成正确;

三、特殊类型

1、枚举类型

(1)类型讲解 

        protobuf为我们提供了一种与C语言C++类似的枚举类型;用法与C语言的枚举类型类似;如下所示;

syntax="proto3";

enum PhoneType
{
    MP = 0;    // 分号分割
    TEL = 1;
}

注意:

1、第一个枚举值必须从0开始!

2、每个枚举值之间用分号分隔开

3、对于一个枚举字段来说,默认的枚举值为0

4、同级(同层)的枚举类型,各个枚举类型中的常量不能重名。(下面举例理解)

protobuf学习日记 | 认识protobuf中的类型_第13张图片

        上图中两个枚举类型的类型名不同,但是都一个MP,这种情况也属于同层;或者说同一个作用域的两个文件中,也不允许;比如我们再创建一个文件,文件中也有枚举值MP,当我们import这个文件后,同样也会编译报错;

(2)升级通讯录

        首先,我们给通讯录的每个号码增加一个号码类型,如移动电话或者固定电话;所以我们先更改proto文件,如下所示;

syntax="proto3";
package contacts;

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄

    repeated Phone phones = 3; // 电话
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        我们再次给我们的通讯录代码的write程序和read程序进行修改,write程序增加让用户输入电话类型,read程序也会相应打印出对应程序;

// write.cc文件
#include 
#include 
#include 
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void AddPeopleInfo(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、获取文件中联系人相关信息,若有读进contact中
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();

    // 2、向通讯录中添加联系人
    
    AddPeopleInfo(con);

    // 3、将通讯录重新写入文件中
    fstream output(FILE_NAME, ios::out | ios::trunc | ios::binary);
    if(!output)
    {
        output.close();
        cout << "写文件时,文件打开失败, 程序退出!" << endl;
        exit(2);
    }
    else if(!con.SerializeToOstream(&output))
    {
        output.close();
        cout << "序列化失败, 程序退出!" << endl;
        exit(3);
    }
    cout << "写入成功!" << endl;
    output.close();
    return 0;
}

void AddPeopleInfo(contacts::Contact& con)
{
    cout << "--------------- 添加联系人 ---------------" << endl;
    // 新增一个联系人
    contacts::PeopleInfo* people = con.add_people();
    // 从用户输入中获取联系人姓名
    string name;
    cout << "请输入联系人姓名# ";
    getline(cin, name);
    // 设置姓名
    people->set_name(name);
    // 获取联系人年龄
    cout << "请输入联系人年龄# ";
    int age;
    cin >> age;
    cin.ignore(256, '\n'); // 清空输入缓冲区中换行
    // 设置年龄
    people->set_age(age);
    // 获取联系人号码
    for(int i = 1; ; i++)
    {
        // 获取用户输入联系人电话
        string phone_num_str;
        cout << "联系人手机号" << i << "(只输入回车表示完成电话新增)# ";
        getline(cin, phone_num_str);
        if(phone_num_str.empty()) break;
        // 新增一个联系人号码类
        contacts::Phone* phone = people->add_phones();
        // 设置联系人号码类中的号码
        phone->set_phone_num(phone_num_str);
        // 获取用户输入电话类型
        cout << "该电话类型(1.移动电话 2.固定电话): ";
        int type = 0;
        cin >> type;
        cin.ignore(256, '\n');
        // 设置电话类型
        switch (type)
        {
        case 1:
            phone->set_phone_type(contacts::PhoneType::MP);
            break;
        case 2:
            phone->set_phone_type(contacts::PhoneType::TEL);
            break;
        default:
            break;
        }
    }
}
// read.cc文件
#include 
#include 
#include 
#include "contacts.pb.h"

#define FILE_NAME "./contact.bin"

using namespace std;

void PrintContacts(contacts::Contact& con);

int main()
{
    contacts::Contact con;
    // 1、 读取文件中数据
    fstream input(FILE_NAME, ios::in | ios::binary);
    if(!input)
    {
        input.close();
        cout << "文件不存在, 已重新创建新文件!" << endl;
    }
    else if(!con.ParseFromIstream(&input))
    {
        input.close();
        cout << "反序列化失败, 程序退出!" << endl;
        exit(1);
    }
    input.close();
    // 2、打印contact内容
    PrintContacts(con);
    return 0;
}

void PrintContacts(contacts::Contact& con)
{
    for(int i = 0; i < con.people_size(); i++)
    {
        cout << "------------------- 联系人" << i + 1 << " -------------------" << endl;
        // 获取联系人信息
        contacts::PeopleInfo* people = con.mutable_people(i);
        cout << "联系人姓名: " << people->name() << endl;
        cout << "联系人年龄: " << people->age() << endl;
        for(int j = 0; j < people->phones_size(); j++)
        {
            // 获取电话号码信息
            const contacts::Phone& phone = people->phones(j);
            cout << "联系人号码" << j + 1 << ": " << phone.phone_num();
            // 获取电话类型信息
            cout << "(" << contacts::PhoneType_Name(phone.phone_type()) << ")" << endl;
        }
    }
}

        这里补充一个接口,接口名是枚举名+Name,也就是下面的PhoneType_Name,这个接口可以将枚举名转换成字段名;如下所示;

protobuf学习日记 | 认识protobuf中的类型_第14张图片

        具体运送结果如下所示;

protobuf学习日记 | 认识protobuf中的类型_第15张图片

        我们可以看到前面张三、李四我们并未为其增加电话类型,可以还是有默认值MP;

2、Any类型

(1)类型讲解 

        字段还可以声明为 Any 类型,可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。Any 类型的字段也⽤ repeated 来修饰。

        Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include ⽬录下查找所有google 已经定义好的 .proto ⽂件。

protobuf学习日记 | 认识protobuf中的类型_第16张图片

        接下来我们通过这个字段继续完善我们的通讯录,我们给我们的通讯录增加一个地址字段,我们设置两个地址,一个是家庭地址,一个是单位地址,我们将这两个地址放到同一个message中,接着我们不想像存储手机号一样存储,而是在PeopleInfo中定义一个Any类型;

(2)升级通讯录

        我们根据上述重新再次更新我们的proto文件;如下所示;

syntax="proto3";
package contacts;

// 使用any前必须引入文件
import "google/protobuf/any.proto";

message Address
{
    string home_address = 1;
    string unit_address = 2;
}

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄
    repeated Phone phones = 3; // 电话
    google.protobuf.Any data = 4; // 地址
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        接着我们依次升级write程序与read程序文件;

// write.cc文件
    // 创建地址类
    contacts::Address address;
    string home_address;
    cout << "联系人家庭地址: ";
    getline(cin, home_address);
    // 设置地址类中的家庭地址
    address.set_home_address(home_address);
    string unit_address;
    cout << "联系人单位地址: ";
    getline(cin, unit_address);
    // 设置地址类中的单位地址
    address.set_unit_address(unit_address);
    // 将Address类型转换成Any类型
    people->mutable_data()->PackFrom(address); 

// read.cc文件
    // 判断Any字段是否被设置以及判断Any字段中类型是否为Address
    if(people->has_data() && people->data().Is())
    {
        contacts::Address address;
        // 将Any类型转换成Address类型
        people->data().UnpackTo(&address);
        if(!address.home_address().empty())
        {
            cout << "联系人家庭地址: " << address.home_address() << endl;
        }
        if(!address.unit_address().empty())
        {
            cout << "联系人单位地址: " << address.unit_address() << endl;
        }
    }

        这里涉及三个新接口,具体作用如下;

PackFrom: 可以将任意消息类型转为 Any 类型。

UnpackTo:将 Any 类型转回之前设置的任意消息类型。

Is:判断存放的消息类型是否为 typename T。

        测试运行结果如下;

protobuf学习日记 | 认识protobuf中的类型_第17张图片

protobuf学习日记 | 认识protobuf中的类型_第18张图片

3、oneof类型

(1)类型讲解

        如果消息中有很多可选字段,并且将来同时只有⼀个字段会被设置,那么就可以使⽤ oneof 加强这个⾏为,也能有节约内存的效果。

        简单来说,就是类似于C语言中的联合体,多个字段只有一个字段生效;

(2)升级通讯录

        我们给通讯录增加一个其他联系方式,如QQ或这微信,我们规定这两者之间只能选一个;我们根据这个重写proto文件,如下所示;

syntax="proto3";
package contacts;

// 使用any前必须引入文件
import "google/protobuf/any.proto";

message Address
{
    string home_address = 1;
    string unit_address = 2;
}

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄
    repeated Phone phones = 3; // 电话
    google.protobuf.Any data = 4; // 地址
    oneof other_contact
    {
        string qq = 5;
        string wechat = 6;
    }
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        oneof字段内设置的字段属于外层message字段编号范围,故注意冲突问题;我们再次修改write与read程序代码,如下所示;

// write.cc文件
// 设置其他联系字段
    int other_contact = 0;
    cout << "请选择其他联系方式(1.qq 2.wechat): ";
    cin >> other_contact;
    cin.ignore(256, '\n');
    string qq;
    string wechat;
    switch(other_contact)
    {
    case 1:
        cout << "请输入qq号: ";
        getline(cin, qq);
        people->set_qq(qq);
        break;
    case 2:
        cout << "请输入微信号: ";
        getline(cin, wechat);
        people->set_wechat(wechat);
        break;
    default:
        cout << "选择有误, 未成功设置其他联系方式" << endl;
        break;    
    }
// read.cc文件
// 判断其他联系方式字段是否被设置
    switch(people->other_contact_case())
    {
    case contacts::PeopleInfo::OtherContactCase::kQq:
        cout << "联系人qq: " << people->qq();
        break;
    case contacts::PeopleInfo::OtherContactCase::kWechat:
        cout << "联系人微信号: " << people->wechat();
        break;
    default:
        break;
    }

        

protobuf学习日记 | 认识protobuf中的类型_第19张图片

protobuf学习日记 | 认识protobuf中的类型_第20张图片

4、map类型

(1)类型讲解

        map类型就是与我们C++中map类似,就是建立一种映射;其格式为:

map map_field = N;

        我们在使用时,仍有以下几点需要特别注意;

  • key_type是除了 float bytes 以外的任意 标量 数据类型。value_type可以是任意类型。
  • map字段不可以用 repeated 来修饰。
  • map中存入的数据是 无序 的。

(2)升级通讯录

        我们给我们的通讯录最后添加一个备注信息,我们希望这个备注信息呈现一种键值对的情况;我们首先更新我们的proto文件;如下所示;

// contacts.proto文件
syntax="proto3";
package contacts;

// 使用any前必须引入文件
import "google/protobuf/any.proto";

message Address
{
    string home_address = 1;
    string unit_address = 2;
}

enum PhoneType
{
    MP = 0;   // 移动电话
    TEL = 1;  // 固定电话
}

// 非嵌套
message Phone
{
    string phone_num = 1;   // 手机号
    PhoneType phone_type = 2;   // 手机号类型
}

message PeopleInfo
{
    string name = 1;  // 姓名
    int32 age = 2;  // 年龄
    repeated Phone phones = 3; // 电话
    google.protobuf.Any data = 4; // 地址
    // 其他联系方式
    oneof other_contact
    {
        string qq = 5;
        string wechat = 6;
    }
    // 备注
    map remarks = 7;
}

message Contact
{
    repeated PeopleInfo people = 1;
}

        接着更新read和write程序,如下所示;

// write.cc文件
    // 设置备注字段
    for(int i = 0; ; i++)
    {
        string key;
        cout << "请输入备注" << i + 1 << "标题(只输入回车表示完成备注): ";
        getline(cin, key);
        if(key.empty()) break;
        string value;
        cout << "请输入备注" << i + 1 << "内容: ";
        getline(cin, value);
        people->mutable_remarks()->insert({key, value});
    }
// read.cc文件
// 设置备注字段
for(int i = 0; ; i++)
{
    string key;
    cout << "请输入备注" << i + 1 << "标题(只输入回车表示完成备注): ";
    getline(cin, key);
    if(key.empty()) break;
    string value;
    cout << "请输入备注" << i + 1 << "内容: ";
    getline(cin, value);
    people->mutable_remarks()->insert({key, value});
}

        其中我们需要主要的是对于map来说,mutable系列接口的返回值就是C++map类型的指针;如下所示;

protobuf学习日记 | 认识protobuf中的类型_第21张图片

        运行测试结果也在下面贴出;

protobuf学习日记 | 认识protobuf中的类型_第22张图片

protobuf学习日记 | 认识protobuf中的类型_第23张图片

你可能感兴趣的:(protobuf)