Protobuf 的快速使用(二)
这个部分会对通讯录进⾏多次升级,使⽤ 2.x 表⽰升级的版本,最终将会升级如下内容:
- 不再打印联系⼈的序列化结果,⽽是将通讯录序列化后并写⼊⽂件中。
- 从⽂件中将通讯录解析出来,并进⾏打印。
- 新增联系⼈属性,共包括:姓名、年龄、性别、电话信息、地址、其他联系⽅式、备注。
字段规则
消息的字段可以⽤下⾯⼏种规则来修饰:
- singular :消息中可以包含该字段零次或⼀次(不超过⼀次)。 proto3 语法中,字段默认使⽤该规则。
- repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。
更新 contacts.proto ,
PeopleInfo
消息中新增
phone_numbers
字段,表⽰⼀个联系⼈有多个号码,可将其设置为 repeated,写法如下:
syntax = "proto3";
package contacts;
message PeopleInfo
{
string name = 1;
int32 age = 2;
repeated string phone_numbers = 3;
}
多文件的使用
我们另创建一个 gender.proto 文件,其中包含了 message Gender,内容如下:
syntax = "proto3";
package gender;
message Gender
{
string gender = 1;
}
在 contacts.proto 导入 gender.proto 文件,并使用其中定义的 message Gender。
创建通讯录 2.0 版本
通讯录 2.x 的需求是向⽂件中写⼊通讯录列表,以上我们只是定义了⼀个联系⼈的消息,并不能存放通讯录列表,所以还需要在完善⼀下 contacts.proto (终版通讯录 2.0):
syntax = "proto3";
package contacts;
message PeopleInfo
{
string name = 1;
int32 age = 2;
repeated string phone_numbers = 3;
}
message Contacts
{
repeated PeopleInfo contacts = 1;
}
接着进⾏⼀次编译:
protoc --cpp_out=. contacts.proto
编译后⽣成的
contacts.pb.h contacts.pb.cc
会将上一次的生成⽂件覆盖掉
通讯录 2.0 的写入实现
write.cc
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void AddPeopleInfo(contacts::PeopleInfo *people_info_ptr)
{
cout << "-------------新增联系⼈-------------" << endl;
cout << "请输⼊联系⼈姓名: " << endl;
string name;
getline(cin, name);
people_info_ptr->set_name(name);
cout << "请输⼊联系⼈年龄: " << endl;
int age;
cin >> age;
people_info_ptr->set_age(age);
cin.ignore(256, '\n');
for (int i = 1;; i++)
{
cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增):";
string phone;
getline(cin, phone);
if (phone.empty())
break;
auto phone_numbers = people_info_ptr->add_phone_numbers();
phone_numbers->append(phone);
}
cout << "-----------添加联系⼈成功-----------" << endl;
}
int main()
{
// 先读取已存在的 contacts
Contacts contacts;
fstream in("contact.bin", ios::in | ios::binary );
if (!in)
{
cout << "打开文件失败!" << endl;
return -1;
}
if (!contacts.ParseFromIstream(&in))
{
cout << "反序列化失败!" << endl;
return -1;
}
AddPeopleInfo(contacts.add_contacts());
// 向磁盘⽂件写⼊新的 contacts
fstream out("contact.bin", ios::out | ios::trunc | ios::binary);
if (!contacts.SerializeToOstream(&out))
{
cout << "序列化失败!" << endl;
in.close();
out.close();
return -1;
}
in.close();
out.close();
return 0;
}
makefile
write:write.cc contacts.pb.cc
g++ -o $@ $^ -std=c++17 -lprotobuf
.PHONY:clean
clean:
rm -f write
通讯录 2.0 的读取实现
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void PrintfContacts(const contacts::Contacts &contacts)
{
cout<<contacts.contacts_size();
for (int i = 0; i < contacts.contacts_size(); i++)
{
cout << "-------------联系⼈" << i + 1 << "-------------" << endl;
const PeopleInfo &info = contacts.contacts(i);
cout << "联系⼈姓名: " << info.name() << endl;
cout << "联系⼈年龄: " << info.age() << endl;
int j = 1;
for (auto phone : info.phone_numbers())
{
cout << "联系⼈电话" << j << ": " << phone << endl;
}
}
}
int main()
{
Contacts contacts;
fstream in("contact.bin", ios::in | ios::binary);
if (!in)
{
cout << "打开文件失败!" << endl;
return -1;
}
if (!contacts.ParseFromIstream(&in))
{
cout << "反序列化失败!" << endl;
in.close();
return -1;
}
PrintfContacts(contacts);
in.close();
return 0;
}
makefile
.PHONY:all
all:write read
write:write.cc contacts.pb.cc
g++ -o $@ $^ -std=c++17 -lprotobuf
read:read.cc contacts.pb.cc
g++ -o $@ $^ -std=c++17 -lprotobuf
.PHONY:clean
clean:
rm -f write read
另⼀种读取方法--decode
我们可以⽤
protoc -h
命令来查看 ProtoBuf 为我们提供的所有命令 option。其中 ProtoBuf 提供⼀个命令选项
--decode
,表⽰从标准输⼊中读取给定类型的⼆进制消息,并将其以⽂本格式写⼊标准输出。 消息类型必须在 .proto ⽂件或导⼊的⽂件中定义。
protoc --decode=contacts.Contacts contacts.proto < contact.bin