【protobuf】三、proto3语法详解② -- enum、Any、oneof、map类型
文章目录
Ⅰ. enum类型
一、定义规则
在 .proto 文件中枚举类型的书写规范为:
- 枚举类型名称:使用驼峰命名法,首字母大写。 例如:
MyEnum - 枚举内常量值名称:全大写字母,多个字母之间用
_连接。例如:ENUM_CONST = 0;
我们可以定义一个名为 PhoneType 的枚举类型,定义如下:
enum PhoneType {
MOBILE_PHONE = 0; // 移动电话
TELEPHONE = 1; // 固定电话
}
要注意枚举类型的定义有以下几种规则:
-
0值常量必须存在,且要作为第一个元素。(这是为了与proto2的语义兼容) -
若在使用枚举类型的时候没有赋值,则默认使用
0值常量,即0值常量就是默认值! -
枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
-
枚举的常量值在
32位整数的范围内,但因 负值无效因而不建议使用(与编码规则有关)。
二、同名枚举值的注意事项
将两个“具有相同枚举值名称”的枚举类型放在单个 .proto 文件下测试时,编译后会报错:“某某某常量已经被定义”,所以这里要注意:
- 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
- 单个
proto文件下,最外层枚举类型和嵌套枚举类型,不算同级。 - 多个
proto文件下,若一个文件引入了其他文件,且每个文件 未全部声明package,每个proto文件中的枚举类型都在最外层,算同级。 - 多个
proto文件下,若一个文件引入了其他文件,且每个文件 都声明了package,不算同级。
// ---------------------- 情况1:同级枚举类型包含相同枚举值名称--------------------
// ⽤法错误!
enum PhoneType {
MOBILE_PHONE = 0; // 移动电话
TELEPHONE = 1; // 固定电话
}
enum PhoneTypeCopy {
MOBILE_PHONE = 0; // 移动电话 // 编译后报错:MP 已经定义
}
// ---------------------- 情况2:不同级枚举类型包含相同枚举值名称-------------------
// ⽤法正确!
enum PhoneTypeCopy {
MOBILE_PHONE = 0; // 移动电话
}
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MOBILE_PHONE = 0; // 移动电话
TELEPHONE = 1; // 固定电话
}
}
// ---------------------- 情况3:多⽂件下都未声明package--------------------
// ⽤法错误!
// phone1.proto
import "phone1.proto"
enum PhoneType {
MOBILE_PHONE = 0; // 移动电话 // 编译后报错:MP 已经定义
TELEPHONE = 1; // 固定电话
}
// phone2.proto
enum PhoneTypeCopy {
MOBILE_PHONE = 0; // 移动电话
}
// ---------------------- 情况4:多⽂件下都声明了package--------------------
// ⽤法正确!
// phone1.proto
import "phone1.proto"
package phone1;
enum PhoneType {
MOBILE_PHONE = 0; // 移动电话
TELEPHONE = 1; // 固定电话
}
// phone2.proto
package phone2;
enum PhoneTypeCopy {
MOBILE_PHONE = 0; // 移动电话
}
三、通讯录升级版2.1
下面我们通过对项目的推进,演示如何使用 enum 类型!
这里我们主要对 2.0 版本进行手机号类型的添加,并且在写入的时候加上输入手机号类型的步骤,以及读取时候打印出手机号类型的步骤!
下面各文件中注释内容就是对 2.0 版本新增的内容,并且 makefile 文件不变的话这里就不展示了!
contacts.proto 文件:
syntax = "proto3";
package contacts2_1;
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MOBILE_PHONE = 0; // 移动电话
TELE_PHONE = 1; // 固定电话
}
PhoneType type = 2; // 手机号类型
}
repeated Phone phone = 3;
}
message Contacts {
repeated PeopleInfo people = 1;
}
write.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void addPeople(::contacts2_1::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 = 1; ; i++)
{
cout << "请输入联系人电话" << i << "(只输入回车则退出):";
string number;
getline(cin, number);
if(number.empty())
break;
auto phone = people->add_phone();
phone->set_number(number);
// 下面是新增内容:
cout << "请输入电话的类型:1为移动电话,2为固定电话";
int type;
cin >> type;
cin.ignore(256, '\n'); // 去除缓冲区中的回车
// 根据选择,设置类型
if(type == 1)
phone->set_type(contacts2_1::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
else if(type == 2)
phone->set_type(contacts2_1::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TELE_PHONE);
else
{
phone->set_type(contacts2_1::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
cout << "输入无效内容,已帮您填入类型为移动电话!" << endl;
}
}
cout << "-----------添加联系⼈成功-----------" << endl;
}
int main()
{
contacts2_1::Contacts contacts;
fstream input("./contacts.bin", ios::in | ios::binary);
if(!input.is_open())
cout << "contacts.bin not found, create new file!" << endl;
else if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 向通讯录中添加一个联系人
addPeople(contacts.add_people());
// 将通讯录写入本地文件中
fstream output("./contacts.bin", ios::out | ios::binary | ios::trunc); // 记得要覆盖写
if(!contacts.SerializeToOstream(&output))
{
// 上面的操作,这里同理!
cerr << "write contacts.bin error!" << endl;
output.close();
return -1;
}
cout << "write success" << endl;
output.close();
return 0;
}
read.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void printContacts(contacts2_1::Contacts& contacts)
{
for(int i = 0; i < contacts.people_size(); ++i)
{
cout << "------------联系⼈" << i + 1 << "------------" << endl;
cout << "名称:" << contacts.people(i).name() << endl;
cout << "年龄:" << contacts.people(i).age() << endl;
for(int j = 0 ; j < contacts.people(i).phone_size(); ++j)
{
// 手机号后面顺便跟上手机类型
auto phone = contacts.people(i).phone(j);
cout << "手机号码" << j + 1 << ":" << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
cout << "------------------------------" << endl;
}
}
int main()
{
contacts2_1::Contacts contacts;
// 1. 读取本地已存在的通讯录(默认存在,故不需要判断是否存在)
fstream input("./contacts.bin", ios::in | ios::binary); // protobuf是二进制操作
if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 2. 打印通讯录列表
printContacts(contacts);
return 0;
}

Ⅱ. Any类型
一、介绍
protobuf 中的 Any 类型提供了一组接口来帮助我们方便地序列化、反序列化以及操作不同类型的消息。Any 类型的接口主要包括封装、解封装(解码)、获取类型信息等操作,它的实现位于 /usr/include/google/protobuf/any.h 中:

protobuf 中的 Any 类型是一种灵活的消息类型,允许你将任意类型的消息嵌入到一个其他消息中,它能够封装任何符合 protobuf 定义的消息,因此适用于需要动态扩展的场景。
使用 Any 类型的时候,在 proto 文件中要引入 google/protobuf/any.proto,然后**定义对象时候要 google.protobuf**,如下所示:
syntax = "proto3";
package contacts2_2;
import "google/protobuf/any.proto" // 引入any.proto文件
message tmp {
google.protobuf.Any data = 1; // 定义时候要声明google.protobuf
}
1. PackFrom() 接口
PackFrom() 用于将一个消息对象序列化为 Any 类型。
bool PackFrom(const google::protobuf::Message& message);
- 参数:接受一个
google::protobuf::Message类型的 对象(这是所有protobuf消息的基类)。 - 返回值:如果成功序列化,返回
true;否则返回false。
示例:
google::protobuf::Any any;
User user;
user.set_name("John");
user.set_age(30);
any.PackFrom(user); // 将 user 消息封装进 any 字段中
2. UnpackTo() 接口
UnpackTo() 用于从 Any 类型反序列化出具体的消息对象。这个接口和上面的 PackFrom() 基本是配套的,就像 write() 和 read() 的关系一样!
bool UnpackTo(google::protobuf::Message* message) const;
- 参数:接受一个指向
google::protobuf::Message类型的**指针**,表示反序列化目标消息对象。 - 返回值:如果成功反序列化,返回
true;否则返回false。
示例:
google::protobuf::Any any;
User user;
user.set_name("John");
user.set_age(30);
any.PackFrom(user);
// 反序列化回 User 消息
User decoded_user;
if (any.UnpackTo(&decoded_user)) {
std::cout << "User name: " << decoded_user.name() << std::endl;
} else {
std::cout << "Failed to unpack message." << std::endl;
}
3. Is() 接口
Is() 用于检查 Any 类型中存储的消息是否与给定的消息类型匹配。
template <typename T>
bool Is() const;
- 返回值:如果
Any存储的消息是给定的类型T,返回true,否则返回false。
示例:
google::protobuf::Any any;
User user;
user.set_name("David");
any.PackFrom(user);
if (any.Is<User>()) {
std::cout << "The Any type contains a User message." << std::endl;
}
二、通讯录升级版2.2
下面我们通过对项目的推进,演示如何使用 Any 类型!
这里我们主要对 2.1 版本新增联系人的地址信息,该地址信息使用 Any 类型来存储!
下面各文件中注释内容就是对 2.1 版本新增的内容,并且 makefile 文件不变的话这里就不展示了!
contacts.proto 文件:
syntax = "proto3";
package contacts2_2;
import "google/protobuf/any.proto"; // 引入对应文件
message Address {
string home_addr = 1; // 家庭地址
string company_addr = 2; // 公司地址
}
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MOBILE_PHONE = 0;
TELE_PHONE = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4; // 地址信息(任意类型)
}
message Contacts {
repeated PeopleInfo people = 1;
}
write.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void addPeople(::contacts2_2::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 = 1; ; i++)
{
cout << "请输入联系人电话" << i << "(只输入回车则退出):";
string number;
getline(cin, number);
if(number.empty())
break;
auto phone = people->add_phone();
phone->set_number(number);
cout << "请输入电话的类型:1为移动电话,2为固定电话";
int type;
cin >> type;
cin.ignore(256, '\n');
if(type == 1)
phone->set_type(contacts2_2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
else if(type == 2)
phone->set_type(contacts2_2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TELE_PHONE);
else
{
phone->set_type(contacts2_2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
cout << "输入无效内容,已帮您填入类型为移动电话!" << endl;
}
}
// 新增内容:家庭地址和公司地址字段添加,需要使用PackFrom()来序列化存储
cout << "请输入联系人的家庭地址:";
string home;
getline(cin, home);
cout << "请输入联系人的公司地址:";
string company;
getline(cin, company);
contacts2_2::Address addr; // 创建Address对象进行内容设置
addr.set_home_addr(home);
addr.set_company_addr(company);
people->mutable_data()->PackFrom(addr); // 然后将addr通过PackFrom()序列化存储到data中
cout << "-----------添加联系⼈成功-----------" << endl;
}
int main()
{
contacts2_2::Contacts contacts;
fstream input("./contacts.bin", ios::in | ios::binary);
if(!input.is_open())
cout << "contacts.bin not found, create new file!" << endl;
else if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 向通讯录中添加一个联系人
addPeople(contacts.add_people());
// 将通讯录写入本地文件中
fstream output("./contacts.bin", ios::out | ios::binary | ios::trunc); // 记得要覆盖写
if(!contacts.SerializeToOstream(&output))
{
// 上面的操作,这里同理!
cerr << "write contacts.bin error!" << endl;
output.close();
return -1;
}
cout << "write success" << endl;
output.close();
return 0;
}
read.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void printContacts(const contacts2_2::Contacts& contacts)
{
for(int i = 0; i < contacts.people_size(); ++i)
{
cout << "------------联系⼈" << i + 1 << "------------" << endl;
const contacts2_2::PeopleInfo& people = contacts.people(i);
cout << "名称:" << people.name() << endl;
cout << "年龄:" << people.age() << endl;
for(int j = 0 ; j < people.phone_size(); ++j)
{
auto phone = people.phone(j);
cout << "手机号码" << j + 1 << ":" << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
// 首先判断是否存在数据,并且该数据类型为Address
if(people.has_data() && people.data().Is<contacts2_2::Address>())
{
// 然后将data中的内容通过UnpackTo()反序列化后存放到addr中,进行打印
contacts2_2::Address addr;
people.data().UnpackTo(&addr);
// 最好判断一下是否字符串非空再输出
if(!addr.home_addr().empty())
cout << "家庭地址:" << addr.home_addr() << endl;
if(!addr.company_addr().empty())
cout << "公司地址:" << addr.company_addr() << endl;
}
cout << "------------------------------" << endl;
}
}
int main()
{
contacts2_2::Contacts contacts;
// 1. 读取本地已存在的通讯录(默认存在,故不需要判断是否存在)
fstream input("./contacts.bin", ios::in | ios::binary); // protobuf是二进制操作
if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 2. 打印通讯录列表
printContacts(contacts);
return 0;
}

Ⅲ. oneof类型
一、定义与使用介绍
在 Protobuf 中,oneof 是一种节省存储和增强消息灵活性的机制,它 允许一个消息中的多个字段共享同一存储空间。oneof 类型的字段互斥,这意味着在 同一时间只能设置其中的一个字段。
这个场景其实挺常见的,比如我们同时要保存一个联系方式,而你可能有多个账号比如微信号和 QQ 号,那么你只需要选择存储其中一个联系方式即可,还能有多种的选择方式,更加的方便!
oneof 用于在消息中定义一组互斥字段,语法如下:
message Example {
// ...
oneof field_name {
int32 field1 = 1; // 注意编号要承接上面,这个作用域不是独立的!
string field2 = 2;
bool field3 = 3;
}
}
field_name:oneof的名称,可以是任意合法的标识符。- 字段类型:可以是任意有效的
Protobuf类型,包括基本类型(int32、string等)或消息类型。 - 字段编号:每个字段都需要唯一的编号。
有如下规则:
- 设置一个字段时,
oneof中其他字段会被自动清除。也就是说设置多个字段后,只有最后一个被存储下来了! - 如果未设置任何字段,
oneof被视为 未设置。 oneof中的字段 不能被声明为repeated类型!- 通常我们 使用
_case方法来获取当前设置了哪个字段,然后进行if判断即可!
二、通讯录升级版2.3
下面我们通过对项目的推进,演示如何使用 oneof 类型!这里我们主要对 2.2 版本新增其它联系方式!
下面各文件中注释内容就是对 2.2 版本新增的内容,并且 makefile 文件不变的话这里就不展示了!
contacts.proto 文件:
syntax = "proto3";
package contacts2_3;
import "google/protobuf/any.proto";
message Address {
string home_addr = 1;
string company_addr = 2;
}
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MOBILE_PHONE = 0;
TELE_PHONE = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4;
// 新增内容:
oneof other_contact {
string qq = 5; // 注意要从5开始,并且不能为repeated修饰
string wechat = 6;
}
}
message Contacts {
repeated PeopleInfo people = 1;
}
write.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void addPeople(::contacts2_3::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 = 1; ; i++)
{
cout << "请输入联系人电话" << i << "(只输入回车则退出):";
string number;
getline(cin, number);
if(number.empty())
break;
auto phone = people->add_phone();
phone->set_number(number);
cout << "请输入电话的类型:1为移动电话,2为固定电话";
int type;
cin >> type;
cin.ignore(256, '\n');
if(type == 1)
phone->set_type(contacts2_3::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
else if(type == 2)
phone->set_type(contacts2_3::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TELE_PHONE);
else
{
phone->set_type(contacts2_3::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
cout << "输入无效内容,已帮您填入类型为移动电话!" << endl;
}
}
cout << "请输入联系人的家庭地址:";
string home;
getline(cin, home);
cout << "请输入联系人的公司地址:";
string company;
getline(cin, company);
contacts2_3::Address addr;
addr.set_home_addr(home);
addr.set_company_addr(company);
people->mutable_data()->PackFrom(addr);
// 下面是新增内容:
cout << "请选择添加一个其它联系方式 (1.qq 2.wechat):";
int choose;
cin >> choose;
cin.ignore(256, '\n');
if(choose == 1)
{
cout << "请输入qq号:";
string qq;
getline(cin, qq);
people->set_qq(qq);
}
else if(choose == 2)
{
cout << "请输入联系人的微信号:";
string wechat;
getline(cin, wechat);
people->set_wechat(wechat);
}
else
cout << "非法选择,该项设置失败!" << endl;
cout << "-----------添加联系⼈成功-----------" << endl;
}
int main()
{
contacts2_3::Contacts contacts;
fstream input("./contacts.bin", ios::in | ios::binary);
if(!input.is_open())
cout << "contacts.bin not found, create new file!" << endl;
else if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 向通讯录中添加一个联系人
addPeople(contacts.add_people());
// 将通讯录写入本地文件中
fstream output("./contacts.bin", ios::out | ios::binary | ios::trunc); // 记得要覆盖写
if(!contacts.SerializeToOstream(&output))
{
// 上面的操作,这里同理!
cerr << "write contacts.bin error!" << endl;
output.close();
return -1;
}
cout << "write success" << endl;
output.close();
return 0;
}
read.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void printContacts(const contacts2_3::Contacts& contacts)
{
for(int i = 0; i < contacts.people_size(); ++i)
{
cout << "------------联系⼈" << i + 1 << "------------" << endl;
const contacts2_3::PeopleInfo& people = contacts.people(i);
cout << "名称:" << people.name() << endl;
cout << "年龄:" << people.age() << endl;
for(int j = 0 ; j < people.phone_size(); ++j)
{
auto phone = people.phone(j);
cout << "手机号码" << j + 1 << ":" << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
if(people.has_data() && people.data().Is<contacts2_3::Address>())
{
contacts2_3::Address addr;
people.data().UnpackTo(&addr);
if(!addr.home_addr().empty())
cout << "家庭地址:" << addr.home_addr() << endl;
if(!addr.company_addr().empty())
cout << "公司地址:" << addr.company_addr() << endl;
}
// 下面是新增内容:
// 判断时候也可以直接判断people.other_contact_case() == 5或者6来判断,但是不容易维护!
if(people.other_contact_case() == contacts2_3::PeopleInfo::OtherContactCase::kQq)
cout << "联系人qq号:" << people.qq() << endl;
else if(people.other_contact_case() == contacts2_3::PeopleInfo::OtherContactCase::kWechat)
cout << "联系人微信号:" << people.wechat() << endl;
else
cout << "联系人qq号或微信号:空" << endl;
cout << "------------------------------" << endl;
}
}
int main()
{
contacts2_3::Contacts contacts;
// 1. 读取本地已存在的通讯录(默认存在,故不需要判断是否存在)
fstream input("./contacts.bin", ios::in | ios::binary); // protobuf是二进制操作
if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 2. 打印通讯录列表
printContacts(contacts);
return 0;
}

Ⅳ. map类型
一、定义
map 类型是一种用来表示键值对集合的内建数据类型,类似于其他编程语言中的哈希表或字典。它适合用于存储动态数量的键值对数据,比如配置项、属性列表等。
基本语法如下:
map<key_type, value_type> field_name = field_number;
key_type:键的类型,可以是以下标量类型之一:- 整数类型:
int32,int64,uint32,uint64,fixed32,fixed64,sfixed32,sfixed64,sint32,sint64 - 字符串:
string - 注意:键不支持
bool、float、double、bytes或消息类型。
- 整数类型:
value_type:值的类型,可以是任何有效的Protobuf类型,包括:- 标量类型(如
int32、string等) - 嵌套消息类型
- 甚至是另一个
map类型
- 标量类型(如
map不能用repeated修饰map中的元素是 无序 的
二、通讯录升级版2.4
下面我们通过对项目的推进,演示如何使用 map 类型!这里我们主要对 2.3 版本新增其它联系方式!
下面各文件中注释内容就是对 2.3 版本新增的内容,并且 makefile 文件不变的话这里就不展示了!我们这里主要是新增备注信息,用 map 进行存储!
contacts.proto 文件:
syntax = "proto3";
package contacts2_4;
import "google/protobuf/any.proto";
message Address {
string home_addr = 1;
string company_addr = 2;
}
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MOBILE_PHONE = 0;
TELE_PHONE = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4;
oneof other_contact {
string qq = 5;
string wechat = 6;
}
// 新增内容:
map<string, string> comment = 7; // 备注信息
}
message Contacts {
repeated PeopleInfo people = 1;
}
write.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void addPeople(::contacts2_4::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 = 1; ; i++)
{
cout << "请输入联系人电话" << i << "(只输入回车则退出):";
string number;
getline(cin, number);
if(number.empty())
break;
auto phone = people->add_phone();
phone->set_number(number);
cout << "请输入电话的类型:1为移动电话,2为固定电话";
int type;
cin >> type;
cin.ignore(256, '\n');
if(type == 1)
phone->set_type(contacts2_4::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
else if(type == 2)
phone->set_type(contacts2_4::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TELE_PHONE);
else
{
phone->set_type(contacts2_4::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MOBILE_PHONE);
cout << "输入无效内容,已帮您填入类型为移动电话!" << endl;
}
}
cout << "请输入联系人的家庭地址:";
string home;
getline(cin, home);
cout << "请输入联系人的公司地址:";
string company;
getline(cin, company);
contacts2_4::Address addr;
addr.set_home_addr(home);
addr.set_company_addr(company);
people->mutable_data()->PackFrom(addr);
cout << "请选择添加一个其它联系方式 (1.qq 2.wechat):";
int choose;
cin >> choose;
cin.ignore(256, '\n');
if(choose == 1)
{
cout << "请输入qq号:";
string qq;
getline(cin, qq);
people->set_qq(qq);
}
else if(choose == 2)
{
cout << "请输入联系人的微信号:";
string wechat;
getline(cin, wechat);
people->set_wechat(wechat);
}
else
cout << "非法选择,该项设置失败!" << endl;
// 下面是新增内容:
while(true)
{
// 新增备注信息,键为备注标题,值为备注内容
cout << "请输入备注标题:(直接回车即可退出)";
string title;
getline(cin, title);
if(title.empty())
break;
cout << "请输入备注内容:";
string body;
getline(cin, body);
people->mutable_comment()->insert({title, body});
}
cout << "-----------添加联系⼈成功-----------" << endl;
}
int main()
{
contacts2_4::Contacts contacts;
fstream input("./contacts.bin", ios::in | ios::binary);
if(!input.is_open())
cout << "contacts.bin not found, create new file!" << endl;
else if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 向通讯录中添加一个联系人
addPeople(contacts.add_people());
// 将通讯录写入本地文件中
fstream output("./contacts.bin", ios::out | ios::binary | ios::trunc); // 记得要覆盖写
if(!contacts.SerializeToOstream(&output))
{
// 上面的操作,这里同理!
cerr << "write contacts.bin error!" << endl;
output.close();
return -1;
}
cout << "write success" << endl;
output.close();
return 0;
}
read.cc 文件:
#include "contacts.pb.h"
#include <iostream>
#include <fstream>
using namespace std;
void printContacts(const contacts2_4::Contacts& contacts)
{
for(int i = 0; i < contacts.people_size(); ++i)
{
cout << "------------联系⼈" << i + 1 << "------------" << endl;
const contacts2_4::PeopleInfo& people = contacts.people(i);
cout << "名称:" << people.name() << endl;
cout << "年龄:" << people.age() << endl;
for(int j = 0 ; j < people.phone_size(); ++j)
{
auto phone = people.phone(j);
cout << "手机号码" << j + 1 << ":" << phone.number();
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
if(people.has_data() && people.data().Is<contacts2_4::Address>())
{
contacts2_4::Address addr;
people.data().UnpackTo(&addr);
if(!addr.home_addr().empty())
cout << "家庭地址:" << addr.home_addr() << endl;
if(!addr.company_addr().empty())
cout << "公司地址:" << addr.company_addr() << endl;
}
if(people.other_contact_case() == contacts2_4::PeopleInfo::OtherContactCase::kQq)
cout << "联系人qq号:" << people.qq() << endl;
else if(people.other_contact_case() == contacts2_4::PeopleInfo::OtherContactCase::kWechat)
cout << "联系人微信号:" << people.wechat() << endl;
else
cout << "联系人qq号或微信号:空" << endl;
// 下面是新增内容:
if(people.comment_size() != 0)
cout << "备注信息:" << endl;
for(auto it = people.comment().begin(); it != people.comment().end(); ++it)
cout << " " << it->first << ":" << it->second << endl;
cout << "------------------------------" << endl;
}
}
int main()
{
contacts2_4::Contacts contacts;
// 1. 读取本地已存在的通讯录(默认存在,故不需要判断是否存在)
fstream input("./contacts.bin", ios::in | ios::binary); // protobuf是二进制操作
if(!contacts.ParseFromIstream(&input))
{
// 这个操作建议学起来!
// 直接通过ParseFromIstream()获取文件流进行反序列化,还能判断是否成功,一步到位!
cerr << "parse contacts.bin error!" << endl;
input.close();
return -1;
}
input.close();
// 2. 打印通讯录列表
printContacts(contacts);
return 0;
}

到此,我们对通讯录 2.x 要求的任务全部完成。在这个过程中我们将通讯录升级到了 2.4 版本,同时对 ProtoBuf 的使用也进一步熟练了,并且也掌握了 ProtoBuf 的 proto3 语法支持的大部分类型及其使用,但只是正常使用还是完全不够的。通过接下来的学习,我们就能更进一步了解到 ProtoBuf 深入的内容!
更多推荐




所有评论(0)