本系列文章将通过对通讯录项目的不断完善,带大家由浅入深的学习Protobuf的使用。这是Contacts的2.1版本,在这篇文章中将带大家学习Protobuf的enum类型语法,并将其用到我们的项目中
.proto文件中可以定义枚举类型。如下我们可以定义了一个PhoneType枚举类型:
enum PhoneType{
MOBILE = 0;
FIXED = 1;
}
[书写规范]:
- 枚举类型名建议采用驼峰命名,开头大写
- enum 类型成员被定义为具名常量。常量命名建议全部使用大写,多个字母之间使用
_
连接,例如:ENUM_COUNT = 0
0值常量必须存在,且要作为第一个元素。这是为了与proto2的语义兼容:第一个元素作为默认值,且值为0。
// 错误示范1: 0值常量必须存在
enum PhoneType{
MOBILE = 1;
FIXED = 2;
}
// 错误示范2:0值常量必须作为第一个元素
enum PhoneType{
FIXED = 1;
MOBILE = 0;
}
枚举类型除了可以在消息外定义,也可以在消息体内定义(嵌套)
message PeopleInfo{
// ……
enum PhoneType{
FIXED = 0;
MOBILE = 1;
}
}
枚举的常量值在32位整数的范围内。但因负值无效因而不建议使用(与编码规则有关)
将两个具有相同枚举值名称的枚举类型放在单个.proto文件下测试时,编译后会报错:某某某常量已经被定义。我们来总结这个问题
同级的枚举常量名不能重复
//【错误】:同级的MOBILE名发生重复
enum PhoneType{
MOBILE = 0;
}
enum PhoneTypeCopy{
MOBILE = 0;
}
处于非同级的枚举常量名可以重复
// 【正确】: 处于不同级的枚举常量名可以重复
enum PhoneType{
MOBILE = 0;
}
message PeopleInfo{
enum PhoneTypeCopy{
MOBILE = 0;
}
}
引入另一个.proto文件时,同级的枚举常量名不能重复
// 【错误】:同级的MOBILE名发生重复
// ----------------- phone.proto -------------------
enum PhoneType{
MOBILE = 0;
}
// ---------------- contact.prot -----------------
import phone.proto
enum PhoneType{
MOBILE = 0;
}
引入另一个.proto文件时,如果两个文件在不同的package下,则枚举常量名可以重复。package起到了隔离的作用,避免了冲突的发生
// 【正确】:因为处于不同的package下
// ----------------- phone.proto -------------------
package phone
enum PhoneType{
MOBILE = 0;
}
// ---------------- contact.prot -----------------
import phone.proto
enum PhoneType{
MOBILE = 0;
}
[总结]:
- 同级的枚举常量名不能重复
- 处于不同package下的常量名可以重复
// 原始版本
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone{
string number = 1;
string type = 2;
}
repeated Phone phone = 3;
}
将Phone的type类型改为枚举类型:
// 修改后的版本
message PeopleInfo{
string name = 1;
int32 age = 2;
message Phone{
string number = 1;
enum PhoneType{
MOBILE = 0;
FIXED = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
}
对上面修改后的 .proto 文件进行编译后,观察生成的 .h
的变化
// 新生成了枚举类
enum PeopleInfo_Phone_PhoneType : int {
PeopleInfo_Phone_PhoneType_MOBILE = 0,
PeopleInfo_Phone_PhoneType_FIXED = 1,
// ……
};
对枚举类型 type 和 PhoneType 类型新生成的部分函数(注意枚举常量名和枚举常量值表达意思的不同)
// 检验value是不是有效的枚举常量值
static inline bool PhoneType_IsValid(int value);
// 返回枚举常量值对应的枚举常量名
static inline const std::string& PhoneType_Name(T enum_t_value);
// 将枚举常量名解析为对应的枚举常量值
static inline bool PhoneType_Parse(const std::string& name, PhoneType* value)
// -----------------------------------------------------------------
// 获取type的枚举常量值
::contacts::PeopleInfo_Phone_PhoneType type() const;
// 设置为默认值,即值为0的枚举值
void clear_type();
// 设置枚举类型值
void set_type(::contacts::PeopleInfo_Phone_PhoneType value);
// value 为上一代码块提到的枚举类,例如“PeopleInfo_Phone_PhoneType_MOBILE”
write.cc修改
#include
#include
#include "contact.pb.h"
using namespace std;
void AddPeopleInfo(contact2::PeopleInfo* p){
cout << "----------新增联系人----------" << endl;
cout << "请输入联系人姓名: ";
string name;
getline(cin, name);
p->set_name(name);
cout << "请输入联系人年龄: ";
int age;
cin >> age;
p->set_age(age);
// 一直清空,直到读到 \n(\n也会被清除),或者清空,直到清空了256个字符
cin.ignore(256, '\n');
// 说明:cin输入后,换行符还会被留在缓冲区中,而getline读到\n就停了
// 所以需要使用 cin.ignore 清空缓冲区从而避免对之后的输入产生影响
for(int i = 0;; i++){
cout << "请输入联系人电话" << i + 1 << "(只输入回车则结束): ";
string number;
getline(cin, number);
if(number.empty()) break;
contact2::PeopleInfo_Phone* phone = p->add_phone();
phone->set_number(number);
// ------------------修改部分-----------------------
cout << "请选择电话类型: (0:MOBIle 1:FIXED)";
int type;
cin >> type;
cin.ignore(256, '\n');
switch(type){
case 0:
phone->set_type(contact2::PeopleInfo_Phone_PhoneType_MOBILE);
break;
case 1:
phone->set_type(contact2::PeopleInfo_Phone_PhoneType_FIXED);
break;
default:
cout << "非法选择, 将采用默认值" << endl;
}
// ---------------------修改部分---------------------
}
cout << "-----------添加成功-----------" << endl;
}
int main(int argc, char* argv[]){
if(argc != 2){
cerr << "use: ./write filename" << endl;
exit(1);
}
contact2::Contact contact;
// 1. 读取通讯录中的原始数据
fstream input(argv[1], ios::in | ios::binary); // 二进制的方式读取
if(!input){
cerr << argv[1] << " file not find. Creating a new file" << endl;
} else if(!contact.ParseFromIstream(&input)){ // 从输出流中反序列化
cerr << "parser original file error" << endl;
exit(2);
}
// 2. 新增联系人
AddPeopleInfo(contact.add_contact());
// 3. 写入文件中
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if(!output || !contact.SerializePartialToOstream(&output)){
cerr << "write error" << endl;
exit(3);
}
cout << "write success" << endl;
input.close();
output.close();
}
read.cc修改
#include
#include
#include "contact.pb.h"
using namespace std;
void PrintContact(contact2::Contact& contact){
for(int i = 0; i < contact.contact_size(); i++){
const contact2::PeopleInfo people = contact.contact(i);
cout << "-------------联系人" << i + 1 << "-------------" << endl;
cout << "name: " << people.name() << endl;
cout << "age: " << people.age() << endl;;
int j = 1;
// ---------------------修改部分---------------------------
// phone.type()枚举常量值
// PhoneType_Name根据枚举常量值来返回对应的枚举常量名
for(const auto& phone : people.phone()){
cout << "number" << j++ << ": " << phone.number()
<< " type: " << phone.PhoneType_Name(phone.type()) << endl;
// ---------------------修改部分---------------------------
}
}
}
int main(int argc, char* argv[])
{
if(argc != 2){
cerr << "use: ./read file" << endl;
exit(1);
}
contact2::Contact contact;
// 1. 读取通讯录中的原始数据
fstream input(argv[1], ios::in | ios::binary); // 二进制的方式读取
if(!input){
cerr << argv[1] << " file not find. exit" << endl;
exit(2);
} else if(!contact.ParseFromIstream(&input)){ // 从输出流中反序列化
cerr << "parser original file error" << endl;
exit(2);
}
PrintContact(contact);
input.close();
return 0;
}
[说明]:
"张三"是我们上次插入的数据,我们并没有对其type类型进行赋值,但是根据proto的语法,对于没有赋值的枚举类型,默认设置枚举常量值为0