Proto3 高级语法:重复字段、嵌套消息与枚举类型实战
本文深入讲解Protobuf3的高级语法,重点介绍了三种复杂字段类型的定义与使用:1)repeated关键字实现重复字段(类似数组);2)嵌套消息体定义结构化子对象;3)enum枚举类型规范字段取值。通过通讯录场景实战,展示了如何定义包含多个联系人、每个联系人含多个电话号码(含类型枚举)的复杂消息体。文章详细解析了编译后生成的C++操作方法,并强调repeated字段顺序保留、枚举必须0值开头等关
🔥个人主页:Milestone-里程碑
❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>
🌟心向往之行必能至
目录
在上一篇博客中,我们学习了 proto3 的基础语法,能定义简单的消息体和基础字段,但实际业务场景中,结构化数据往往更复杂 —— 例如一个联系人有多个电话号码、电话号码包含号码和类型、通讯录包含多个联系人等。
本文将讲解 proto3 的高级语法,包括重复字段(repeated)、嵌套消息体、枚举类型(enum),并结合通讯录场景实战,让大家能定义复杂的、贴合实际业务的 Protobuf 协议。
一、重复字段:repeated 关键字
实际业务中,经常需要一个字段包含多个值(如一个联系人有多个电话号码、一个订单有多个商品),proto3 通过repeated关键字定义重复字段,相当于编程语言中的数组 / 列表,重复值的顺序会被保留。
1. 重复字段的定义格式
proto
message 消息体名称 {
// repeated + 字段类型 + 字段名 = 唯一编号;
repeated 字段类型 字段名 = 字段唯一编号;
}
2. 实战:联系人的多个电话号码
在通讯录的PeopleInfo消息体中,添加多个电话号码字段,使用repeated string类型:
proto
syntax = "proto3";
package contacts;
// 联系人消息体
message PeopleInfo {
string name = 1; // 姓名
sint32 age = 2; // 年龄
// 重复字段:多个电话号码,字符串类型,编号3
repeated string phone_numbers = 3;
}
- 编译后,C++ 会生成对应的列表操作方法(如
add_phone_numbers()、phone_numbers_size()、phone_numbers(int index)); - 重复字段的默认值为空列表,无值时不参与序列化。
二、嵌套消息体:定义复杂的子对象
当字段的结构化程度较高时(如电话号码不仅包含号码,还包含类型:移动 / 固定),单独的基础类型无法满足需求,此时可以在一个消息体中嵌套定义另一个消息体,形成父子消息体,也可以定义独立的消息体供其他消息体引用。
proto3 支持任意多层嵌套,且不同消息体的字段编号可以重复。
1. 嵌套消息体的定义格式(两种方式)
方式 1:内部嵌套定义(适合仅当前消息体使用的子对象)
proto
message 父消息体 {
// 嵌套定义子消息体,仅父消息体能使用
message 子消息体 {
字段类型 字段名 = 唯一编号;
}
// 使用嵌套的子消息体作为字段类型
子消息体 字段名 = 唯一编号;
// 重复的子消息体(多个子对象)
repeated 子消息体 字段名 = 唯一编号;
}
方式 2:独立定义(适合多个消息体共用的子对象)
proto
// 独立定义子消息体,所有消息体均可引用
message 子消息体 {
字段类型 字段名 = 唯一编号;
}
message 父消息体1 {
repeated 子消息体 字段名 = 唯一编号;
}
message 父消息体2 {
子消息体 字段名 = 唯一编号;
}
2. 实战:电话号码包含号码和类型
将电话号码从简单的字符串,升级为包含号码(number) 和类型(type) 的复杂对象,使用内部嵌套消息体定义,并结合repeated实现多个电话号码:
proto
syntax = "proto3";
package contacts;
// 联系人消息体
message PeopleInfo {
string name = 1; // 姓名
sint32 age = 2; // 年龄
// 嵌套消息体:电话号码(仅PeopleInfo使用)
message Phone {
string number = 1; // 电话号码
}
// 重复字段:多个电话号码对象,编号3
repeated Phone phone = 3;
}
三、枚举类型:enum 关键字
当字段的取值为有限的固定值时(如电话号码类型:移动 / 固定、性别:男 / 女、订单状态:待支付 / 已支付 / 已取消),使用枚举类型(enum) 可以规范字段取值,避免无效值,提高代码的可读性和可维护性。
1. 枚举类型的定义规范
proto
// 枚举类型命名:驼峰命名法(大驼峰),首字母大写
enum 枚举类型名称 {
// 枚举常量命名:全大写字母,多个单词用下划线连接
常量名1 = 0; // 必须包含0值常量,且作为第一个元素(proto3规范)
常量名2 = 1;
常量名3 = 2;
// ...
}
核心规则:
- 枚举类型必须包含0 值常量,且作为第一个元素(为了兼容 proto2 的默认值语义,0 为默认值);
- 枚举常量的取值为32 位整数,不建议使用负数(与编码规则冲突);
- 枚举类型可以嵌套在消息体中(仅当前消息体使用),也可以独立定义(多个消息体共用);
- 同级枚举的常量名不可重复(不同级、不同包名的枚举常量名可重复)。
2. 枚举类型的使用场景
- 嵌套使用:枚举仅为某个消息体的字段服务,如电话号码类型仅为
Phone消息体服务; - 独立使用:枚举为多个消息体服务,如
OrderStatus(订单状态)供Order、OrderDetail等消息体使用。
3. 实战:为电话号码添加类型枚举
在嵌套的Phone消息体中,添加电话号码类型枚举(移动:MP、固定:TEL),并将其作为Phone的字段:
proto
syntax = "proto3";
package contacts;
// 联系人消息体
message PeopleInfo {
string name = 1; // 姓名
sint32 age = 2; // 年龄
// 嵌套消息体:电话号码
message Phone {
// 枚举类型:电话号码类型(嵌套定义,仅Phone使用)
enum PhoneType {
MP = 0; // 移动电话(默认值,必须为第一个元素)
TEL = 1; // 固定电话
}
string number = 1; // 电话号码
PhoneType type = 2; // 电话号码类型,使用枚举类型
}
// 重复字段:多个电话号码对象,编号3
repeated Phone phone = 3;
}
- 编译后,C++ 会生成对应的枚举类和字段操作方法(如
set_type()、type()、PhoneType_Name()(获取枚举常量名)); - 枚举字段的默认值为第一个枚举常量(0 值)。
四、实战:定义完整的通讯录消息体
结合重复字段、嵌套消息体、枚举类型,定义一个完整的通讯录消息体 —— 包含多个联系人,每个联系人包含姓名、年龄、多个电话号码(含类型):
proto
syntax = "proto3";
package contacts;
// 联系人消息体
message PeopleInfo {
string name = 1; // 姓名
sint32 age = 2; // 年龄
// 嵌套:电话号码(含类型枚举)
message Phone {
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
string number = 1; // 号码
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 多个电话号码
}
// 通讯录消息体:包含多个联系人
message Contacts {
// 重复字段:多个联系人对象,编号1
repeated PeopleInfo contacts = 1;
}
五、编译复杂.proto 文件并生成 C++ 方法
使用以下命令编译上述contacts.proto文件:
bash
运行
protoc --cpp_out=. contacts.proto
编译后生成contacts.pb.h和contacts.pb.cc,其中会为复杂字段生成专属操作方法(C++ 为例),核心方法如下:
1. 嵌套消息体的方法
- 创建子对象:
PeopleInfo_Phone* add_phone();(为重复的 phone 字段添加一个 Phone 对象); - 获取子对象:
const PeopleInfo_Phone& phone(int index) const;(根据索引获取 Phone 对象); - 获取子对象数量:
int phone_size() const;(获取电话号码的个数)。
2. 枚举类型的方法
- 设置枚举值:
void set_type(PeopleInfo_Phone_PhoneType value);; - 获取枚举值:
PeopleInfo_Phone_PhoneType type() const;; - 获取枚举常量名:
static const std::string& PhoneType_Name(PhoneType value);(如将 MP 转为字符串 "MP")。
3. 外层通讯录的方法
- 添加联系人:
PeopleInfo* add_contacts();; - 获取联系人数量:
int contacts_size() const;; - 获取指定联系人:
const PeopleInfo& contacts(int index) const;。
六、高级语法注意事项
repeated字段的顺序会被严格保留,序列化和反序列化后的顺序一致;- 嵌套消息体的作用域仅限于父消息体,外部消息体无法直接引用(如需引用,改为独立定义);
- 枚举类型必须以 0 值常量开头,否则编译报错;
- 同级枚举的常量名不可重复,不同级枚举无此限制;
- 复杂字段的编号仍需遵循唯一编号规则,为后续扩展预留编号。
下一篇博客,我们将讲解 proto3 的高级特性 ——Any 类型、Oneof 类型和 Map 类型,这三种类型能解决更复杂的业务场景,如泛型字段、多选一字段、键值对字段等,让 Protobuf 的协议定义更灵活。
更多推荐
所有评论(0)