【ProtoBuf】语法类型详解

news2024/11/28 0:38:29

目录

字段规则

消息类型的定义与使用

嵌套定义

非嵌套定义

同文件中消息类型作为字段类型使用

其他文件.proto文件的消息使用

enum类型

Any类型

oneof类型

map类型

默认值

更新消息

保留字段

未知字段

前后兼容

选项option


字段规则

上篇文章我们提到消息是由一个一个的字段组成,每个字段也应该有不同的属性;因此消息的字段可以⽤下⾯⼏种规则来修饰:

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

示例:

更新我们的通讯录,在PeopleInfo 消息中新增 phone_numbers 字段,表⽰⼀个联系⼈有多个
号码,可将其设置为 repeated,写法如下:

syntax = "proto3";
package contacts;

message PeopleInfo {
    string name = 1;
    int32 age = 2;
    repeated string phone_numbers = 3;
}

消息类型的定义与使用

定义

在单个.protobuf文件中可以定义多个消息体,且支持定义嵌套类型的消息(任意多层)。每个消息体中的字段编号可以重复。

示例:

我们可以将 phone_number 提取出来,单独成为⼀个消息;

嵌套定义

syntax = "proto3";
package contacts;
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    //嵌套定义
    message Phone{
        string number=1;
    }
}

非嵌套定义

//---------非嵌套定义-----------
syntax = "proto3"

package contacts;
massage Phone{
    string number=1;
}
massage PeopleInfo{
    string name=1;
    int32 age = 2;
}

使用

同文件中消息类型作为字段类型使用

syntax = "proto3";
package contacts;
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    //嵌套定义
    message Phone{
        string number=1;
    }
    repeated Phone phone =3;
}

其他文件.proto文件的消息使用

例如 Phone 消息定义在 phone.proto ⽂件中:

syntax="proto3";
package phone;

message Phone{
    string number=1;
}

在本文件中的PeopleInfo使用Phone消息:

syntax = "proto3";
package contacts;
import "phone.proto";
//使用import将phone.proto文件导入进来
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    //引入的文件声明了package,使用消息时,需要用"命名空间.消息类型"格式
    repeated phone.Phone phone=3;
}

注:在 proto3 ⽂件中可以导⼊ proto2 消息类型并使⽤它们,反之亦然。


更新通讯录版本

上面只是创建好了一个联系人的信息,但是一个通讯录不可能只包含一个人;因此我们要创建一个通讯录消息,里面需要能够包含多个联系人信息,那么就需要repeated来进行修饰

yntax = "proto3";
package contacts;
message PeopleInfo {
    string name = 1;//姓名
    int32 age = 2;//年龄
    //嵌套定义
    message Phone{
        string number=1;
    }
    repeated Phone phone =3;//电话号码
}
//通讯录
message Contacts{
    repeated PeopleInfo contacts=1;//一个通讯录中可以包含多个联系人信息
}

使用命令进行编译后,生成的contacts.pb.h contacts.pb.cc会将之前的两个文件覆盖掉;

仔细阅读生成的.h文件我们会发现:

  • 每个字段都有⼀个 clear_ ⽅法,可以将字段重新设置回 empty 状态。
  • 每个字段都有设置和获取的⽅法, 获取⽅法的⽅法名称与⼩写字段名称完全相同。但如果是消息类型的字段,其设置⽅法为 mutable_ ⽅法,返回值为消息类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改。
  • 对于使⽤ repeated 修饰的字段,也就是数组类型,pb 为我们提供了 add_ ⽅法来新增⼀个值,并且提供了 _size ⽅法来判断数组存放元素的个数
