
@toc
<font color="#DFD=545" size="4" face="楷体">本文详解ProtoBuf核心类型:enum需遵循命名规范,0值必选且同级不重名;Any可存任意消息,支持动态扩展;oneof强制单字段互斥,节省内存;map创建键值映射,键限标量类型。还涵盖默认值规则、协议兼容策略、未知字段处理及option配置优化。</font>
enum类型MyEnumENUM_CONST = 0;PhoneType的枚举类型:注意:
同级(同层)枚举类型中常量不能重名,否则单个.proto文件下测试编译会报错(某某某常量已被定义)不算同级声明了package则不算同级。演示下:
syntax= "proto3";
package enum;
enum PhoneType{
MF=0;//移动电话
TEL=1;//固定电话
}




<font color="blue">还有就是如果导入的是同级别的存在同名的枚举类型;那么也会报错;此时就可以**手动加上对应的命名空间(package)**;此时就不算同级别则不会报错了。</font>
<font color="red">下面结合枚举改造下之前的通讯录:/font>




Any类型Any 类型(Protocol Buffers 通用容器类型):
repeated 修饰,实现存储多个任意类型消息(类似数组/列表)。include 目录下的 .proto 文件中可找到所有预定义类型(包括 Any 的声明和详细定义)。下面演示下:



<font color="green">下面给之前写的通讯录利用Any类型加上对应的地址;也就是把地址填入对应Any类型管理的数据了:</font>



contacts::Address address;
cout << "请输入联系人家庭地址:";
string home;
getline(cin, home);
address.set_home_address(home);
cout << "请输入联系人工作地址:";
string work;
getline(cin, work);
address.set_work_address(work);
//进行加入Any管理的变量(把对应地址):
persons->mutable_data()->PackFrom(address);


==注:==
has_value clear_value mutable_value**);其次就是将对象放入以及取出(**PackFrom UnpackTo**)。</font>oneof类型<font color="brown">简单说就是被这个类型变量包含的成员;只能设置一个;然后内部进行编译成c++代码的时候会生成对应枚举来供选择(注意的是这里面**不能选择使用对应的repeated选项**)。</font>
下面再基于oneof增加对应的qq wechat等选项:




下面别忘记编译一下proto文件再就进行make:
write.cc(这里只能选择一个):

read.cc:

oneof);没有设置就是默认值故不操作。

map类型map<key_type, value_type> map_field = N;格式创建关联映射字段。float和bytes外的任意标量类型。map字段不能用repeated修饰。map中元素无序。下面演示下:

write.cc:
for (int i = 0;; i++)
{
cout << "请输入备注" << i + 1 << "标题(只输入回车完成备注新增):";
string key;
getline(cin, key);
if (key.empty()) {break;}
cout << "请输入备注" << i + 1 << "内容: ";
string value;
getline(cin, value);
persons->mutable_comment()->insert({key,value});
}read.cc:
if (cs.contacts(i).comment_size()) {
cout << "备注信息:" << endl;
}
for (auto it = cs.contacts(i).comment().cbegin(); it !=cs.contacts(i).comment().cend(); it++) {
cout << " " << it->first << ": " << it->second << endl;
}

反序列化消息时,若二进制序列不含某字段,反序列化后对象中对应字段会设为该字段默认值,且不同类型默认值不同。
消息字段、oneof字段和any字段,有has_方法来检测当前字段是否被设置。==注:对于如string int32 int64这样的简单变量才支持对应set操作;而如数组(repeated) map等这样的变量就不支持而改成了mutable操作。==
<font color="purple"> 因此可能会存在比如数值类型;导致忘记设置了;那么它默认就是0;此时就不能判断是设置的0还是默认的;如果有has的话就能检测;可惜没有;因此就需要设置的时候确定好。</font>
1. 别碰的:
reserved(保留),别重复用,也别直接删/注释,容易乱(如果保留了再次使用也会报错)。2. 能换的类型:
int32/uint32/int64/uint64/bool)互相换,解析时自动截断(比如64位改32位,多的位不要了)。sint32 和 sint64 能互相换,和其他整型不行。string 和 bytes 只要在合法UTF-8下,能互换。enum 能和 int32/uint32 这些换,但值超范围会被截断;反序列化时,不认识的枚举值会保留(不同语言处理不一样)。fixed32 ↔ sfixed32、fixed64 ↔ sfixed64 能互换。3. oneof 怎么改:
oneof 里的新成员,安全!oneof 也行。oneof,会崩!<font color="red">下面简单说下对于字段修改及删除操作;基于文件读写带来的影响:</font>
如果读与写用的是同一个文件;但是proto文件不是同一个;当写入文件后;改变写已经存在的字段的类型变量的时候;看下对应读的效果:


