Protobuf数据类型

news/2024/11/17 22:35:26/

enum类型

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

枚举类型名称:
使⽤驼峰命名法,⾸字⺟⼤写。例如: MyEnum

常量值名称:
全⼤写字⺟,多个字⺟之间⽤连接。例如: ENUM_CONST = 0;

比如我们定义一个PhoneType类型

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

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

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

定义规则

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

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

情况一:同级出现常量重名的情况

syntax="proto3";
package enum1;enum PhoneType1 {MP=0;TEL=1;
}enum PhoneType2 {MP=0;
}

image-20230602002838802

情况二:嵌套

enum PhoneType1 {MP=0;TEL=1;
}message phone {string number=1;enum PhoneType2 {MP=0;}
}

image-20230602003107599

情况三:多文件未使用package

image-20230602003359719

编译,发现报错。错误信息为重复定义。

image-20230602003452513

情况四:多文件使用package

image-20230602003931203

改进通讯录

更新通讯录。为电话号码加上类型。

修改contacts.proto文件

enum PhoneType {MP=0;   //移动电话TEL=1;  //固定电话
}message Phone {string number=1;PhoneType type=2;
}//定义联系人消息
message PeopleInfo{string name=1;string age=2;repeated Phone phone=3;
}message Contacts{repeated PeopleInfo people=1;
}

更新write.cc文件:添加电话的类型

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{for (int j = 0;; j++){std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;std::cout << " 请输入联系人姓名: ";string name;getline(std::cin, name);if(name=="退出"){break;}contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息peopleinfo->set_name(name);std::cout << " 请输入联系人年龄: ";string age;getline(std::cin, age);peopleinfo->set_age(age);for (int i = 0;; i++){std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";string number;getline(std::cin, number);if (number.empty()){break;}contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息phone->set_number(number);std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";int type=0;std::cin>>type;std::cin.ignore(256,'\n');switch (type){case 0:phone->set_type(contacts1::MP);break;case 1:phone->set_type(contacts1::TEL);break;default:cout << "⾮法选择,使⽤默认值!" << endl;break;}}}
}

执行结果:

image-20230602010001278

更新read.cc文件

void PrintContacts(contacts1::Contacts& contacts){for(int i=0;i<contacts.people_size();i++){contacts1::PeopleInfo peopleinfo=contacts.people(i);    //通过下标获取peoplestd:cout<<"----------联系人"<<i+1<<"----------"<<std::endl;std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;int j=1;for(;j<=peopleinfo.phone_size();j++){std::cout<<"电话: "<<peopleinfo.phone(j-1).number();contacts1::Phone phone=peopleinfo.phone(j-1);std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;}}
}

输出结果:

image-20230602010810937

当我们未设置值时,默认第一个值为默认值。

Any类型

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

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

image-20230604171158173

改进通讯录

修改contacts.proto文件,添加Any类型。

syntax="proto3";
package contacts1;
import "google/protobuf/any.proto";     //引入any.proto文件message Address{string home_address=1;  //家庭地址string unit_address=2;  //单位地址
}enum PhoneType {MP=0;   //移动电话TEL=1;  //固定电话
}message Phone {string number=1;PhoneType type=2;
}//定义联系人消息
message PeopleInfo{string name=1;string age=2;repeated Phone phone=3;google.protobuf.Any address=4;
}message Contacts{repeated PeopleInfo people=1;
}

image-20230604172808177

