文章目录
- 1、字段规则
- 2、消息类型的定义和使用
- 3、enum的使用
- 4、any类型
- 2、oneof类型
- 3、map类型
1、字段规则
消息的字段可以用以下几种规则来修饰:
- singular:消息中可以包含该字段零次或一次(不超过一次)。proto3语法中,字段默认使用该规则
- repeated:消息中可以包含该字段任意多次(包括0次),其中重复值的顺序会保留。可以理解为定义了一个数组
更新contacts.proto,PeopleInfo 消息中新增 phone 字段,表示一个联系人有多个号码,可将其设置为repeated,写法如下:
//首行:语法指定行
syntax = "proto3";
package contacts2;
message Phone
{
string number = 1;
}
//定义联系人message
message PeopleInfo
{
string name = 1; //姓名
int32 age = 2; //年龄
repeated Phone phone = 3;
}
message 是可以嵌套的,并且嵌套层数没有限制
//定义联系人message
message PeopleInfo
{
string name = 1; //姓名
int32 age = 2; //年龄
message Phone
{
string number = 1;
}
repeated Phone phone = 3;
}
如果Phone定义在其他proto文件中,则应该为
#phone.proto
// 首行:语法指定行
syntax = "proto3";
package phone;
message Phone
{
string number = 1;
}
#contacts.proto
//首行:语法指定行
syntax = "proto3";
package contacts2;
import "phone.proto";
//定义联系人message
message PeopleInfo
{
string name = 1; //姓名
int32 age = 2; //年龄
repeated phone.Phone phone = 3;
}
2、消息类型的定义和使用
将通讯录序列化,并写入文件中
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
void AddPeopleInfo(contacts2::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;
}
contacts2::PeopleInfo_Phone *phone = people->add_phone();
phone->set_number(number);
}
cout << "添加联系人成功" << endl;
}
int main()
{
contacts2::Contacts 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;
}
return 0;
}
编译:
g++ -o write write.cc contacts.pb.cc -std=c++11 -lprotobuf
运行:
从文件中将通讯录解析出来,并进行打印
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
void PrintContacts(contacts2::Contacts &contacts)
{
for (int i = 0; i < contacts.contacts_size(); ++i)
{
cout << "联系人" << i + 1 << endl;
const ::contacts2::PeopleInfo &people = contacts.contacts(i);
cout << "姓名" << people.name() << endl;
cout << "年龄" << people.age() << endl;
for (int j = 0; j < people.phone_size(); ++j)
{
const ::contacts2::PeopleInfo_Phone &phone = people.phone(j);
cout << "电话" << j + 1 << ":" << phone.number() << endl;
}
}
}
int main()
{
contacts2::Contacts contacts;
// 读取本地已存在的文件
fstream input("contacts.bin", ios::in | ios::binary);
if (!contacts.ParseFromIstream(&input))
{
cerr << "parse error" << endl;
input.close();
return -1;
}
// 打印通讯录列表
PrintContacts(contacts);
return 0;
}
编译:
g++ -o read read.cc contacts.pb.cc -std=c++11 -lprotobuf
运行:
因为protobuf是以二进制进行序列化,存储到文件中,如果需要查看,可以采用命令:
protoc --decode=contacts2.Contacts contacts.proto < contacts.bin
contacts2.Contacts:表示哪个包中的message
contacts.proto:表示对应的文件
contacts.bin:表示对应的二进制文件
结果:
可以使用命令 protoc -h 查看其他参数选项
3、enum的使用
当需要定义一个消息类型的时候,可能想为一个字段指定某“预定义值序列”中的一个值,就比如通讯录中的电话类型有移动电话和固定电话,这时候可以通过枚举实现。
enum PhoneType
{
MP = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
TEL = 1;
}
枚举类型不仅可以定义在protobuf文件的最外层,也可以定义在message内部
message phone
{
enum PhoneType
{
MP = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
TEL = 1;
}
}
如果在同一个protobuf文件中,不同enum有相同的字段名,在编译时则会报错,例如:
syntax = "proto3";
enum PhoneType
{
MP = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
TEL = 1;
}
enum PhoneTypeCopy
{
MP = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
TEL = 1;
}
如果两个protobuf文件使用了相同的枚举常量名称,编译时也会报错:
#test_enum.proto
syntax = "proto3";
import "test_enum2.proto";
enum PhoneType
{
MP = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
TEL = 1;
}
#test_enum2.proto
syntax = "proto3";
enum PhoneTypeCopy
{
MP = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
}
解决方法:给其中任意一个文件添加package,或者两个文件都添加package
对通讯录进行升级
//首行:语法指定行
syntax = "proto3";
package contacts2;
//定义联系人 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; //电话信息
}
枚举的默认值是编号为0的值
更新对应的通讯录
//write.cc
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
//......
//和前面一致
for (int i = 0;; ++i)
{
//......
//和前面一致
phone->set_number(number);
cout << "请输入该电话类型(1.移动电话 2.固定电话): ";
int type;
cin >> type;
cin.ignore(256, '\n');
switch (type)
{
case 1:
phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
break;
case 2:
phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
break;
default:
cout << "选择有误" << endl;
break;
}
}
cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
for (int i = 0; i < contacts.contacts_size(); ++i)
{
//......
//和前面一致
for (int j = 0; j < people.phone_size(); ++j)
{
const ::contacts2::PeopleInfo_Phone &phone = people.phone(j);
cout << "电话" << j + 1 << ":" << phone.number();
// 联系人电话1:123456789 (MP)
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
}
}
4、any类型
在 Protocol Buffers(Protobuf)中,Any 类型是一种特殊的消息类型,用于存储任意类型的数据。它提供了一种灵活的方式来在消息中嵌入其他类型的消息,而无需提前知道具体的消息结构。
可以将Any类型理解为泛型类型
使用Any类型,需要导入google/protobuf/any.proto
给PeopleInfo添加Address
对contacts.proto进行更新
//首行:语法指定行
syntax = "proto3";
package contacts2;
import "google/protobuf/any.proto";
message Address
{
string home_address = 1; //家庭住址
string unit_address = 2; //单位地址
}
//定义联系人 message
message PeopleInfo
{
//......
//和前面一致
google.protobuf.Any data = 4;
}
更新对应的通讯录
//write.cc
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
//......
//和前面一致
contacts2::Address address;
cout<<"请输入联系人的家庭地址"<<endl;
string home_address;
getline(cin, home_address);
address.set_home_address(home_address);
cout<<"请输入联系人的单位地址"<<endl;
string unit_address;
getline(cin, unit_address);
address.set_unit_address(unit_address);
//将Address对象转换为Any
if(!people->mutable_data()->PackFrom(address))
{
cerr << "转换失败" << endl;
}
cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
for (int i = 0; i < contacts.contacts_size(); ++i)
{
//......
//和前面一致
// 判断有没有为data进行设值,并且为Address类型
if (people.has_data() && people.data().Is<contacts2::Address>())
{
contacts2::Address address;
// 将people中的data转换为address
people.data().UnpackTo(&address);
if (!address.home_address().empty())
{
cout << "联系人家庭住址: " << address.home_address() << endl;
}
if (!address.unit_address().empty())
{
cout << "联系人单位住址: " << address.unit_address() << endl;
}
}
}
}
2、oneof类型
在 Protocol Buffers(Protobuf)中,oneof 是一种特殊的字段声明,它允许在一个消息中定义多个互斥的字段,只能同时选择其中一个进行赋值。
使用 oneof 类型可以有效地在消息结构中表示多个可能的字段,但只允许其中一个字段被设置为非默认值。这在某些情况下可以帮助简化消息的定义和使用,并节省存储空间。
比如在通讯录中,除了电话号码之外,还需要保留一种通信方式,比如微信和qq,但两者只能选一种,此时就能使用 oneof 类型
对contacts.proto进行修改
message PeopleInfo
{
//......
//和前面一致
oneof other_contact
{
//repeated string qq = 5; 不能使用repeated,否则编译时会报错
string qq = 5;
string wechat = 6;
}
}
注意:oneof 中的字段不能使用 repeated 进行修饰
更改对应的通讯录
//write.cc
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
//......
//和前面一致
cout << "请选择要添加的其他联系方式(1.qq号 2.微信号):";
int other_contact;
cin >> other_contact;
cin.ignore(256, '\n');
if (1 == other_contact)
{
cout << "请输入联系人qq号:";
string qq;
getline(cin, qq);
people->set_qq(qq);
}
else if (2 == other_contact)
{
cout << "请输入联系人微信号:";
string wechat;
getline(cin, wechat);
people->set_wechat(wechat);
}
else
{
cout << "选择有误,未成功设置其他联系方式!" << endl;
}
cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
for (int i = 0; i < contacts.contacts_size(); ++i)
{
//......
//和前面一致
switch (people.other_contact_case())
{
case contacts2::PeopleInfo::OtherContactCase::kQq:
cout << "联系人qq: " << people.qq() << endl;
break;
case contacts2::PeopleInfo::OtherContactCase::kWechat:
cout << "联系人微信: " << people.wechat() << endl;
break;
default:
break;
}
}
}
3、map类型
在 Protocol Buffers(Protobuf)中,map用于表示键值对的集合。它提供了一种方便的方式来在消息中存储和传输映射关系的数据。
在 Protobuf 中,Map 类型需要指定键(key)和值(value)的类型。键必须是基本数据类型(例如 int32、string)或枚举类型,而值可以是任意的消息类型。map中的数据是无序的
并且map字段不能用 repeated 修饰
对contacts.proto进行修改,给PeopleInfo添加备注信息
message PeopleInfo
{
//......
//和前面一致
map<string, string> remark = 7; //备注信息
}
更改对应的通讯录
//write.cc
void AddPeopleInfo(contacts2::PeopleInfo *people)
{
//......
//和前面一致
for(int i = 0;; ++i)
{
cout<<"请输入备注"<<i+1<<"标题(只输入回车完成备注新增):";
string remark_key;
getline(cin, remark_key);
if(remark_key.empty())
{
break;
}
cout<<"请输入备注"<<i+1<<"内容:";
string remark_value;
getline(cin, remark_value);
people->mutable_remark()->insert({remark_key, remark_value});
}
cout << "添加联系人成功" << endl;
}
//read.cc
void PrintContacts(contacts2::Contacts &contacts)
{
for (int i = 0; i < contacts.contacts_size(); ++i)
{
//......
//和前面一致
if (people.remark_size())
{
cout << "备注信息:" << endl;
}
for (auto it = people.remark().cbegin(); it != people.remark().end(); ++it)
{
cout << " " << it->first << ":" << it->second << endl;
}
}
}