而预期的是年龄默认值为0;生日不打印;因此就不能复用之前的编号;然后新创建编号为生日:
只需要在对应类内 reserved字段如(reserved 1,2;可以多个保留或者也可以reserved "field3", "field4"进行变量类型保留),然后新建编号:


简单说:可以理解成读取的时候和写入的时候是按照**对应字段编号**识别进行读写的,这也就是修改或者删除的时候需要注意字段编号信息。
<font color="pink">总之,编号和保留字段守规矩,类型在兼容范围内换,oneof 操作小心,新旧版本就不会互相认不出来。</font>
未知字段指解析已序列化数据时的未识别字段,如旧程序解析带新字段数据的情形;proto3原本解析时丢弃未知字段,3.5及以上版本重新保留未知字段,反序列化和序列化结果都包含。
下面演示下:
下面就拿上面修改过字段的读端新加入字段;然后写端进行反序列化进来;而读端对应的**.pb.h**文件可以看到如下(此时新加入的字段而读端反序列化后不能识别的就是未知字段了):

每个变量含有的一个表(枚举类;标记它是啥类型变量)。

合type这个枚举值来获取;不然会出错);也就是先判断变量是什么类型;然后在进行得到内容。下面演示下:
对应部分代码:
const Reflection* reflection = PeopleInfo::GetReflection();
const UnknownFieldSet& set = reflection->GetUnknownFields(people);
for (int j = 0; j < set.field_count(); j++) {
const UnknownField& unknown_field = set.field(j);
cout << "未知字段" << j+1 << ": "
<< " 编号:" << unknown_field.number();
switch(unknown_field.type()) {
case UnknownField::Type::TYPE_VARINT:
cout << " 值:" << unknown_field.varint() << endl;
break;
case UnknownField::Type::TYPE_LENGTH_DELIMITED:
cout << " 值:" << unknown_field.length_delimited() << endl;
break;
// case ...
}
}GetReflection;也就是对应的静态函数;拿到对应对象;然后获取对应某个人的未知字段集合GetUnknownFields(people);根据数量进行遍历(set.field_count());判断什么类型;获取对应什么类型的值;然后就是对应的每个未知字段变量的number编号就是之前被在proto文件中设置的编号。需要尽量保证通讯协议的 “向后兼容” 或 “向前兼容”。也就是为了这但做到了对应的对于接受到未知字段保存功能。
option类型<font color="blue"> 在.proto文件中可通过option标注声明许多选项,这些选项能影响**proto**编译器的某些处理方式。</font>
选项分类:
google/protobuf/descriptor.proto中定义。FileOptions)、消息级(如MessageOptions)、字段级(如FieldOptions)等多种,没有一种选项能作用于所有类型。常用选项列举:
1·optimize_for:
为文件选项,可设置protoc编译器的优化级别,有SPEED、CODE_SIZE、LITE_RUNTIME三种。
SPEED:生成的代码高度优化、运行效率高,但占用空间多,是默认选项。CODE_SIZE:生成最少的类,占用空间少,依赖基于反射的代码实现相关操作,运行效率低,适合大量.proto文件且不盲目追求速度的应用。LITE_RUNTIME:生成的代码执行效率高、占用空间少,牺牲了反射功能,仅提供编码+序列化功能,链接BP库时仅需链接libprotobuf - lite,常用于资源有限平台(如移动手机平台),示例代码为option optimize_for = LITE_RUNTIME;。2·allow_alias:
为枚举选项,允许将相同常量值分配给不同枚举常量来定义别名。
enum PhoneType中设置option allow_alias = true;后,MP = 0;、TEL = 1;、LANDLINE = 1;不会报错(此时这俩变量就是同一个东西),若不设置则会编译报错。<font color="#DC3545" size="6" face="楷体">Protobuf仓库传送门</font>
<font color="#DC3545" size="5" face="楷体">通过本篇可以学习到ProtoBuf通过enum/Any/oneof/map等类型灵活定义数据结构,结合默认值、兼容规则及未知字段保留机制,保障协议升级稳定性。合理使用option优化性能,严格遵循编号/命名规范,实现高效安全的跨版本通信。</font>
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。