#include <iostream>
#include <string>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
int main()
{
    //先创建一个通讯录
    Contacts contacts;
    //通讯录类中包含一个个的联系人类
    //在通讯录中创建一个联系人
    PeopleInfo *person = contacts.add_contacts();
    string name;
    cout << "请输入联系人姓名: ";
    getline(cin, name);
    //调用联系人类的set_方法设置联系人类的属性
    person->set_name(name);
    int age;
    cout << "请输入联系人年龄:";
    cin >> age;
    person->set_age(age);
    //忽略回车
    cin.ignore(256, '\n');
    
    for (int i = 0;; i++)
    {
        cout << "请输入联系人电话" << i << "(输入回车完成电话新增或空行结束):";
        string number;
        getline(cin, number);
        if (number.empty())
        {
            break;
        }
        person->add_phone()->set_number(number);
    }
    cout << "添加联系人成功" << endl;
        
    //可以把整个通讯录看成一个数组,数组中的每个元素是联系人
    for (auto &person_info : contacts.contacts())
    {
        cout << person_info.name() << endl;
        cout << person_info.age() << endl;
        for (const auto &phone : person_info.phone())
        {
            cout << "电话: " << phone.number() << endl;
        }
    }
    return 0;
}

enum类型

语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为:

  • 枚举类型名称:使⽤驼峰命名法,⾸字⺟⼤写。 例如: MyEnum
  • 常量值名称:全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0;

我们可以定义一个PhoneType的枚举类型,定义如下:

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

要注意枚举类型的定义有以下⼏种规则:

  1. 0 值常量必须存在,且要作为第⼀个元素。这是为了与 proto2 的语义兼容:第⼀个元素作为默认值,且值为 0。
  2. 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
  3. 枚举的常量值在 32 位整数的范围内。但因负值⽆效因⽽不建议使⽤(与编码规则有关)

定义枚举时注意:

将两个 ‘具有相同枚举值名称’ 的枚举类型放在单个 .proto ⽂件下测试时,编译后会报错:某某某常
量已经被定义!所以这⾥要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
  • 单个 .proto ⽂件下,最外层枚举类型和嵌套枚举类型,不算同级。
  • 多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都未声明 package,每个 proto ⽂件中的枚举类型都在最外层,算同级。
  • 多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都声明了 package,不算同级。
//同级枚举类型包含相同的枚举值名称

enum PhoneType{
    MP=0;
    TEL=1;
}
enum PhoneType{
    MP=0; //编译proto文件后会报错,因为再同级一定定义了
}
//正确用法


//不同级枚举类型包含相同枚举值名称
enum PhoneType{
    MP=0;
}
message{
    string number=1;
    enum PhoneType{
        MP=0;
        TEL=1;
    }
}
// 多⽂件下都未声明package
// phone1.proto
import "phone1.proto"
enum PhoneType {
    MP = 0;
    // 移动电话
    // 编译后报错:MP 已经定义
    TEL = 1;
    // 固定电话
}
// phone2.proto

enum PhoneTypeCopy {
    MP = 0;
    // 移动电话
}

//因为两个不相同的文件在同一个目录下,也就相当于再同一级下
//多⽂件下都声明了package
// phone1.proto

import "phone1.proto"
package phone1;

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

// phone2.proto

package phone2;
    enum PhoneTypeCopy {
    MP = 0;
    // 移动电话
}

我们可以给我们的通讯录添加枚举字段并使用,更新内容如下:

syntax = "proto3";
package contacts;
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;//电话号码
}
//通讯录
message Contacts{
    repeated PeopleInfo contacts=1;//一个通讯录中可以包含多个联系人信息
}

编译后生成的contacts.pb.h也会被新的方法所覆盖:

新生成的PeopleInfo_Phone_PhoneType 枚举类

上述的代码中:

  • 对于在.proto⽂件中定义的枚举类型,编译⽣成的代码中会含有与之对应的枚举类型、校验枚举值是否有效的⽅法 _IsValid、以及获取枚举值名称的⽅法 _Name。
  • 对于使⽤了枚举类型的字段,包含设置和获取字段的⽅法,已经清空字段的⽅法clear_。

        //设置电话类型
        cout<<"请选择电话类型(1、移动电话 2、固定电话): ";
        int type;
        cin>>type;
        cin.ignore(256,'\n');
        switch (type)
        {
        case 1:
            phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
        default:
            cout<<"非法选择,使用默认值!"<<endl;
            break;
        }
    }
    cout<<"添加联系人成功"<<endl;
    for(auto person_info:contacts.contacts())
    {
        cout<<person_info.name()<<endl
            <<person_info.age()<<endl;
        for(auto phone:person_info.phone())
        {
            cout<<phone.number()<<endl;
            cout<<PeopleInfo_Phone_PhoneType_Name(phone.type())<<endl;
        }
    }

