ProtoBuf—2

文章目录

  • 1、字段规则
  • 2、消息类型的定义和使用
  • 3、enum的使用
  • 4、any类型
  • 2、oneof类型
  • 3、map类型

1、字段规则

消息的字段可以用以下几种规则来修饰:

  • singular:消息中可以包含该字段零次或一次(不超过一次)。proto3语法中,字段默认使用该规则
  • repeated:消息中可以包含该字段任意多次(包括0次),其中重复值的顺序会保留。可以理解为定义了一个数组

更新contacts.proto,PeopleInfo 消息中新增 phone 字段,表示一个联系人有多个号码,可将其设置为repeated,写法如下:

//首行:语法指定行
syntax = "proto3";
package contacts2;

message Phone
{
    string number = 1;
}

//定义联系人message
message PeopleInfo 
{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    repeated Phone phone = 3;
}

message 是可以嵌套的,并且嵌套层数没有限制

//定义联系人message
message PeopleInfo 
{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    
    message Phone
    {
        string number = 1;
    }
    repeated Phone phone = 3;
}

如果Phone定义在其他proto文件中,则应该为

#phone.proto
// 首行:语法指定行
syntax = "proto3";
package phone;

message Phone
{
    string number = 1;
}

#contacts.proto
//首行:语法指定行
syntax = "proto3";
package contacts2;
import "phone.proto";

//定义联系人message
message PeopleInfo 
{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    repeated phone.Phone phone = 3;
}

2、消息类型的定义和使用

将通讯录序列化,并写入文件中

#include 
#include 
#include "contacts.pb.h"
using namespace std;

void AddPeopleInfo(contacts2::PeopleInfo *people)
{
    cout << "新增联系人" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');

    for (int i = 0;; ++i)
    {
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty())
        {
            break;
        }
        contacts2::PeopleInfo_Phone *phone = people->add_phone();
        phone->set_number(number);
    }

    cout << "添加联系人成功" << endl;
}

int main()
{
    contacts2::Contacts contacts;

    // 读取本地已经存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if (!input)
    {
        cout << "contacts.bin not find, create new file!" << endl;
    }
    else if (contacts.ParseFromIstream(&input))
    {
        cerr << "parse error" << endl;
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddPeopleInfo(contacts.add_contacts());

    // 将通讯录写入本地文件
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output))
    {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }
    return 0;
}

编译:

g++ -o write write.cc contacts.pb.cc -std=c++11 -lprotobuf

运行:
ProtoBuf—2_第1张图片

从文件中将通讯录解析出来,并进行打印

#include 
#include 
#include "contacts.pb.h"
using namespace std;

void PrintContacts(contacts2::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
        cout << "联系人" << i + 1 << endl;
        const ::contacts2::PeopleInfo &people = contacts.contacts(i);
        cout << "姓名" << people.name() << endl;
        cout << "年龄" << people.age() << endl;
        for (int j = 0; j < people.phone_size(); ++j)
        {
            const ::contacts2::PeopleInfo_Phone &phone = people.phone(j);
            cout << "电话" << j + 1 << ":" << phone.number() << endl;
        }
    }
}

int main()
{
    contacts2::Contacts contacts;
    // 读取本地已存在的文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if (!contacts.ParseFromIstream(&input))
    {
        cerr << "parse error" << endl;
        input.close();
        return -1;
    }

    // 打印通讯录列表
    PrintContacts(contacts);
    return 0;
}

编译:

g++ -o read read.cc contacts.pb.cc -std=c++11 -lprotobuf

运行:
ProtoBuf—2_第2张图片

因为protobuf是以二进制进行序列化,存储到文件中,如果需要查看,可以采用命令:

protoc --decode=contacts2.Contacts contacts.proto < contacts.bin

contacts2.Contacts:表示哪个包中的message
contacts.proto:表示对应的文件
contacts.bin:表示对应的二进制文件

结果:
ProtoBuf—2_第3张图片

可以使用命令 protoc -h 查看其他参数选项

