文章目录
- [1. enum 类型](#1. enum 类型)
-
- [1.1 定义规则](#1.1 定义规则)
- [1.2 定义时注意](#1.2 定义时注意)
-
- 情况一:同级枚举类型包含相同枚举值名称
- 情况二:不同级枚举类型包含相同枚举值名称
- [情况三:多文件下都未声明 package](#情况三:多文件下都未声明 package)
- [情况四:多文件下都声明了 package](#情况四:多文件下都声明了 package)
- [2. 升级通讯录至 2.1 版本](#2. 升级通讯录至 2.1 版本)
1. enum 类型
1.1 定义规则
该语法支持我们定义枚举类型并使用。
在 .proto 文件中枚举类型的书写规范为:
- 枚举类型名称: 使用驼峰命名法,首字母大写。 例如:
MyEnum - 常量值名称: 全大写字母,多个字母之间用
_连接。例如:ENUM_CONST = 0;
我们可以定义一个名为 PhoneType 的枚举类型,定义如下:
proto
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
要注意枚举类型的定义有以下几种规则:
- 0 值常量必须存在,且要作为第一个元素。这是为了与 proto2 的语义兼容:第一个元素作为默认值,且值为 0。
- 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
- 枚举的常量值在 32 位整数的范围内。但因负值无效因而不建议使用(与编码规则有关)。
1.2 定义时注意
将两个 "具有相同枚举值名称" 的枚举类型放在单个 .proto 文件下测试时,编译后会报错:某某某常量已经被定义!
所以这里要注意:
- 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
- 单个 .proto 文件下,最外层枚举类型和嵌套枚举类型,不算同级。
- 多个 .proto 文件下,若一个文件引入了其他文件,且每个文件都未声明 package,每个 proto 文件中的枚举类型都在最外层,算同级。
- 多个 .proto 文件下,若一个文件引入了其他文件,且每个文件都声明了 package,不算同级。
情况一:同级枚举类型包含相同枚举值名称
代码如下:
proto
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
enum PhoneTypeCopy {
MP = 0; // 移动电话 // 编译后报错: MP 已经定义
}
情况二:不同级枚举类型包含相同枚举值名称
代码如下:
proto
// 用法正确
enum PhoneTypeCopy {
MP = 0; // 移动电话
}
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
}
情况三:多文件下都未声明 package
proto
// phone1.proto
import "phone1.proto"
enum PhoneType {
MP = 0; // 移动电话 // 编译后报错: MP 已经定义
TEL = 1; // 固定电话
}
// phone2.proto
enum PhoneTypeCopy {
MP = 0; // 移动电话
}
情况四:多文件下都声明了 package
proto
// phone1.proto
import "phone1.proto"
package phone1;
enum PhoneType {
MP = 0; // 移动电话 // 用法正确
TEL = 1; // 固定电话
}
// phone2.proto
package phone2;
enum PhoneTypeCopy {
MP = 0; // 移动电话
}
2. 升级通讯录至 2.1 版本
2.1 更新 proto 文件
更新 contacts.proto(通讯录 2.1),新增枚举字段并使用,更新内容如下:
proto
syntax = "proto3";
package contacts; // package 是一个可选的声明符, 声明其命名空间
// 定义联系人message
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; // 电话
}
// 通讯录:修改消息名为ContactBook(大驼峰,避免和字段名contacts冲突)
message ContactBook
{
repeated PeopleInfo contacts = 1;
}
如下图所示:

然后直接编译即可:

此时在 contacts.pb.h 的代码中:
- 对于在
.proto文件中定义的枚举类型,编译生成的代码中会含有与之对应的枚举类型、校验枚举值是否有效的方法IsValid、以及获取枚举值名称的方法_Name。 - 对于使用了枚举类型的字段,包含设置和获取字段的方法,已经清空字段的方法
clear_。
2.2 更新 write.cc 文件
设置联系人电话类型
cpp
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts; // 把命名空间展开
// 新增联系人
void AddPeopleInfo(PeopleInfo *people)
{
cout << "-------------新增联系人-------------" << endl;
cout << "请输入联系人姓名: ";
string name;
getline(cin, name);
people->set_name(name);
cout << "请输入联系人年龄: ";
int age;
cin >> age;
people->set_age(age);
cin.ignore(256, '\n'); // 清除回车
for (int i = 0;; i++)
{
cout << "请输入联系人电话" << i + 1 << " (输入回车即可完成电话新增): ";
string number;
getline(cin, number);
if (number.empty())
{
break;
}
PeopleInfo_Phone *phone = people->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;
}
}
cout << "-----------添加联系人成功-----------" << endl;
}
int main()
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
ContactBook contacts;
// 读取本地已存在的通讯录文件
fstream input("contacts.bin", ios::in | ios::binary);
if (!input)
{
cout << "contacts.bin not find, create new file!" << endl;
}
else if (!contacts.ParseFromIstream(&input))
{
cerr << "parse error!" << endl;
input.close();
return -1;
}
// 向通讯录中添加一个联系人
AddPeopleInfo(contacts.add_contacts());
// 将通讯录写入本地文件中
fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
if (!contacts.SerializeToOstream(&output))
{
cerr << "write error!" << endl;
input.close();
output.close();
return -1;
}
cout << "write success!" << endl;
input.close();
output.close();
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
更新内容如下:

2.3 更新 read.cc 文件
在读取联系人的时候,打印出电话的类型。
cpp
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts; // 把命名空间展开
// 打印联系人列表
void PrintContacts(ContactBook& contacts)
{
for (int i = 0; i < contacts.contacts_size(); i++)
{
cout << "---------------联系人" << i+1 << "---------------" << endl;
const PeopleInfo& people = contacts.contacts(i);
cout << "联系人姓名: " << people.name() << endl;
cout << "联系人年龄: " << people.age() << endl;
for (int j = 0; j < people.phone_size() ; j++)
{
// 打印联系人电话
const PeopleInfo_Phone& phone = people.phone(j);
cout << "联系人电话" << j+1 << ": " << phone.number(); // 这里不需要换行符
// 打印联系人电话类型
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
}
}
int main()
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
ContactBook contacts;
// 读取本地已存在的通讯录文件(以二进制方式读取)
fstream input("contacts.bin", ios::in | ios::binary);
if (!contacts.ParseFromIstream(&input))
{
cerr << "parse error!" << endl;
input.close();
return -1;
}
// 打印通讯录列表
PrintContacts(contacts);
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
更新内容如下:

2.4 编译运行
运行结果如下:先添加第一个联系人

然后再添加第二个联系人,当我们输入错误的电话类型的时候,可以看到此时电话类型被设置成了 MP。

原因就是:我们在 read.cc 代码中进行反序列化的时候,会为李四设置一个默认的电话类型,也就是 MP(因为我们在 .proto 文件中,对 MP 定义的常量值为 0,这个值就是默认的)