Any类型

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

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

我们可以给联系人新增地址信息,使用Any类型的字段来存储地址信息。

syntax = "proto3";
package contacts;
import "google/protobuf/any.proto";//引入any.proto文件
message Address{
    string home_address=1;//家庭地址
    string unit_address=2;//单位地址
}
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;//电话号码
    google.protobuf.Any data = 4;
}
message Contacts {
    repeated PeopleInfo contacts = 1;
}

编译重新生成contacts.pb.h文件,会生成新的Address类和更新PeoleInfo类

上述的代码中,对于 Any 类型字段:

设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法可以使⽤ mutable_ ⽅
法,返回值为Any类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏
修改。


    Address address;
    cout<<"请输入联系人的家庭地址: ";
    string home_address;
    getline(cin,home_address);
    address.set_home_address(home_address);
    cout<<"请输入联系人的单位地址";
    string unit_address;
    getline(cin,unit_address);
    address.set_unit_address(unit_address);

    //person里面有一份开辟好的空间
    //使用mutable_data()进行调用
    //data是Any类型 address是message类型 因此需要类型抓换
    //使用PackFrom进行转换
    google::protobuf::Any* data = person->mutable_data();
    data->PackFrom(address);
    cout<<"添加联系人成功"<<endl;
    for(auto person_info:contacts.contacts())
    {
        cout<<person_info.name()<<endl
            <<person_info.age()<<endl;
        for(auto phone:person_info.phone())
        {
            cout<<phone.number()<<" ";
            cout<<PeopleInfo_Phone_PhoneType_Name(phone.type())<<endl;
        }
        //先判断Any类型是否有数据,再判断它的数据是否为Address
        if(person->has_data()&&person->data().Is<Address>())
        {
            //person里面是Any类型,我们想要的是Addressl类型
            //因此需要类型转换
            Address address;
            person->data().UnpackTo(&address);
            if(!address.home_address().empty())
            {
                cout<<"家庭住址: "<<address.home_address()<<endl;
            }
            if(!address.unit_address().empty())
            {
                cout<<"单位地址: "<<address.unit_address()<<endl; 
            }

        }
    }

oneof类型

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

给通讯录增加一个联系人的其他方式,比如QQ或者WeChat二选一,我们就可以使用oneof字段来加强多选一这个行为。

syntax = "proto3";
package contacts;
import "google/protobuf/any.proto";//引入any.proto文件
message Address{
    string home_address=1;//家庭地址
    string unit_address=2;//单位地址
}
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;//电话号码
    google.protobuf.Any data = 4;
    oneof other_contace{
        string qq=5;
        stirng wechat=6;
    }
}
message Contacts {
    repeated PeopleInfo contacts = 1;
}

注意:

  • 可选字段中的字段编号,不能与⾮可选字段的编号冲突。
  • 不能在 oneof 中使⽤ repeated 字段。
  • 将来在设置 oneof 字段中值时,如果将 oneof 中的字段设置多个,那么只会保留最后⼀次设置的成员,之前设置的 oneof 成员会⾃动清除。

将proto文件编译后,contacts.pb.h会更新PropleInfo类

