在 .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; // 移动电话
}
下面我们通过对项目的推进,演示如何使用 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;
}
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
}
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 字段中
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;
}
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;
}
下面我们通过对项目的推进,演示如何使用 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;
}
在 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
判断即可! 下面我们通过对项目的推进,演示如何使用 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<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
中的元素是 无序 的 下面我们通过对项目的推进,演示如何使用 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
深入的内容!