网站建设需要学ps吗,大连招标信息网,面膜网络推广方案,免费凡科建站官网Protobuf#xff1a;复杂类型接口 package字段规则复杂类型enumAnyoneofmap 本博客基于proto3语法#xff0c;讲解protobuf中的复杂类型。
package
在.proto文件中#xff0c;支持导入其它.proto文件的内容#xff0c;例如#xff1a;
test.proto#xff1a;
syntax … Protobuf复杂类型接口 package字段规则复杂类型enumAnyoneofmap 本博客基于proto3语法讲解protobuf中的复杂类型。
package
在.proto文件中支持导入其它.proto文件的内容例如
test.proto
syntax proto3;
package test_pkg;import other.proto;message Person {int32 id 1;string name 2;other_pkg.Address addr 3;
}other.proto
syntaxproto3;
package other_pkg;message Address {string country 1;string city 2;
}在test.proto文件中使用了other.proto的内容。想要实现多个文件的效果需要通过improt导入文件
import 文件名.proto随后在导入的文件中使用包名.变量名的形式使用变量比如other_pkg.Address。 字段规则
如果想在protobuf中实现一个数组它使用了一种字段规则的修饰方式来实现数组。
消息字段类型可以用以下规则修饰
singular消息中该字段可以出现0次或1次字段默认使用该规则repeated消息中该字段可以出现任意次数包括0次
简单来说对于singular变量只能存储一个值如果输入新的值后来的值会把原先的值覆盖。而对于repeated变量其可以存储多个值所以值都会被保留其实也就是数组。
示例
syntax proto3;
package test_pkg;message Person {int32 id 1;string name 2;repeated string phone 3;
}此处的phone字段被设置为了repeated可以理解为一个string的数组在pb.h文件中该字段包含以下接口
// repeated string phone 3;
int phone_size() const;
private:
int _internal_phone_size() const;
public:
void clear_phone();
const std::string phone(int index) const;
std::string* mutable_phone(int index);
void set_phone(int index, const std::string value);
void set_phone(int index, std::string value);
void set_phone(int index, const char* value);
void set_phone(int index, const char* value, size_t size);
std::string* add_phone();
void add_phone(const std::string value);
void add_phone(std::string value);
void add_phone(const char* value);
void add_phone(const char* value, size_t size);可以看到相比于一般的string类型被repeated修饰后多出了很多下标操作这里挑几个接口出来讲解
int phone_size() const获取当前数组的长度。const std::string phone(int index) const通过下标获取值的操作获取该变量的const引用。std::string* mutable_phone(int index)获取指定下标元素的指针可以通过指针修改该元素。
void set_phone(int index, const std::string value);
void set_phone(int index, std::string value);
void set_phone(int index, const char* value);
void set_phone(int index, const char* value, size_t size);这四个接口用于设置指定下标的值四种函数重载效果是一样的只是传入字符串的形式不同。
std::string* add_phone()数组末尾增加一个空元素并返回指向该元素的指针可以通过指针修改该元素。
void add_phone(const std::string value);
void add_phone(std::string value);
void add_phone(const char* value);
void add_phone(const char* value, size_t size);这四个接口直接插入一个指定元素到数组末尾重载是为了兼容不同形式的字符串。
被repeat修饰的变量接口总结
接口功能xxx_size()获取数组的长度xxx(index)获取指定下标的元素不可修改muteble_xxx(index)获取指定下标元素的指针可修改set_xxx(index, value)设置指定下标的元素为valueadd_xxx()尾插一个元素到数组返回指向该元素的指针可修改add_xxx(value)尾插value到数组 复杂类型
enum
enum是枚举类型其列举多个常量方便后续使用。
定义一个枚举表示有哪些人物种类
enum Type{STU 0;TEACHER 1;OTHER 2;
}此处枚举了三个常量分别表示学生老师其它。
枚举类型有以下要求
0值必须存在且要作为第一个元素在语言中定义枚举变量后如果指定变量值第一个元素0是默认值枚举的常量值在32位整数内但是负数无效
示例
syntax proto3;
package test_pkg;enum Type{STU 0;TEACHER 1;OTHER 2;
}message Person {int32 id 1;string name 2;repeated string phone 3;Type type 4;
}编译后在.pb.h文件可以找到以下枚举类型
enum Type : int {STU 0,TEACHER 1,OTHER 2,Type_INT_MIN_SENTINEL_DO_NOT_USE_ std::numeric_limitsint32_t::min(),Type_INT_MAX_SENTINEL_DO_NOT_USE_ std::numeric_limitsint32_t::max()
};此处使用了C11的强枚举类型语法enum Type : int 指定底层类型为int。除去在.proto文件中指定的三个成员还增加了两个其它成员最后两个成员用于做边界值判断限制成员的值只在int32范围内。
紧接着的是部分枚举类型的专有接口
bool Type_IsValid(int value);
constexpr Type Type_MIN STU;
constexpr Type Type_MAX OTHER;
constexpr int Type_ARRAYSIZE Type_MAX 1;const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* Type_descriptor();
templatetypename T
inline const std::string Type_Name(T enum_t_value)
inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, Type* value);Type_IsValid判断整数是否是enum内部的成员之一Type_Name输入枚举值返回枚举值对应的字符串名称Type_parse输入字符串和枚举值判断两者是否匹配
再往后在Person类中定义了枚举的通用接口
// .test_pkg.Type type 4;
void clear_type();
::test_pkg::Type type() const;
void set_type(::test_pkg::Type value);可以看到枚举类型的get和set接口还是比较简单的。
enum接口总结
接口功能xxx_IsValid()判断整数是否是enum内部的成员之一xxx_Name(value)输入枚举值返回枚举值对应的字符串名称xxx_Parse(string, value)输入字符串和枚举值判断两者是否匹配xxx()获取当前枚举变量的值set_xxx(value)设置枚举的值 Any
假设现在要给Person类定义一个info字段用于存储该人的具体信息。但是由于Person可以是学生可以是老师这两种人存储的info是不同的如何让一个字段存储不同的值成为一个泛型
此处Any类型就排上用场了Any可以存储任何其它类型的message相当于一个泛型。
syntax proto3;
package test_pkg;import google/protobuf/any.proto;message test{google.protobuf.Any info 1;
}Any的本质是一个message写在google/protobuf/any.proto中使用Any类型需要import导包。在类内使用类型时也需要指定包名google.protobuf.Any。
引入泛型后当前Person类如下
syntax proto3;
package test_pkg;import google/protobuf/any.proto;enum Type{STU 0;TEACHER 1;OTHER 2;
}message stuInfo {string className 1; // 班级int32 score 2; // 成绩
}message teacherInfo {string subject 1; // 科目int32 jobAge 2; // 工龄
}message Person {int32 id 1;string name 2;repeated string phone 3;Type type 4;google.protobuf.Any info 5;
}Person的info字段用于存储具体信息对于学生来说要存储班级和成绩。而对于老师来说要存储所教的科目和工龄。此时将这些信息分别定义在stuInfo和teacherInfo中在Person内引入一个Any字段info这个info就可以存储任意其它的message自然也包括stuInfo和teacherInfo。
编译后在.pb.h中Any的接口如下
// .google.protobuf.Any info 5;
bool has_info() const;
private:
bool _internal_has_info() const;
public:
void clear_info();
const ::PROTOBUF_NAMESPACE_ID::Any info() const;
PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_info();
::PROTOBUF_NAMESPACE_ID::Any* mutable_info();
void set_allocated_info(::PROTOBUF_NAMESPACE_ID::Any* info);has_info()检查该字段是否为空clear_info()清空该字段的值info()获取该字段的值release_info()获取字段内的值后清空字段内的值mutable_info()返回指向元素的指针可通过指针修改元素
另外的还有几个重要的接口在google/protobuf/any.pb.h中
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message message);bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message);templatetypename T
bool Is();这三个接口是Any最核心的接口
PackFrom将一个message类型转化为Any类型UnpackTo将一个Any类型转化回messageIs传入模板参数判断当前Any存储的类型是否和模板参数一样
这个用法有点复杂示例
#include iostream
#include test.pb.husing namespace std;
using namespace test_pkg;int main()
{test_pkg::Person ps;ps.set_id(123456);ps.set_name(张三);// repeat字段可以设置多个值ps.add_phone(123456);ps.add_phone(654321);ps.set_type(Type::STU); // 学生类型stuInfo stuinfo;stuinfo.set_classname(六年级一班);stuinfo.set_score(95);google::protobuf::Any any_message;if (any_message.PackFrom(stuinfo)) *ps.mutable_info() any_message;stuInfo getStuinfo;if (ps.info().IsstuInfo())ps.info().UnpackTo(getStuinfo);return 0;
}首先初始化了一个Person随后初始化了该对象的初始信息确定该对象是一个学生。所以他的info要存储一个stuInfo类型用于表示详细信息。
stuInfo stuinfo;
stuinfo.set_classname(六年级一班);
stuinfo.set_score(95);这段代码初始化了一个stuinfo对象。
随后将这个stuInfo对象设置到ps.info成员中
google::protobuf::Any any_message;
if (any_message.PackFrom(stuinfo)) *ps.mutable_info() any_message;设置Any成员时首先要定义一个Any对象然后通过PackFrom函数把stuInfo对象转化为Any对象。如果PackFrom返回true说明转化成功此时any_message内部就已经是stuInfo的内容了。最后通过*ps.mutable_info() any_message设置info字段为any_message对象。
如果后续想要把Any对象变回stuInfo对象
stuInfo getStuinfo;
if (ps.info().IsstuInfo())ps.info().UnpackTo(getStuinfo);定义一个getStuinfo接收返回值。随后执行ps.info().IsstuInfo()判断当前的info内存储的变量类型是不是stuInfo如果是那么执行ps.info().UnpackTo(getStuinfo)将info的内容解析到getStuinfo中。
不论用Any存储或提取任何一个message类型都是这一套逻辑。
Any接口总结
接口功能has_xxx()检查该字段是否为空clear_xxx()清空该字段的值xxx()获取该字段的值release_xxx()获取字段内的值后清空字段内的值mutable_xxx()返回指向元素的指针可通过指针修改元素PackFrom(message)将一个message类型转化为Any类型UnpackTo(message)将一个Any类型转化回messageIsmessageType()传入模板参数判断当前Any存储的类型是否和模板参数一样 oneof
有时候message中的字段在多个中选择一个就可以使用oneof类型。
示例
message Person {int32 id 1;string name 2;oneof contact {int32 qq 3;int32 tel 4;}
}此处的Person增加了一个联系方式contact用户可以在qq和tel中二选一。
在oneof内部的字段编号不能与外部的message冲突比如qq和tel的字段编号就不可以是1和2。
另外的在oneof内部不允许使用repeated修饰字段比如这样
message Person {int32 id 1;string name 2;oneof contact {repeated int32 qq 3; // errrepeated int32 tel 4; // err}
}如果设置了repeated该修饰会失效变量依然只能保存一个值。
对以下protoc代码编译
message Person {int32 id 1;string name 2;oneof contact {int32 qq 3;int32 tel 4;}
}结果
// int32 qq 3;
bool has_qq() const;
private:
bool _internal_has_qq() const;
public:
void clear_qq();
int32_t qq() const;
void set_qq(int32_t value);public:
// int32 tel 4;
bool has_tel() const;
private:
bool _internal_has_tel() const;
public:
void clear_tel();
int32_t tel() const;
void set_tel(int32_t value);void clear_contact();看起来这个oneof好像没有生效此处就好像是单独定义了qq和tel两个字段也没有什么oneof的相关方法。
其实这个地方qq和tel确实是直接当作两个字段处理的但是两个字段有点像C语言中的联合体最后一次设置的是那个变量那么存储的就是哪一个类型。
示例
test_pkg::Person ps;
ps.set_id(123456);
ps.set_name(张三);
ps.set_qq(111111);
ps.set_tel(222222);if (ps.has_qq())cout qq: ps.qq() endl;if (ps.has_tel())cout tel: ps.tel() endl;定义了一个Person变量后先后设置了qq(111111)和tel(222222)随后通过has_xxx方法分别检测两个值随后输出。
输出结果
tel: 222222最后只输出了tel因为后来的tel覆盖了qq两者是二选一的关系。
当然oneof类型也不是完全没有接口比如这个接口
void clear_contact();在需要清除oneof内部的值的时候如果不确定具体是哪一个类型就无法确定是caler_qq还是clear_tel如果一个个通过has判断还是有点麻烦的就可以通过clear_contact一键清除。
所有的oneof的字段都被保存在一个枚举中
enum ContactCase {kQq 3,kTel 4,CONTACT_NOT_SET 0,
};ContactCase contact_case() const;此处的kQq和kTel分别就是之前设置的qq和tel而CONTACT_NOT_SET表示未设置参数。
其实在判断当前oneof存储的是哪一个字段时不用一个个进行has_xxx的判断。通过contact_case函数会返回一个枚举类型只需要判断枚举类型是哪一个变量即可
switch (ps.contact_case())
{case Person::ContactCase::kQq:// 处理qq字段break;case Person::ContactCase::kTel:// 处理tel字段break;case Person::ContactCase::CONTACT_NOT_SET:// 没有字段被设置break;
}oneof接口总结
接口功能clear_xxx()不论字段存储的是什么清空该字段的值xxx_case()返回当前存储的类型的枚举值 map
map用于创建一个键值对的映射关系语法
mapkey_tyep, value_type map_name N;其实语法和C的std::map是一样的。
map有以下注意点
key_type可以是处理float和bytes之外的任意标量类型value_type可以是任意类型map中的元素是无序的
示例
message score {mapstring, int32 chinese 1;mapstring, int32 english 2;mapstring, int32 math 3;
}这是一个记录学生成绩的表格三个成员分别代表不同科目的成绩。
编译结果以chinese为例
// mapstring, int32 chinese 1;
int chinese_size() const;
private:
int _internal_chinese_size() const;
public:
void clear_chinese();
private:
const ::PROTOBUF_NAMESPACE_ID::Map std::string, int32_t _internal_chinese() const;
::PROTOBUF_NAMESPACE_ID::Map std::string, int32_t *_internal_mutable_chinese();
public:
const ::PROTOBUF_NAMESPACE_ID::Map std::string, int32_t chinese() const;
::PROTOBUF_NAMESPACE_ID::Map std::string, int32_t *mutable_chinese();chinese_size()获取map内部元素的个数clear_chinese()清空map的元素chinese()获取map的const引用mutable_chinese()获取map的指针可通过指针修改map
此处map的类型不是std::map而是protobuf自己封装的::PROTOBUF_NAMESPACE_ID::Map但是其用法和std::map没有很大区别。
::PROTOBUF_NAMESPACE_ID::Map也重载了operator[]拿到变量后可以直接通过[]进行访问。
示例
test_pkg::Score sc;auto chinese_mp *sc.mutable_chinese();
auto english_mp *sc.mutable_english();
auto math_mp *sc.mutable_math();chinese_mp[张三] 66;
chinese_mp[李四] 88;
chinese_mp[王五] 98;english_mp[张三] 97;
english_mp[李四] 77;
english_mp[王五] 82;math_mp[张三] 46;
math_mp[李四] 25;
math_mp[王五] 79;cout 张三-chinese: chinese_mp[张三] endl;
cout 张三-english: english_mp[张三] endl;
cout 张三-math: math_mp[张三] endl;通过auto chinese_mp *sc.mutable_chinese()获取到map由于mutable_chinese返回的是一个指针所以还要进行解引用。
随后通过chinese_mp[] 设置与获取元素了。
输出结果
张三-chinese: 66
张三-english: 97
张三-math: 46map接口总结
接口功能xxx_size()获取map内部元素的个数clear_xxx()清空map的元素xxx()获取map的const引用mutable_xxx()获取map的指针可通过指针修改mapmap.operator[]直接通过[]获取与设置元素