上述的代码中,对于 oneof 字段:

  • 会将 oneof 中的多个字段定义为⼀个枚举类型。
  • 设置和获取:对 oneof 内的字段进⾏常规的设置和获取即可,但要注意只能设置⼀个。如果设置多个,那么只会保留最后⼀次设置的成员。
  • 清空oneof字段:clear_ ⽅法
  • 获取当前设置了哪个字段:_case ⽅法

    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);
        person->set_qq(qq);
    }
    else if (2 == other_contact)
    {
        cout << "请输入微信号: ";
        string WeChat;
        getline(cin, WeChat);
        person->set_wechat(WeChat);
    }
    else
    {
        cout << "非法选择设置失败" << endl;
    }
    cout << "添加联系人成功" << endl;
    for (auto person_info : contacts.contacts())
    {
        cout << person_info.name() << endl
             << person_info.age() << endl;
        for (auto phone : person_info.phone())
        {
            cout << phone.number() << " ";
            cout << PeopleInfo_Phone_PhoneType_Name(phone.type()) << endl;
        }
        // 先判断Any类型是否有数据,再判断它的数据是否为Address
        if (person->has_data() && person->data().Is<Address>())
        {
            // person里面是Any类型,我们想要的是Addressl类型
            // 因此需要类型转换
            Address address;
            person->data().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                cout << "家庭住址: " << address.home_address() << endl;
            }
            if (!address.unit_address().empty())
            {
                cout << "单位地址: " << address.unit_address() << endl;
            }
        }
        // 我们可以选择调用函数判断qq/wechat号是否存在
        //  person->has_qq();
        //  person->has_wechat();

        // 但是这样做太麻烦了
        switch (person->other_contace_case())
        {
        case PeopleInfo::OtherContaceCase::kQq:
            cout << "QQ号: " << person->qq() << endl;
            break;
        case PeopleInfo::OtherContaceCase::kWechat:
            cout << "WeChat号: " << person->wechat() << endl;
            break;
        case PeopleInfo::OtherContaceCase::OTHER_CONTACE_NOT_SET:
            break;
        }
    }

map类型

语法⽀持创建⼀个关联映射字段,也就是可以使⽤ map 类型去声明字段类型,格式为:
map<key_type, value_type> map_field = N;

要注意的是:

  • key_type 是除了 float 和 bytes 类型以外的任意标量类型。 value_type 可以是任意类型。
  • map 字段不可以⽤ repeated 修饰
  • map 中存⼊的元素是⽆序的

继续对通讯录进行更新,田间联系人备注,我们可以使⽤ map 类型的字段来存储备注信息。

syntax = "proto3";
package contacts;
import "google/protobuf/any.proto";//引入any.proto文件
message Address{
    string home_address=1;//家庭地址
    string unit_address=2;//单位地址
}
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;//电话号码
    google.protobuf.Any data = 4;
    oneof other_contace{
        string qq=5;
        string wechat=6;
    }
    map<string,string> remark=7;//备注
}
message Contacts {
    repeated PeopleInfo contacts = 1;
}

将proto文件编译后,contacts.pb.h会更新PropleInfo类

上述的代码中,对于Map类型的字段:
• 清空map: clear_ ⽅法
• 设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法为 mutable_ ⽅法,返回
值为Map类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改


    for(int i=1;;i++)
    {
        cout<<"请输入备注"<<i<<"标题(只输入回车完成备注新增): ";
        string remark_key;
        getline(cin,remark_key);
        if(remark_key.empty())
        {
            break;
        }

        cout<<"请输入备注"<<i<<"内容: ";
        string remark_value;
        getline(cin,remark_value);
        person->mutable_remark()->insert({remark_key,remark_value});
    }

    cout << "添加联系人成功" << endl;
    for (auto person_info : contacts.contacts())
    {
        cout << person_info.name() << endl
             << person_info.age() << endl;
        for (auto phone : person_info.phone())
        {
            cout << phone.number() << " ";
            cout << PeopleInfo_Phone_PhoneType_Name(phone.type()) << endl;
        }
        // 先判断Any类型是否有数据,再判断它的数据是否为Address
        if (person->has_data() && person->data().Is<Address>())
        {
            // person里面是Any类型,我们想要的是Addressl类型
            // 因此需要类型转换
            Address address;
            person->data().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                cout << "家庭住址: " << address.home_address() << endl;
            }
            if (!address.unit_address().empty())
            {
                cout << "单位地址: " << address.unit_address() << endl;
            }
        }
        // 我们可以选择调用函数判断qq/wechat号是否存在
        //  person->has_qq();
        //  person->has_wechat();

        // 但是这样做太麻烦了
        switch (person->other_contace_case())
        {
        case PeopleInfo::OtherContaceCase::kQq:
            cout << "QQ号: " << person->qq() << endl;
            break;
        case PeopleInfo::OtherContaceCase::kWechat:
            cout << "WeChat号: " << person->wechat() << endl;
            break;
        case PeopleInfo::OtherContaceCase::OTHER_CONTACE_NOT_SET:
            break;
        }
        if(person->remark_size())
        {
            cout<<"备注信息: "<<endl;
        }
        for(auto it =person->remark().cbegin();it!=person->remark().end();it++)
        {
            cout<<" "<<it->first<<" "<<it->second<<endl;
            
        }
    }