修改write.cc:将message Address转化为Any类型

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{for (int j = 0;; j++){std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;std::cout << " 请输入联系人姓名: ";string name;getline(std::cin, name);if(name=="退出"){break;}contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息peopleinfo->set_name(name);std::cout << " 请输入联系人年龄: ";string age;getline(std::cin, age);peopleinfo->set_age(age);for (int i = 0;; i++){std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";string number;getline(std::cin, number);if (number.empty()){break;}contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息phone->set_number(number);std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";int type=0;std::cin>>type;std::cin.ignore(256,'\n');switch (type){case 0:phone->set_type(contacts1::MP);break;case 1:phone->set_type(contacts1::TEL);break;default:std::cout << "⾮法选择,使⽤默认值!" << std::endl;break;}}contacts1::Address address;std::cout<<" 请输入家庭地址:";string home_address;getline(std::cin,home_address);address.set_home_address(home_address);std::cout<<" 请输入单位地址:";string unit_address;getline(std::cin,unit_address);address.set_unit_address(unit_address);google::protobuf::Any * data=peopleinfo->mutable_address(); //若该对象存在,则直接返回该对象,若不存在则新new 一个。//类型转化:转化为Any类型。Any类型也就是通用类型data->PackFrom(address);}
}

image-20230604181134592

修改read.cc:将Any类型转化为message address类型

void PrintContacts(const contacts1::Contacts& contacts){for(int i=0;i<contacts.people_size();i++){const contacts1::PeopleInfo& peopleinfo=contacts.people(i);    //通过下标获取peoplestd::cout<<"----------联系人"<<i+1<<"----------"<<std::endl;std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;int j=1;for(;j<=peopleinfo.phone_size();j++){std::cout<<"电话: "<<peopleinfo.phone(j-1).number();contacts1::Phone phone=peopleinfo.phone(j-1);std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;}//如果有address成员,并且address成员的类型是Addressif(peopleinfo.has_address()&&peopleinfo.address().Is<contacts1::Address>()){contacts1::Address address;peopleinfo.address().UnpackTo(&address);//如果家庭地址不为空if(!address.home_address().empty()){std::cout<<" 家庭地址:"<<address.home_address()<<std::endl;}//如果单位地址不为空if(!address.unit_address().empty()){std::cout<<" 单位地址:"<<address.unit_address()<<std::endl;}}}

image-20230604183314361

oneof类型

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

考虑以下的场景:一个人的性别只能说男或者女。只能是两种性别中的一个,既不能都选也不能都不选。

  • 每一个oneof类型都被转化为了一个enum类型
message Address{string home_address=1;  //家庭地址string unit_address=2;  //单位地址
}enum PhoneType {MP=0;   //移动电话TEL=1;  //固定电话
}message Phone {string number=1;PhoneType type=2;
}//定义联系人消息
message PeopleInfo{string name=1;string age=2;repeated Phone phone=3;google.protobuf.Any address=4;oneof gender {string male=5;string female=6;}
}
message Contacts{repeated PeopleInfo people=1;
}

image-20230604185429434

可以看到oneof类型都被转化为了enum类型,其成员就是oneof包含的值加上NOT_SET。

注意:

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

改进通讯录

修改write.cc文件:添加oneof类型

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{for (int j = 0;; j++){std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;std::cout << " 请输入联系人姓名: ";string name;getline(std::cin, name);if(name=="退出"){break;}contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息peopleinfo->set_name(name);std::cout << " 请输入联系人年龄: ";string age;getline(std::cin, age);peopleinfo->set_age(age);for (int i = 0;; i++){std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";string number;getline(std::cin, number);if (number.empty()){break;}contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息phone->set_number(number);std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";int type=0;std::cin>>type;std::cin.ignore(256,'\n');switch (type){case 0:phone->set_type(contacts1::MP);break;case 1:phone->set_type(contacts1::TEL);break;default:std::cout << "⾮法选择,使⽤默认值!" << std::endl;break;}}contacts1::Address address;std::cout<<" 请输入家庭地址:";string home_address;getline(std::cin,home_address);address.set_home_address(home_address);std::cout<<" 请输入单位地址:";string unit_address;getline(std::cin,unit_address);address.set_unit_address(unit_address);google::protobuf::Any * data=peopleinfo->mutable_address(); //若该对象存在,则直接返回该对象,若不存在则新new 一个。//类型转化:转化为Any类型data->PackFrom(address);//设置oneof类型std::cout<<" 请输入联系人性别:(1.male  2.female):";int male=0;std::cin>>male;std::cin.ignore(256,'\n');if(male==1){string male="male";peopleinfo->set_male(male);}else if(male==2){string female="female";peopleinfo->set_female(female);}else{std::cout<<" 非法选择,设置为默认值!"<<std::endl;}}
}

image-20230604212013379

修改read.cc文件:读取oneof类型

void PrintContacts(const contacts1::Contacts& contacts){for(int i=0;i<contacts.people_size();i++){const contacts1::PeopleInfo& peopleinfo=contacts.people(i);    //通过下标获取peoplestd::cout<<"----------联系人"<<i+1<<"----------"<<std::endl;std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;int j=1;for(;j<=peopleinfo.phone_size();j++){std::cout<<"电话: "<<peopleinfo.phone(j-1).number();contacts1::Phone phone=peopleinfo.phone(j-1);std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;}//如果有address成员,并且address成员的类型是Addressif(peopleinfo.has_address()&&peopleinfo.address().Is<contacts1::Address>()){contacts1::Address address;peopleinfo.address().UnpackTo(&address);if(!address.home_address().empty()){std::cout<<"家庭地址:"<<address.home_address()<<std::endl;}if(!address.unit_address().empty()){std::cout<<"单位地址:"<<address.unit_address()<<std::endl;}}//读取oneof类型/* 第一种写法if (people.has_qq()) {} else if (people.has_weixin()) {} *///写法二switch (peopleinfo.gender_case()){case contacts1::PeopleInfo::kMale:std::cout<<"性别:male"<<std::endl;break;case contacts1::PeopleInfo::kFemale:std::cout<<"性别:female"<<std::endl;break;case contacts1::PeopleInfo::GENDER_NOT_SET:std::cout<<"性别:未设置"<<std::endl;break;default:std::cout<<"未知错误! "<<std::endl;break;}}
}

image-20230604213445055

map类型

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

map<key_type, value_type> map_field = N;

要注意的是:

  • key_type是除了float和bytes类型以外的任意标量类型。 value_type 可以是任意类型。
  • map字段不可以⽤repeated修饰
  • map中存⼊的元素是⽆序的
message Address{string home_address=1;  //家庭地址string unit_address=2;  //单位地址
}enum PhoneType {MP=0;   //移动电话TEL=1;  //固定电话
}message Phone {string number=1;PhoneType type=2;
}//定义联系人消息
message PeopleInfo{string name=1;string age=2;repeated Phone phone=3;google.protobuf.Any address=4;oneof gender {string male=5;string female=6;}map<string,string> remarks=7;
}
message Contacts{repeated PeopleInfo people=1;
}

image-20230604222227122

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

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

改进通讯录

修改write.cc文件:添加备注项

void AddPeopleInfo(contacts1::Contacts *contacts_ptr)
{for (int j = 0;; j++){std::cout << "--------- 新增联系人(输入“退出”即可结束新增联系人) --------" << std::endl;std::cout << " 请输入联系人姓名: ";string name;getline(std::cin, name);if(name=="退出"){break;}contacts1::PeopleInfo *peopleinfo = contacts_ptr->add_people(); //添加一个people消息peopleinfo->set_name(name);std::cout << " 请输入联系人年龄: ";string age;getline(std::cin, age);peopleinfo->set_age(age);for (int i = 0;; i++){std::cout << " 请输入联系人电话 " << i + 1 << "(只输入回车完成电话的新增): ";string number;getline(std::cin, number);if (number.empty()){break;}contacts1::Phone *phone = peopleinfo->add_phone();  //添加一个phone信息phone->set_number(number);std::cout<< " 请输入电话类型:(MP/移动电话:0  TEL/固定电话:1): ";int type=0;std::cin>>type;std::cin.ignore(256,'\n');switch (type){case 0:phone->set_type(contacts1::MP);break;case 1:phone->set_type(contacts1::TEL);break;default:std::cout << "⾮法选择,使⽤默认值!" << std::endl;break;}}//增加Any类型contacts1::Address address;std::cout<<" 请输入家庭地址:";string home_address;getline(std::cin,home_address);address.set_home_address(home_address);std::cout<<" 请输入单位地址:";string unit_address;getline(std::cin,unit_address);address.set_unit_address(unit_address);google::protobuf::Any * data=peopleinfo->mutable_address(); //若该对象存在,则直接返回该对象,若不存在则新new 一个。//类型转化:转化为Any类型data->PackFrom(address);//增加oneof类型std::cout<<" 请输入联系人性别:(1.male  2.female):";int male=0;std::cin>>male;std::cin.ignore(256,'\n');if(male==1){string male="male";peopleinfo->set_male(male);}else if(male==2){string female="female";peopleinfo->set_female(female);}else{std::cout<<" 非法选择,设置为默认值!"<<std::endl;}//增加map类型for(int i=1;;i++){std::cout<<"添加第"<<i<<"条备注的标题:(只输入回车表示添加结束)";string remark_key;getline(std::cin,remark_key);if(remark_key.empty()){break;}std::cout<<"添加第"<<i<<"条备注的内容:";string remark_value;getline(std::cin,remark_value);//开辟remarks空间google::protobuf::Map<std::string, std::string> *remarks=peopleinfo->mutable_remarks();remarks->insert({remark_key,remark_value});}}
}

image-20230604224359840

修改read.cc文件

void PrintContacts(const contacts1::Contacts& contacts){for(int i=0;i<contacts.people_size();i++){const contacts1::PeopleInfo& peopleinfo=contacts.people(i);    //通过下标获取peoplestd::cout<<"----------联系人"<<i+1<<"----------"<<std::endl;std::cout<<"联系人姓名: "<<peopleinfo.name()<<std::endl;std::cout<<"联系人年龄: "<<peopleinfo.age()<<std::endl;int j=1;for(;j<=peopleinfo.phone_size();j++){std::cout<<"电话: "<<peopleinfo.phone(j-1).number();contacts1::Phone phone=peopleinfo.phone(j-1);std::cout<<"  类型["<<phone.type()<<"]"<<std::endl;}//如果有address成员,并且address成员的类型是Addressif(peopleinfo.has_address()&&peopleinfo.address().Is<contacts1::Address>()){contacts1::Address address;peopleinfo.address().UnpackTo(&address);if(!address.home_address().empty()){std::cout<<"家庭地址:"<<address.home_address()<<std::endl;}if(!address.unit_address().empty()){std::cout<<"单位地址:"<<address.unit_address()<<std::endl;}}/* 第一种写法if (people.has_qq()) {} else if (people.has_weixin()) {} *///写法二switch (peopleinfo.gender_case()){case contacts1::PeopleInfo::kMale:std::cout<<"性别:male"<<std::endl;break;case contacts1::PeopleInfo::kFemale:std::cout<<"性别:female"<<std::endl;break;case contacts1::PeopleInfo::GENDER_NOT_SET:std::cout<<"性别:未设置"<<std::endl;break;default:std::cout<<"未知错误! "<<std::endl;break;}//打印备注if(peopleinfo.remarks_size()){for(auto it=peopleinfo.remarks().cbegin();it!=peopleinfo.remarks().end();it++){std::cout << " " << it->first << ": " << it->second << std::endl;}}
}

image-20230605163047688

默认值

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

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

更新消息

当我们需要删除或者修改一些字段时,如果是直接进行修改就会出现错误。因此需要使用reserved关键字。

验证直接删除的影响

image-20230605182324203

我们将文件分别在client和server目录下保留一份。然后修改server目录下contacts.proto文件的内容:

image-20230605183230688

分别执行read程序和server程序观察结果

执行write程序

image-20230605185149213

执行read程序

image-20230605185227711

直接进行修改,就会出现数据丢失的现象。这和protobuf的规则有关:protobuf通过字段序号进行确认。

reserved保留项字段

更新规则

如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可 :

  • 禁⽌修改任何已有字段的字段编号。
  • 若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号(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类型是不安全的。
message Message {// 设置保留项reserved 100, 101, 200 to 299;reserved "field3", "field4";// 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。// reserved 102, "field5";// 设置保留项之后,下⾯代码会告警int32 field1 = 100; //告警:Field 'field1' uses reserved number 100int32 field2 = 101; //告警:Field 'field2' uses reserved number 101int32 field3 = 102; //告警:Field name 'field3' is reservedint32 field4 = 103; //告警:Field name 'field4' is reserved
}

使用reserved关键字,修改server的消息

image-20230605202210086

执行write程序

image-20230605202419205

执行read程序

image-20230605202618408

未知字段

在上面的通讯录程序中,我们向service⽬录下的contacts.proto新增了‘birthday’字段,但对于client相关的代码并没有任何改动。验证后发现新代码序列化的消息(service)也可以被旧代码(client)解析。并且这⾥要说的是,新增的‘⽣⽇’字段在旧程序(client)中其实并没有丢失,⽽是会作为旧程序的未知字段。

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

打印未知字段

修改client.cc文件的内容

void PrintContacts(const r_contacts::Contacts &contacts)
{for (int i = 0; i < contacts.people_size(); i++){const r_contacts::PeopleInfo &peopleinfo = contacts.people(i); // 通过下标获取peoplestd::cout << "----------联系人" << i + 1 << "----------" << std::endl;std::cout << "联系人姓名: " << peopleinfo.name() << std::endl;std::cout << "联系人年龄: " << peopleinfo.age() << std::endl;int j = 1;for (; j <= peopleinfo.phone_size(); j++){std::cout << "电话: " << peopleinfo.phone(j - 1).number();r_contacts::Phone phone = peopleinfo.phone(j - 1);std::cout << "  类型[" << phone.type() << "]" << std::endl;}// 如果有address成员,并且address成员的类型是Addressif (peopleinfo.has_address() && peopleinfo.address().Is<r_contacts::Address>()){r_contacts::Address address;peopleinfo.address().UnpackTo(&address);if (!address.home_address().empty()){std::cout << "家庭地址:" << address.home_address() << std::endl;}if (!address.unit_address().empty()){std::cout << "单位地址:" << address.unit_address() << std::endl;}}/*第一种写法if (people.has_qq()) {} else if (people.has_weixin()) {} */// 写法二switch (peopleinfo.gender_case()){case r_contacts::PeopleInfo::kMale:std::cout << "性别:male" << std::endl;break;case r_contacts::PeopleInfo::kFemale:std::cout << "性别:female" << std::endl;break;case r_contacts::PeopleInfo::GENDER_NOT_SET:std::cout << "性别:未设置" << std::endl;break;default:std::cout << "未知错误! " << std::endl;break;}// 打印备注if (peopleinfo.remarks_size()){for (auto it = peopleinfo.remarks().cbegin(); it != peopleinfo.remarks().end(); it++){std::cout << " " << it->first << ": " << it->second << std::endl;}}/*打印未知字段*/const google::protobuf::Reflection *reflect = r_contacts::PeopleInfo::GetReflection(); // 获取未知字段const google::protobuf::UnknownFieldSet &unknowset = reflect->GetUnknownFields(peopleinfo);for (int j = 0; j < unknowset.field_count(); j++){const google::protobuf::UnknownField &unknow_field = unknowset.field(j);cout << "未知字段" << j + 1 << ":"<< " 字段编号: " << unknow_field.number()<< " 类型: " << unknow_field.type();switch (unknow_field.type()){case google::protobuf::UnknownField::Type::TYPE_VARINT:cout << " 值: " << unknow_field.varint() << endl;break;case google::protobuf::UnknownField::Type::TYPE_LENGTH_DELIMITED:cout << " 值: " << unknow_field.length_delimited() << endl;break;}}}
}

image-20230605204757541

protobuf主要类

image-20230605215029947

MessageLite类介绍

  • MessageLite从名字看是轻量级的message,仅仅提供序列化、反序列化功能。
  • 类定义在google提供的message_lite.h中。

**Message类介绍 **

我们⾃定义的message类,都是继承⾃Message。

  • Message最重要的两个接⼝GetDescriptor/GetReflection,可以获取该类型对应的Descriptor对象指针和Reflection对象指针。
  • 类定义在google提供的message.h中
//google::protobuf::Message 部分代码展⽰
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;

**Descriptor类介绍 **

  • Descriptor:是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto⽂件内容等。
  • 类定义在google提供的descriptor.h中
// 部分代码展⽰
class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {string& name () constint field_count() const;const FieldDescriptor* field(int index) const;const FieldDescriptor* FindFieldByNumber(int number) const;const FieldDescriptor* FindFieldByName(const std::string& name) const;const FieldDescriptor* FindFieldByLowercaseName(const std::string& lowercase_name) const;const FieldDescriptor* FindFieldByCamelcaseName(const std::string& camelcase_name) const;int enum_type_count() const;const EnumDescriptor* enum_type(int index) const;const EnumDescriptor* FindEnumTypeByName(const std::string& name) const;const EnumValueDescriptor* FindEnumValueByName(const std::string& name)const;
}

**Reflection类介绍 **

  • Reflection接⼝类,主要提供了动态读写消息字段的接⼝,对消息对象的⾃动读写主要通过该类完成。

  • 提供⽅法来动态访问/修改message中的字段,对每种类型,Reflection都提供了⼀个单独的接⼝⽤于读写字段对应的值。

    ◦ 针对所有不同的field类型 FieldDescriptor::TYPE_* ,需要使⽤不同的 Get*()/Set*()/Add*() 接⼝;
    ◦ repeated类型需要使⽤ GetRepeated*()/SetRepeated*() 接⼝,不可以和⾮repeated
    类型接⼝混⽤;
    ◦ message对象只可以被由它⾃⾝的 reflection(message.GetReflection()) 来操
    作;

  • 类中还包含了访问/修改未知字段的⽅法。

  • 类定义在google提供的message.h中。

UnknownFieldSet类介绍

  • UnknownFieldSet包含在分析消息时遇到但未由其类型定义的所有字段。
  • 若要将UnknownFieldSet附加到任何消息,请调⽤Reflection::GetUnknownFields()。
  • 类定义在unknown_field_set.h中。
class PROTOBUF_EXPORT UnknownFieldSet {inline void Clear();void ClearAndFreeMemory();inline bool empty() const;inline int field_count() const;inline const UnknownField& field(int index) const;inline UnknownField* mutable_field(int index);
// Adding fields ---------------------------------------------------void AddVarint(int number, uint64_t value);void AddFixed32(int number, uint32_t value);void AddFixed64(int number, uint64_t value);void AddLengthDelimited(int number, const std::string& value);std::string* AddLengthDelimited(int number);UnknownFieldSet* AddGroup(int number);
// Parsing helpers -------------------------------------------------
// These work exactly like the similarly-named methods of Message.bool MergeFromCodedStream(io::CodedInputStream* input);bool ParseFromCodedStream(io::CodedInputStream* input);bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);	bool ParseFromArray(const void* data, int size);inline bool ParseFromString(const std::string& data) {return ParseFromArray(data.data(), static_cast<int>(data.size()));
}
// Serialization.bool SerializeToString(std::string* output) const;bool SerializeToCodedStream(io::CodedOutputStream* output) const;static const UnknownFieldSet& default_instance();
};

**UnknownField类介绍 **

  • 表⽰未知字段集中的⼀个字段。
  • 类定义在unknown_field_set.h中
class PROTOBUF_EXPORT UnknownField {public:enum Type {TYPE_VARINT,TYPE_FIXED32,TYPE_FIXED64,TYPE_LENGTH_DELIMITED,TYPE_GROUP};inline int number() const;inline Type type() const;// Each method works only for UnknownFields of the corresponding type.inline uint64_t varint() const;inline uint32_t fixed32() const;inline uint64_t fixed64() const;inline const std::string& length_delimited() const;inline const UnknownFieldSet& group() const;inline void set_varint(uint64_t value);inline void set_fixed32(uint32_t value);inline void set_fixed64(uint64_t value);inline void set_length_delimited(const std::string& value);inline std::string* mutable_length_delimited();inline UnknownFieldSet* mutable_group();
};

option选项

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

选项分类

syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
...

由此可⻅,选项分为 ⽂件级、消息级、字段级等等,但并没有⼀种选项能作⽤于所有的类型 。

**常⽤选项列举 **

  • optimize_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; 这⼀⾏会编译报错
}	

http://www.ppmy.cn/news/230397.html

相关文章

I2C学习笔记——00apb_tb、mst_seq_lib、test(env、test_sequence、test)

apb_tb:tb是在dut侧&#xff1b; 导入tests、if文件&#xff1b;设定周期、复位&#xff1b;例化接口&#xff0c;将接口config_db到uvm_test_top.env.mst和slv中&#xff1b; timescale 1ps/1ps import uvm_pkg::*; include "uvm_macros.svh" include "lvc_ap…

黑苹果关机不断电解决方法

进入&#xff1a;/EFI/CLOVER/dirvers64UEFI 下 添加&#xff1a;EmuVariableUefi-64.efi 使用Kext Utility 修复权限和重建缓存重启即可

DW1820a 黑苹果开机一段时间 卡死机问题或者屏蔽针脚问题

屏蔽针脚的时候 不要用透明胶带, 不要用透明胶带 不要用透明胶带 要用电工胶带 我的屏蔽一面即可,大家可以借鉴一下 ,我只屏蔽了背面的两个针脚 . 机型是 机械革命x7tis

什么是黑苹果(Hackintosh)

什么是黑苹果&#xff1f; 非苹果电脑使用Macos系统的设备&#xff0c;简称黑苹果&#xff08;Hackintosh&#xff09; 苹果公司的Macos系统&#xff0c;一直针对的是自家电脑&#xff0c;普通的PC电脑无法直接使用苹果的系统。随着大神们对Macos系统研究&#xff0c;黑苹果技…

假如苹果公司倒闭了,会对iOS及macOS用户产生什么影响?

首先苹果公司是不会一天之内瞬间倒闭的。那么他在倒闭前会发生什么呢&#xff1f; 首先是股价大跌&#xff0c;投资者的不信任以及市场的恶化导致苹果公司开始裁员&#xff0c;部分创新项目暂停甚至撤下&#xff08;比如智能眼镜&#xff0c;智能家居&#xff0c;智能汽车&…

黑苹果 MAC Monterey 在睡眠后 bluetoothd 占用很高的cpu解决方案

问题 在安装了黑苹果后&#xff0c;在睡眠唤醒后&#xff0c;bluetoothd这个进程会占用200%的CPU&#xff0c;使cpu 的温度急剧升高&#xff0c;找了很久也没找到解决方法。下面列出我的解决方案 解决 首先使用homebrew安装sleepwatcher和blueutil brew install (sleepwatch…

黑苹果各种问题汇总

1.opencore com.apple.applefscompressiontypedataless kmod start 2.com.apple.AppleFSCompressionTypeZlib load succeeded 3.ifnet:attach: All kernel threads created for interface lo0 have been scheduled at least once. Proceeding 整了半天好像我的打开这个就可以…

苹果手机经常开低电量模式,对电池会有影响吗?

苹果手机经常开低电量模式&#xff0c;对电池会有影响吗&#xff1f;需要注意什么问题&#xff1f; 苹果手机开启低电量模式&#xff0c;对电池是没有影响的。 不过虽然对电池没有影响&#xff0c;但是对您的一些功能的使用会产生影响。 比如说&#xff1a;邮箱将不再接收邮件…