3、enum的使用

当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值,就比如通讯录中的电话类型有移动电话和固定电话,这时候可以通过枚举实现。

enum PhoneType
{
    MP = 0;  //proto3版本中,首成员必须为0,成员不应有相同的值   
    TEL = 1;
}

枚举类型不仅可以定义在protobuf文件的最外层,也可以定义在message内部

message phone
{
    enum PhoneType
    {
        MP = 0;  //proto3版本中,首成员必须为0,成员不应有相同的值   
        TEL = 1;
    } 
}

如果在同一个protobuf文件中,不同enum有相同的字段名,在编译时则会报错,例如:

syntax = "proto3";

enum PhoneType
{
    MP = 0;  //proto3版本中,首成员必须为0,成员不应有相同的值   
    TEL = 1;
}

enum PhoneTypeCopy
{
    MP = 0;  //proto3版本中,首成员必须为0,成员不应有相同的值   
    TEL = 1;
}

在这里插入图 片描述

如果两个protobuf文件使用了相同的枚举常量名称,编译时也会报错:

#test_enum.proto
syntax = "proto3";
import "test_enum2.proto";

enum PhoneType
{
    MP = 0;  //proto3版本中,首成员必须为0,成员不应有相同的值   
    TEL = 1;
}

#test_enum2.proto
syntax = "proto3";

enum PhoneTypeCopy
{
    MP = 0;  //proto3版本中,首成员必须为0,成员不应有相同的值   
}

在这里插入图片描述

解决方法:给其中任意一个文件添加package,或者两个文件都添加package

对通讯录进行升级

//首行:语法指定行
syntax = "proto3";
package contacts2;


//定义联系人 message
message PeopleInfo 
{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    
    message Phone
    {
        string number = 1;
        enum PhoneType
        {
            MP = 0; //移动电话
            TEL = 1;//固定电话
        }
        PhoneType type = 2;
    }
    repeated Phone phone = 3;   //电话信息
}

枚举的默认值是编号为0的值

更新对应的通讯录

//write.cc
#include 
#include 
#include "contacts.pb.h"
using namespace std;

void AddPeopleInfo(contacts2::PeopleInfo *people)
{
	//......
	//和前面一致
    for (int i = 0;; ++i)
    {
    	//......
    	//和前面一致
        phone->set_number(number);

        cout << "请输入该电话类型(1.移动电话  2.固定电话): ";
        int type;
        cin >> type;
        cin.ignore(256, '\n');
        switch (type)
        {
        case 1:
            phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
            break;
        default:
            cout << "选择有误" << endl;
            break;
        }
    }

    cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
		//......
		//和前面一致
        for (int j = 0; j < people.phone_size(); ++j)
        {
            const ::contacts2::PeopleInfo_Phone &phone = people.phone(j);
            cout << "电话" << j + 1 << ":" << phone.number();
            // 联系人电话1:123456789 (MP)
            cout << "     (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }
    }
}

4、any类型

在 Protocol Buffers(Protobuf)中,Any 类型是一种特殊的消息类型,用于存储任意类型的数据。它提供了一种灵活的方式来在消息中嵌入其他类型的消息,而无需提前知道具体的消息结构。
可以将Any类型理解为泛型类型

使用Any类型,需要导入google/protobuf/any.proto
给PeopleInfo添加Address

对contacts.proto进行更新

//首行:语法指定行
syntax = "proto3";
package contacts2;

import "google/protobuf/any.proto";


message Address
{
    string home_address = 1; //家庭住址
    string unit_address = 2; //单位地址
}

//定义联系人 message
message PeopleInfo 
{
	//......
	//和前面一致
    google.protobuf.Any data = 4;
}

更新对应的通讯录