完整的类型使用

我们可以将添加联系人和读写联系人分开操作,可以序列化写到一个文件里面;然后反序列化读出来;为了方便演示每个类型的接口我就写在了一个文件中给大家演示;

#include "contacts.pb.h"
using namespace std;
using namespace contacts;
int main()
{
    Contacts contacts;
    PeopleInfo *person = contacts.add_contacts();
    cout << "------------新增联系人-----------" << endl;
    cout << "请输入联系人姓名: ";
    string name;
    getline(cin, name);
    person->set_name(name);
    cout << "请输入联系人年龄: ";
    int age;
    cin >> age;
    cin.ignore(256, '\n');
    person->set_age(age);
    for (int i = 1;; i++)
    {
        cout << "请输入联系人电话" << i << "(只输入回车完成电话更新)";
        string number;
        getline(cin, number);
        if (number.empty())
        {
            break;
        }
        PeopleInfo_Phone *phone = person->add_phone();
        phone->set_number(number);
        // 设置电话类型
        cout << "请选择电话类型(1、移动电话 2、固定电话): ";
        int type;
        cin >> type;
        cin.ignore(256, '\n');
        switch (type)
        {
        case 1:
            phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
            break;
        default:
            cout << "非法选择,使用默认值!" << endl;
            break;
        }
    }
    Address address;
    cout << "请输入联系人的家庭地址: ";
    string home_address;
    getline(cin, home_address);
    address.set_home_address(home_address);
    cout << "请输入联系人的单位地址: ";
    string unit_address;
    getline(cin, unit_address);
    address.set_unit_address(unit_address);

    // person里面有一份开辟好的空间
    // 使用mutable_data()进行调用
    // data是Any类型 address是message类型 因此需要类型抓换
    // 使用PackFrom进行转换
    google::protobuf::Any *data = person->mutable_data();
    data->PackFrom(address);
    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);
        person->set_qq(qq);
    }
    else if (2 == other_contact)
    {
        cout << "请输入微信号: ";
        string WeChat;
        getline(cin, WeChat);
        person->set_wechat(WeChat);
    }
    else
    {
        cout << "非法选择设置失败" << endl;
    }
    for(int i=1;;i++)
    {
        cout<<"请输入备注"<<i<<"标题(只输入回车完成备注新增): ";
        string remark_key;
        getline(cin,remark_key);
        if(remark_key.empty())
        {
            break;
        }

        cout<<"请输入备注"<<i<<"内容: ";
        string remark_value;
        getline(cin,remark_value);
        person->mutable_remark()->insert({remark_key,remark_value});
    }
    cout << "添加联系人成功" << endl;



    //序列化
    string str;
    contacts.SerializeToString(&str);
    //反序列化
    Contacts cont;
    cont.ParseFromString(str);


    
    for (auto person_info : cont.contacts())
    {
        cout << person_info.name() << endl
             << person_info.age() << endl;
        for (auto phone : person_info.phone())
        {
            cout << phone.number() << " ";
            cout << PeopleInfo_Phone_PhoneType_Name(phone.type()) << endl;
        }
        // 先判断Any类型是否有数据,再判断它的数据是否为Address
        if (person->has_data() && person->data().Is<Address>())
        {
            // person里面是Any类型,我们想要的是Addressl类型
            // 因此需要类型转换
            Address address;
            person->data().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                cout << "家庭住址: " << address.home_address() << endl;
            }
            if (!address.unit_address().empty())
            {
                cout << "单位地址: " << address.unit_address() << endl;
            }
        }
        // 我们可以选择调用函数判断qq/wechat号是否存在
        //  person->has_qq();
        //  person->has_wechat();

        // 但是这样做太麻烦了
        switch (person->other_contace_case())
        {
        case PeopleInfo::OtherContaceCase::kQq:
            cout << "QQ号: " << person->qq() << endl;
            break;
        case PeopleInfo::OtherContaceCase::kWechat:
            cout << "WeChat号: " << person->wechat() << endl;
            break;
        case PeopleInfo::OtherContaceCase::OTHER_CONTACE_NOT_SET:
            break;
        }
        if(person->remark_size())
        {
            cout<<"备注信息: "<<endl;
        }
        for(auto it =person->remark().cbegin();it!=person->remark().end();it++)
        {
            cout<<" "<<it->first<<" "<<it->second<<endl;

        }
    }
    return 0;
}

默认值

反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为 false。
  • 对于数值类型,默认值为 0。
  • 对于枚举,默认值是第⼀个定义的枚举值, 必须为 0。
  • 对于消息字段,未设置该字段。它的取值是依赖于语⾔。
  • 对于设置了 repeated 的字段的默认值是空的( 通常是相应语⾔的⼀个空列表 )。
  • 对于 消息字段 、 oneof字段 和 any字段 ,C++ 和 Java 语⾔中都有 has_ 方法来检测当前字段是否被设置

更新消息

  • 禁⽌修改任何已有字段的字段编号。
  • 若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。
  • int32, uint32, int64, uint64 和 bool 是完全兼容的。可以从这些类型中的⼀个改为另⼀个,⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与 C++ ⼀致的处理⽅案(例如,若将 64 位整数当做 32 位进⾏读取,它将被截断为 32 位)。
  • sint32 和 sint64 相互兼容但不与其他的整型兼容。
  • string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。
  • bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。
  • fixed32 与 sfixed32 兼容, fixed64 与 sfixed64兼容。

enum

与 int32,uint32, int64 和 uint64 兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语⾔采⽤不同的处理⽅案:例如,未识别的 proto3 枚举类型会被保存在消息中,但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。

oneof

  • 将⼀个单独的值更改为 新 oneof 类型成员之⼀是安全和⼆进制兼容的。
  • 若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可⾏的。
  • 将任何字段移⼊已存在的 oneof 类型是不安全的

保留字段

如果通过 删除 或 注释掉 字段来更新消息类型,未来的⽤⼾在添加新字段时,有可能会使⽤以前已经存在,但已经被删除或注释掉的字段编号。将来使⽤该 .proto 的旧版本时的程序会引发很多问题:数据损坏、隐私错误等等。

确保不会发⽣这种情况的⼀种⽅法是:使⽤ reserved 将指定字段的编号或名称设置为保留项 。当
我们再使⽤这些编号或名称时,protocol buffer 的编译器将会警告这些编号或名称不可⽤。举个例
⼦:

message Message {
    // 设置保留项
    reserved 100, 101, 200 to 299;
    reserved "field3", "field4";
    // 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
    // reserved 102, "field5";
    // 设置保留项之后,下⾯代码会告警
    int32 field1 = 100; //告警:Field 'field1' uses reserved number 100
    int32 field2 = 101; //告警:Field 'field2' uses reserved number 101
    int32 field3 = 102; //告警:Field name 'field3' is reserved
    int32 field4 = 103; //告警:Field name 'field4' is reserved
}

未知字段

  • 未知字段:解析结构良好的 protocol buffer 已序列化数据中的未识别字段的表⽰⽅式。例如,当旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段。
  • 本来,proto3 在解析消息时总是会丢弃未知字段,但在 3.5 版本中重新引⼊了对未知字段的保留机制。所以在 3.5 或更⾼版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果中。

前后兼容

  • 向前兼容:⽼模块能够正确识别新模块⽣成或发出的协议。
  • 向后兼容:新模块也能够正确识别⽼模块⽣成或发出的协议。
  • 前后兼容的作⽤:当我们维护⼀个很庞⼤的分布式系统时,由于你⽆法同时 升级所有 模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。

选项option

proto ⽂件中可以声明许多选项,使⽤ option 标注。选项能影响 proto 编译器的某些处理⽅式。

常用选项列表

ptimize_for : 该选项为⽂件选项,可以设置 protoc 编译器的优化级别,分别为 SPEED 、
CODE_SIZE 、 LITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译 .proto ⽂件后⽣
成的代码内容不同。

  • SPEED : protoc 编译器将⽣成的代码是⾼度优化的,代码运⾏效率⾼,但是由此⽣成的代码编译后会占⽤更多的空间。 SPEED 是默认选项。
  • CODE_SIZE : proto 编译器将⽣成最少的类,会占⽤更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运⾏效率较低。这种⽅式适合⽤在包含⼤量的.proto⽂件,但并不盲⽬追求速度的应⽤中。
  • LITE_RUNTIME : ⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供 encoding+序列化 功能,所以我们在链接 BP 库时仅需链接libprotobuf-lite,⽽⾮libprotobuf。这种模式通常⽤于资源有限的平台,例如移动⼿机平台中。