//write.cc
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
	//......
	//和前面一致
    contacts2::Address address;
    cout<<"请输入联系人的家庭地址"<<endl;
    string home_address;
    getline(cin, home_address);
    address.set_home_address(home_address);
    cout<<"请输入联系人的单位地址"<<endl;
    string unit_address;
    getline(cin, unit_address);
    address.set_unit_address(unit_address);
    //将Address对象转换为Any
    if(!people->mutable_data()->PackFrom(address))
    {
        cerr << "转换失败" << endl;
    }

    cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
    	//......
    	//和前面一致
        // 判断有没有为data进行设值,并且为Address类型
        if (people.has_data() && people.data().Is<contacts2::Address>())
        {
            contacts2::Address address;
            // 将people中的data转换为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;
            }
        }
    }
}

2、oneof类型

在 Protocol Buffers(Protobuf)中,oneof 是一种特殊的字段声明,它允许在一个消息中定义多个互斥的字段,只能同时选择其中一个进行赋值。

使用 oneof 类型可以有效地在消息结构中表示多个可能的字段,但只允许其中一个字段被设置为非默认值。这在某些情况下可以帮助简化消息的定义和使用,并节省存储空间。

比如在通讯录中,除了电话号码之外,还需要保留一种通信方式,比如微信和qq,但两者只能选一种,此时就能使用 oneof 类型

对contacts.proto进行修改

message PeopleInfo 
{
	//......
	//和前面一致
    oneof other_contact
    {
        //repeated string qq = 5; 不能使用repeated,否则编译时会报错
        string qq = 5;
        string wechat = 6;
    }
    
}

注意:oneof 中的字段不能使用 repeated 进行修饰

更改对应的通讯录

//write.cc
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
	//......
	//和前面一致
    cout << "请选择要添加的其他联系方式(1.qq号  2.微信号):";
    int other_contact;
    cin >> other_contact;
    cin.ignore(256, '\n');
    if (1 == other_contact)
    {
        cout << "请输入联系人qq号:";
        string qq;
        getline(cin, qq);
        people->set_qq(qq);
    }
    else if (2 == other_contact)
    {
        cout << "请输入联系人微信号:";
        string wechat;
        getline(cin, wechat);
        people->set_wechat(wechat);
    }
    else
    {
        cout << "选择有误,未成功设置其他联系方式!" << endl;
    }

    cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
    	//......
    	//和前面一致
        switch (people.other_contact_case())
        {
        case contacts2::PeopleInfo::OtherContactCase::kQq:
            cout << "联系人qq: " << people.qq() << endl;
            break;
        case contacts2::PeopleInfo::OtherContactCase::kWechat:
            cout << "联系人微信: " << people.wechat() << endl;
            break;
        default:
            break;
        }
    }
}

3、map类型

在 Protocol Buffers(Protobuf)中,map用于表示键值对的集合。它提供了一种方便的方式来在消息中存储和传输映射关系的数据。

在 Protobuf 中,Map 类型需要指定键(key)和值(value)的类型。键必须是基本数据类型(例如 int32、string)或枚举类型,而值可以是任意的消息类型。map中的数据是无序的

并且map字段不能用 repeated 修饰

对contacts.proto进行修改,给PeopleInfo添加备注信息

message PeopleInfo 
{
	//......
	//和前面一致
    map<string, string> remark = 7; //备注信息
}

更改对应的通讯录

//write.cc
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
	//......
	//和前面一致
    for(int i = 0;; ++i)
    {
        cout<<"请输入备注"<<i+1<<"标题(只输入回车完成备注新增):";
        string remark_key;
        getline(cin, remark_key);
        if(remark_key.empty())
        {
            break;
        }
        cout<<"请输入备注"<<i+1<<"内容:";
        string remark_value;
        getline(cin, remark_value);
        people->mutable_remark()->insert({remark_key, remark_value});
    }

    cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
    	//......
    	//和前面一致
        if (people.remark_size())
        {
            cout << "备注信息:" << endl;
        }
        for (auto it = people.remark().cbegin(); it != people.remark().end(); ++it)
        {
            cout << "  " << it->first << ":" << it->second << endl;
        }
    }
}

你可能感兴趣的:(ProtoBuf学习,protobuf,序列化,反序列化)