option optimize_for = LITE_RUNTIME;

 allow_alias : 允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。举个例子:

enum PhoneType {
    option allow_alias = true;
    MP = 0;
    TEL = 1;
    LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}

今天对ProtoBuf语法详解的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1985258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】字符串相关类的底层原理(014)

目录 ♦️字符串存储的内存原理 &#x1f38f;字面量字符串对象 &#x1f38f;构造函数声明字符串对象 ♦️比较的是什么 &#x1f38f;基本数据类型 &#x1f38f;引用数据类型 ♦️字符串拼接的底层原理 &#x1f38f;拼接的时候没有变量参与 &#x1f38f;拼接的时候…

3GPP 4G 5G 主要协议

4G LTE的协议主要是36 series 5G NR的协议主要是38 series

别被割韭菜了!这些才是好评率90%以上的低代码开发平台!

别找了~别找了~ 翻遍全网&#xff0c;好评率高达 90% 以上的低代码开发平台都在这了&#xff01; 想要“提高人员效率&#xff0c;降低开发成本”的朋友&#xff0c;看这一篇就够了&#xff01; 7100字干货&#xff0c;带你“沉浸式”了解国内 7 家领先的低代码开发平台&…

IPsec VPN综合实验

一、实验目的及拓扑 实验目的&#xff1a; 1、在总部通过防火墙建立双机热备 2、在分支通过路由器向外宣告防火墙NAT SERVER 3、在分支和总部自己建立站点到站点VPN并实现负载分担 二、基础配置 如拓扑所示配置相应端口地址 三、详细配置 &#xff08;一&#xff09;双机…

unity VR项目3DUI:人物头部旁“说话框”,功能:1.永远朝向相机 2.打字效果 3.UI不会被模型遮挡 4.切换位置 5.文字自动根据内容扩充

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、UI搭建1.创建基节点2.创建Canvas3.添加永远看向相机代码4.创建UI背景&#xff0c;设置相关操作1.锚点设置2.添加组件&#xff1a;Vertical Layout Group、Content Size Fitter…

P-one新增火焰图-为性能测试开启新视野

随着软件业务流程的日益复杂&#xff0c;传统的性能测试方法已经难以满足对性能问题精准定位的需求。测试人员需要一种更加直观、全面的方式来分析软件在运行过程中的性能表现&#xff0c;以便快速准确地找到性能瓶颈并进行优化。因此&#xff0c;我们在性能测试平台P-One中加入…

几分钟教你实现一个酷炫的扫光效果

前言 话不多说&#xff0c;咱们先来看看本篇文章中我们实现的效果。 是不是发现这个效果非常的熟悉&#xff1f;没错&#xff0c;这经常能够在一些电商网站可以看到&#xff0c;那这究竟是怎么实现的呢&#xff1f;接下来由我来带领大家尝试做一个类似这样的效果出来。 实现…

浏览器采集黑屏 问题

chrome://flags/#use-angle Choose ANGLE graphics backend 选择OPENGL 然后重启浏览器 就可以了

美股市场波动与科技股动态

一、美股市场波动 周一&#xff0c;美股三大股指低开高走&#xff0c;但最终收盘时道指跌2.6%&#xff0c;纳指跌3.43%&#xff0c;标普跌3%。美国十年国债收益率涨0.053%&#xff0c;收报3.787%&#xff0c;恐慌指数VIX涨64.9%至38.57。现货黄金跌1.34%&#xff0c;报2409.42…

2024年有哪些好用的文件加密软件?十款常用加密软件推荐

在2024年&#xff0c;随着数据泄露和网络威胁的日益复杂&#xff0c;文件加密软件成为了保护敏感信息不可或缺的工具。无论是个人用户还是企业&#xff0c;选择合适的加密软件都是确保数据安全的重要一环。 1. 安秉加密软件 安秉加密软件专为企业设计&#xff0c;提供全面的信…

SpringBoot框架学习笔记(六):自定义转换器、内容协商 和 Thymeleaf

1 自定义转换器 1.1 基本介绍 &#xff08;1&#xff09;SpringBoot在响应客户端请求时&#xff0c;将提交的数据封装成对象时&#xff0c;使用了内置的转换器&#xff0c;一共提供了124个内置转换器&#xff0c;核心源码&#xff0c;在 GenericConverter 接口的内部类 Conve…

澳洲联储按兵不动,通胀阴霾难散

澳洲联储核心通胀率仍远高于目标水平&#xff0c;经济增长依然强劲&#xff0c;因此维持高利率是必要的。 鹰派立场坚定 澳洲联储的这一决定与全球其他央行的政策走向形成了鲜明对比。许多发达经济体的央行已经开始降息&#xff0c;以应对经济增长放缓的风险。然而&#xff0…

TapData 信创数据源 | 国产信创数据库 OceanBase 数据同步指南,加速国产化进程,推进自主创新建设

随着国家对自主可控的日益重视&#xff0c;目前在各个行业和区域中面临越来越多的国产化&#xff0c;采用有自主知识产权的国产数据库正在成为主流。长期以来&#xff0c;作为拥有纯国产自研背景的 TapData&#xff0c;自是非常重视对于更多国产信创数据库的数据连接器支持&…

WiFi模块无线通信交互,乐鑫ESP32物联网方案,启明云端乐鑫代理商

随着物联网(IoT)技术的飞速发展&#xff0c;我们正步入一个智能化、互联化的世界。在这一进程中&#xff0c;无线WiFi模块作为连接物理世界与数字世界的桥梁&#xff0c;扮演着至关重要的角色。 WiFi模块是一种基于WiFi协议的无线模块&#xff0c;它可以实现设备之间的无线通信…

ACM MM 2024 | 比SDXL和DALL-E·3更引人入胜!ReCorD:交互场景生成最新SOTA!

文章链接&#xff1a;https://arxiv.org/pdf/2407.17911 git链接&#xff1a;https://alberthkyhky.github.io/ReCorD/ 亮点直击&#xff1a; 引入了一种新颖的推理框架&#xff0c;将潜在扩散模型&#xff08;LDM&#xff09;与视觉语言模型&#xff08;VLM&#xff09;相结合…

私域成交的关键点

私域运营&#xff0c;私域团购&#xff0c;私域秒杀&#xff0c;私域发售

单张图像降噪Masked and Shuffled Blind Spot Denoising for Real-World Images

文章目录 Masked and Shuffled Blind Spot Denoising for Real-World Images1.噪声的空间相关性2.mask3.loss4.masking ratio 与 spatial correlation的关系5.空间噪声去相关6.Automated selection of the BSD masking ratio7.小结 Masked and Shuffled Blind Spot Denoising f…

深入 Dify 源码,洞察 Dify RAG 切片机制实现细节

背景介绍 最近测试时发现 Dify 的 RAG 分片效果一般&#xff0c;不管是使用之前 深入 Dify 源码&#xff0c;洞察 Dify RAG 核心机制 中有调研过的默认解析还是 Unstructured 解析。因此调研比较了 大量的开源框架 实现了特定格式的结构化解析方案&#xff0c;并与 Dify 现有解…

高通8255 Android Virtio Virtio-IIC 配置方法

目录 一&#xff1a;VirtIO和Passthrough的区别 方法一&#xff1a; passthrough 方法二&#xff1a;virtIO 二&#xff1a;配置逻辑 三&#xff1a;示例Virtio-I2C配置 Virtio-I2C框架 步骤一&#xff1a;QNX IIC资源配置 & 测试 配置 测试 步骤二&#xff1a;B…

干货整理: 什么软件能够监控电脑? 六大好用监控电脑软件抢手推荐

如何保障电脑的安全是许多企业用户关注的焦点。 除了常见的杀毒软件外&#xff0c;电脑监控软件也是一道重要的防线。 这些软件能够实时监控电脑的各项运行状况&#xff0c;及时发现并处理潜在的安全威胁&#xff0c;确保电脑的正常运行和用户数据的安全。 接下来&#xff0…