1.字段规则
消息的字段可以用下面几种规则来修饰:
●singular: 消息中可以包含该字段零次或一次(不超过一次)。proto3语法中,字段默认使用该
规则。
●repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理
解为定义了一个数组。
更新contacts.proto,PeopleInfo 消息中新增phone_ numbers 字段,表示一个联系人有多个
号码,可将其设置为repeated,写法如下:
syntax = "proto3"
package contacts;
message PeopleInfo {
string name = 1;
int32 age=2;
repeated string phone_number = 3;
}
2.消息类型的定义与使用
2.1定义
在单个.proto文件中可以定义多个消息体,且支持定义嵌套类型的消息(任意多层) 。每个消息体中
的字段编号可以重复。
更新contacts.proto,我们可以将phone_ number 提取出来,单独成为一一个消息:
// -------------------------- 嵌套写法 -------------------------
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
}
}
// -------------------------- 非嵌套写法 -------------------------
message Phone {
string number = 1;
}
message PeopleInfo {
string name = 1;
int32 age = 2;
}
2.2使用
●消息类型可作为字段类型使用
contacts.proto
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
}
repeated Phone phone = 3;
}
可导入其他.proto文件的消息并使用
例如Phone消息定义在phone.proto文件中:
syntax = "proto3";
package phone;
message Phone {
string number = 1;
}
contacts.proto中的PeopleInfo使用Phone 消息:
import "phone.proto"; // 使⽤ import 将 phone.proto ⽂件导⼊进来 !!!
message PeopleInfo {
string name = 1;
int32 age = 2;
// 引⼊的⽂件声明了package,使⽤消息时,需要⽤ ‘命名空间.消息类型’ 格式
repeated phone.Phone phone = 3;
}
注:在proto3文件中可以导入proto2消息类型并使用它们,反之亦然。
2.3创建通讯录2.0版本
通讯录2.x的需求是向文件中写入通讯录列表,以上我们只是定义了一个联系人的消息,并不能存放通讯录列表,所以还需要在完善一下contacts.proto (终版通讯录2.0):
//联系人
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
}
repeated Phone phone = 3;
}
//通讯录:
message Contacts {
repeated PeopleInfo contacts = 1;
}
接着进行一次编译:
protoc --cpp_out=. contacts.proto
编译后生成的contacts. pb.h contacts.pb.cc 会将前面的生成文件覆盖掉。
contacts.pb.h更新的部分代码展示
// 新增了 PeopleInfo_Phone 类
class PeopleInfo_Phone final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:
using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
void CopyFrom(const PeopleInfo_Phone &from);
using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
void MergeFrom(const PeopleInfo_Phone &from)
{
PeopleInfo_Phone::MergeImpl(*this, from);
}
static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName()
{
return "PeopleInfo.Phone";
}
// string number = 1;
void clear_number();
const std::string &number() const;
template <typename ArgT0 = const std::string &, typename... ArgT>
void set_number(ArgT0 &&arg0, ArgT... args);
std::string *mutable_number();
PROTOBUF_NODISCARD std::string *release_number();
void set_allocated_number(std::string *number);
};
// 更新了 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:
using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
void CopyFrom(const PeopleInfo &from);
using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
void MergeFrom(const PeopleInfo &from)
{
PeopleInfo::MergeImpl(*this, from);
}
static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName()
{
return "PeopleInfo";
}
typedef PeopleInfo_Phone Phone;
// repeated .PeopleInfo.Phone phone = 3;
int phone_size() const;
void clear_phone();
::PeopleInfo_Phone *mutable_phone(int index);
::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<::PeopleInfo_Phone> *
mutable_phone();
const ::PeopleInfo_Phone &phone(int index) const;
::PeopleInfo_Phone *add_phone();
const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<::PeopleInfo_Phone> &
phone() const;
};
// 新增了 Contacts 类
class Contacts final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:
using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
void CopyFrom(const Contacts &from);
using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
void MergeFrom(const Contacts &from)
{
Contacts::MergeImpl(*this, from);
}
static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName()
{
return "Contacts";
}
// repeated .PeopleInfo contacts = 1;
int contacts_size() const;
void clear_contacts();
::PeopleInfo *mutable_contacts(int index);
::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<::PeopleInfo> *
mutable_contacts();
const ::PeopleInfo &contacts(int index) const;
::PeopleInfo *add_contacts();
const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField<::PeopleInfo> &
contacts() const;
};
上述的例子中: .
●每个字段都有一个clear_方法,可以将字段重新设置回empty状态。
●每个字段都有设置和获取的方法,获取方法的方法名称与小写字段名称完全相同。但如果是消息类型的字段,其设置方法为mutable_方法,返回值为消息类型的指针,这类方法会为我们开辟好空间,可以直接对这块空间的内容进行修改。
对于使用repeated修饰的字段,也就是数组类型,pb 为我们提供了add_方法来新增一个值,
并且提供了_ size 方法来判断数组存放元素的个数。
2.3.1通讯录2.0的写入实现
write.cc (通讯录2.0)
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void AddPeopleInfo(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;
PeopleInfo_Phone* phone = people->add_phone();
phone->set_number(number);
}
cout << "-------------添加联系人成功-------------" << endl;
}
int main()
{
Contacts contacts;
//先读取已经存在的contacts:
fstream input("contacts.bin",ios::in | ios ::binary);
if(!input) {
cout << "file not exist,create new file"<< endl;
} else if(!contacts.ParseFromIstream(&input)) {
cerr << "Parse failed!" << endl;
input.close();
return -1;
}
//向通讯录添加一个联系人:
AddPeopleInfo(contacts.add_contacts());
//将通讯录写入到本地文件中:
fstream output("contacts.bin",ios::out | ios :: binary | ios :: trunc);
if(!contacts.SerializePartialToOstream(&output)) {
cerr << "write failed !" << endl;
input.close();
output.close();
return -1;
}
cout << "write sucess !" << endl;
input.close();
output.close();
return 0;
}
makefile:
write:write.cc contacts.pb.cc
g++ write.cc contacts.pb.cc -o write -std=c++11 -lprotobuf
.PHONY:clean
clean:
rm -rf write
运行write:
2.3.2通讯录2.0的读取实现
read.cc (通讯录2.0)
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void PrintContacts(const Contacts& contacts) {
for(int i = 0; i < contacts.contacts_size(); ++i) {
const PeopleInfo people = contacts.contacts(i);
cout << "------------联系⼈" << i+1 << "------------" << endl;
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
int j = 1;
for(auto& phone : people.phone()) {
cout << "电话" << j++ << ": " << phone.number() << endl;
}
}
}
int main()
{
Contacts contacts;
fstream input("contacts.bin",ios::in | ios :: binary);
if(!contacts.ParseFromIstream(&input)){
cerr << "Parse failed!" << endl;
input.close();
return -1;
}
//打印contacts
PrintContacts(contacts);
input.close();
return 0;
}
makefile:
all:write read
write:write.cc contacts.pb.cc
g++ write.cc contacts.pb.cc -o write -std=c++11 -lprotobuf
read:read.cc contacts.pb.cc
g++ read.cc contacts.pb.cc -o read -std=c++11 -lprotobuf
.PHONY:clean
clean:
rm -rf write read
make后运行read
3.enum类型
3.1定义规则
语法支持我们定义枚举类型并使用。在.proto文件中枚举类型的书写规范为:
枚举类型名称:
使用驼峰命名法,首字母大写。例如: MyEnum
常量值名称:
全大写字母,多个字母之间用_连接。 例如: ENUM_ CONST = 0;
我们可以定义一个名为PhoneType的枚举类型,定义如下:
enum PhoneType {
MB = 0;
TEL = 1;
}
要注意枚举类型的定义有以下几种规则:
1. 0值常量必须存在,且要作为第一-个元素。这是为了与proto2的语义兼容:第一个元素作为默认
值,且值为0。
2.枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
3.枚举的常量值在32位整数的范围内。但因负值无效因而不建议使用(与编码规则有关)。
3.2定义时注意
将两个‘具有相同枚举值 名称’的枚举类型放在 单个.proto文件下测试时,编译后会报错:某某某常
量已经被定义!所以这里要注意:
●同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
●单个.proto 文件下,最外层枚举类型和嵌套枚举类型,不算同级。
●多个.proto文件下,若一个文件引入了其他文件,且每个文件都未声明package,每个proto文
件中的枚举类型都在最外层,算同级。
●多个.proto文件下,若一个文件引入了其他文件,且每个文件都声明了package,不算同级。
// ---------------------- 情况1:同级枚举类型包含相同枚举值名称--------------------
enum PhoneType
{
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
enum PhoneTypeCopy
{
MP = 0; // 移动电话 // 编译后报错:MP 已经定义
}
// ---------------------- 情况2:不同级枚举类型包含相同枚举值名称-------------------
enum PhoneTypeCopy
{
MP = 0; // 移动电话 // ⽤法正确
}
message Phone
{
string number = 1; // 电话号码
enum PhoneType
{
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
}
// ---------------------- 情况3:多⽂件下都未声明package--------------------
// phone1.proto
import "phone1.proto"
enum PhoneType
{
MP = 0; // 移动电话 // 编译后报错:MP 已经定义
TEL = 1; // 固定电话
}
// phone2.proto
enum PhoneTypeCopy
{
MP = 0; // 移动电话
}
// ---------------------- 情况4:多⽂件下都声明了package--------------------
// phone1.proto
import "phone1.proto" package phone1;
enum PhoneType
{
MP = 0; // 移动电话 // ⽤法正确
TEL = 1; // 固定电话
}
// phone2.proto
package phone2;
enum PhoneTypeCopy
{
MP = 0; // 移动电话
}
3.3升级通讯录至2.1版本
更新contacts.proto (通讯录2.1),新增枚举字段并使用,更新内容如下:
//联系人
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MB = 0;
TEL = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
}
//通讯录:
message Contacts {
repeated PeopleInfo contacts = 1;
}
编译:
protoc --cpp_out=. contacts.proto
// 新⽣成的 PeopleInfo_Phone_PhoneType 枚举类
enum PeopleInfo_Phone_PhoneType : int
{
PeopleInfo_Phone_PhoneType_MP = 0,
PeopleInfo_Phone_PhoneType_TEL = 1,
PeopleInfo_Phone_PhoneType_PeopleInfo_Phone_PhoneType_INT_MIN_SENTINEL_DO_NOT_U
SE_ = std::numeric_limits<int32_t>::min(),
PeopleInfo_Phone_PhoneType_PeopleInfo_Phone_PhoneType_INT_MAX_SENTINEL_DO_NOT_U
SE_ = std::numeric_limits<int32_t>::max()
};
// 更新的 PeopleInfo_Phone 类
class PeopleInfo_Phone final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:
typedef PeopleInfo_Phone_PhoneType PhoneType;
static inline bool PhoneType_IsValid(int value)
{
return PeopleInfo_Phone_PhoneType_IsValid(value);
}
template <typename T>
static inline const std::string &PhoneType_Name(T enum_t_value) { ... }
static inline bool PhoneType_Parse(
::PROTOBUF_NAMESPACE_ID::ConstStringParam name, PhoneType *value) { ... }
// .contacts.PeopleInfo.Phone.PhoneType type = 2;
void clear_type();
::contacts::PeopleInfo_Phone_PhoneType type() const;
void set_type(::contacts::PeopleInfo_Phone_PhoneType value);
};
上述的代码中:
对于在.proto文件中定义的枚举类型,编译生成的代码中会含有与之对应的枚举类型、校验枚举
值是否有效的方法__IsValid、 以及获取枚举值名称的方法_ Name。
●对于使用了枚举类型的字段,包含设置和获取字段的方法,已经清空字段的方法clear__。
更新write.cc(通讯录2.1)
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void AddPeopleInfo(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;
PeopleInfo_Phone *phone = people->add_phone();
phone->set_number(number);
//更新代码:
cout << "选择此电话类型 (1、移动电话 2、固定电话) : " ;
int type;
cin >> type;
cin.ignore(256,'\n');
switch (type) {
case 1:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MB);
break;
case 2:
phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
break;
default:
cout << "输入有误,请重新输入" << endl;
}
}
cout << "-------------添加联系人成功-------------" << endl;
}
int main()
{
Contacts contacts;
// 先读取已经存在的contacts:
fstream input("contacts.bin", ios::in | ios ::binary);
if (!input)
{
cout << "file not exist,create new file" << endl;
}
else if (!contacts.ParseFromIstream(&input))
{
cerr << "Parse failed!" << endl;
input.close();
return -1;
}
// 向通讯录添加一个联系人:
AddPeopleInfo(contacts.add_contacts());
// 将通讯录写入到本地文件中:
fstream output("contacts.bin", ios::out | ios ::binary | ios ::trunc);
if (!contacts.SerializePartialToOstream(&output))
{
cerr << "write failed !" << endl;
input.close();
output.close();
return -1;
}
cout << "write sucess !" << endl;
input.close();
output.close();
return 0;
}
更新read.cc(通讯录2.1)
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
void PrintContacts(const Contacts& contacts) {
for(int i = 0; i < contacts.contacts_size(); ++i) {
const PeopleInfo people = contacts.contacts(i);
cout << "------------联系⼈" << i+1 << "------------" << endl;
cout << "联系人姓名:" << people.name() << endl;
cout << "联系人年龄:" << people.age() << endl;
int j = 1;
for(auto& phone : people.phone()) {
cout << "电话" << j++ << ": " << phone.number();
//更新代码:
cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
}
}
}
int main()
{
Contacts contacts;
fstream input("contacts.bin",ios::in | ios :: binary);
if(!contacts.ParseFromIstream(&input)){
cerr << "Parse failed!" << endl;
input.close();
return -1;
}
//打印contacts
PrintContacts(contacts);
input.close();
return 0;
}
运行截图:
4. Any类型
字段还可以声明为Any类型,可以理解为泛型类型。使用时可以在Any中存储任意消息类型。Any类型的字段也用repeated来修饰。
Any类型是google已经帮我们定义好的类型,在安装ProtoBuf时,其中的include目录下查找所有
google已经定义好的.proto文件。
4.1升级通讯录至2.2版本
通讯录2.2版本会新增联系人的地址信息,我们可以使用any类型的字段来存储地址信息。
更新contacts.proto (通讯录2.2),更新内容如下:
import "google/protobuf/any.proto"; //引入any.proto文件
//地址:
message Address {
string home_address = 1; //家庭住址
string unit_address = 2; //单位住址
}
//联系人
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MB = 0;
TEL = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4;
}
//通讯录:
message Contacts {
repeated PeopleInfo contacts = 1;
}
编译
protoc --cpp_ out=. contacts.proto
contacts.pb.h更新的部分代码展示:
// 新⽣成的 Address 类
class Address final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:
using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
void CopyFrom(const Address &from);
using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
void MergeFrom(const Address &from)
{
Address::MergeImpl(*this, from);
}
// string home_address = 1;
void clear_home_address();
const std::string &home_address() const;
template <typename ArgT0 = const std::string &, typename... ArgT>
void set_home_address(ArgT0 &&arg0, ArgT... args);
std::string *mutable_home_address();
PROTOBUF_NODISCARD std::string *release_home_address();
void set_allocated_home_address(std::string *home_address);
// string unit_address = 2;
void clear_unit_address();
const std::string &unit_address() const;
template <typename ArgT0 = const std::string &, typename... ArgT>
void set_unit_address(ArgT0 &&arg0, ArgT... args);
std::string *mutable_unit_address();
PROTOBUF_NODISCARD std::string *release_unit_address();
void set_allocated_unit_address(std::string *unit_address);
};
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:
// .google.protobuf.Any data = 4;
bool has_data() const;
void clear_data();
const ::PROTOBUF_NAMESPACE_ID::Any &data() const;
PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any *release_data();
::PROTOBUF_NAMESPACE_ID::Any *mutable_data();
void set_allocated_data(::PROTOBUF_NAMESPACE_ID::Any *data);
};
上述的代码中,对于Any类型字段:
●设置和获取:获取方法的方法名称与小写字段名称完全相同。设置方法可以使用mutable_ 方
法,返回值为Any类型的指针,这类方法会为我们开辟好空间,可以直接对这块空间的内容进行
修改。
之前讲过,我们可以在Any字段中存储任意消息类型,这就要涉及到任意消息类型和Any类型的互
转。这部分代码就在Google为我们写好的头文件any.pb.h中。对any.pb. h部分代码展示:
class PROTOBUF_EXPORT Any final : public ::PROTOBUF_NAMESPACE_ID::Message
{
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message &message)
{
...
}
bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message *message) const
{
...
}
template <typename T>
bool Is() const
{
return _impl_._any_metadata_.Is<T>();
}
};
解释:
使用 PackFrom() ⽅法可以将任意消息类型转为 Any 类型。
使用 UnpackTo() ⽅法可以将 Any 类型转回之前设置的任意消息类型。
使用 Is() 方法可以用来判断存放的消息类型是否为 typename T。
更新write.cc(通讯录2.2)
Address address;
cout << "请输入联系人家庭地址:";
string home_address;
getline(cin,home_address);
cin.ignore(256,'\n');
address.set_home_address(home_address);
cout << "请输入联系人单位地址:";
string unit_address;
getline(cin,unit_address);
cin.ignore(256,'\n');
address.set_unit_address(unit_address);
google::protobuf::Any* data = people->mutable_data();
data->PackFrom(address);
更新read.cc(通讯录2.2):
if(people.has_data() && people.data().Is<Address>()) {
Address 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;
}
}
运行截图:
5. oneof类型
如果消息中有很多可选字段,并且将来同时只有一个字段会被设置,那么就可以使用oneof 加强这
个行为,也能有节约内存的效果。
5.1升级通讯录至2.3版本
通讯录2.3版本想新增联系人的其他联系方式,比如qq或者微信号二选一, 我们就可以使用oneof字段来加强多选一这个行为。oneof 字段定义的格式为: oneof 字段名{字段1;字段2; ... }
更新contacts.proto (通讯录2.3),更新内容如下:
mport "google/protobuf/any.proto"; //引入any.proto文件
//地址:
message Address {
string home_address = 1; //家庭住址
string unit_address = 2; //单位住址
}
//联系人
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MB = 0;
TEL = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4;
oneof other_Ways {
string qq = 5;
string weixin = 6;
}
}
//通讯录:
message Contacts {
repeated PeopleInfo contacts = 1;
}
注意:
●可选字段中的字段编号,不能与非可选字段的编号冲突。
不能在oneof中使用repeated字段。
●将来在设置oneof字段中值时,如果将oneof中的字段设置多个,那么只会保留最后一次设置的成
员,之前设置的oneof成员会自动清除。
编译
protoc --cpp_ out=. contacts.proto
contacts.pb.h更新的部分代码展示:
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
enum OtherContactCase {
kQq = 5,
kWeixin = 6,
OTHER_CONTACT_NOT_SET = 0,
};
// string qq = 5;
bool has_qq() const;
void clear_qq();
const std::string& qq() const;
template <typename ArgT0 = const std::string&, typename... ArgT>
void set_qq(ArgT0&& arg0, ArgT... args);
std::string* mutable_qq();
PROTOBUF_NODISCARD std::string* release_qq();
void set_allocated_qq(std::string* qq);
// string weixin = 6;
bool has_weixin() const;
void clear_weixin();
const std::string& weixin() const;
template <typename ArgT0 = const std::string&, typename... ArgT>
void set_weixin(ArgT0&& arg0, ArgT... args);
std::string* mutable_weixin();
PROTOBUF_NODISCARD std::string* release_weixin();
void set_allocated_weixin(std::string* weixin);
void clear_other_contact();
OtherContactCase other_contact_case() const;
};
上述的代码中,对于oneof字段:
●会将 oneof中的多个字段定义为一个枚举类型。
●设置和获取:对oneof内的字段进行常规的设置和获取即可,但要注意只能设置- -个。 如果设置
多个,那么只会保留最后一次设置的成员。
●清空oneof字段: clear_ 方法
●获取当前设置了哪个字段:_ case方法
更新write.cc (通讯录2.3),更新内容如下:
cout << "选择添加⼀个其他联系⽅式 (1、qq号 2、微信号) : " ;
int other_contacts;
cin >> other_contacts;
cin.ignore(256,'\n');
if(1 == other_contacts) {
cout << "请输入qq号: " ;
string qq;
getline(cin,qq);
cin.ignore(256,'\n');
people->set_qq(qq);
} else if(2 == other_contacts) {
cout << "请输入微信号:";
string weixin;
getline(cin,weixin);
cin.ignore(256,'\n');
people->set_weixin(weixin);
} else {
cout << "非法选择,设置失败" << endl;
}
更新read.cc (通讯录2.3),更新内容如下:
switch (people.other_Ways_case()) {
case PeopleInfo::OtherWaysCase::kQq:
cout << "qq号: " << people.qq() << endl;
break;
case PeopleInfo::OtherWaysCase::kWeixin:
cout << "微信号:" << people.weixin() << endl;
break;
case PeopleInfo::OtherWaysCase::OTHER_WAYS_NOT_SET:
break;
}
运行截图:
6. map类型
语法支持创建一个关联映射字段,也就是可以使用map类型去声明字段类型,格式为:
map<key_ type, value_type> map _field = N;
要注意的是:
●key_ type是除了float和bytes类型以外的任意标量类型。 value_type可以是任意类型。
map字段不可以用repeated修饰
●map中存入的元素是无序的
6.1升级通讯录至2.4版本
最后,通讯录2.4版本想新增联系人的备注信息,我们可以使用map类型的字段来存储备注信息。
更新contacts.proto (通讯录2.4),更新内容如下:
import "google/protobuf/any.proto"; //引入any.proto文件
//地址:
message Address {
string home_address = 1; //家庭住址
string unit_address = 2; //单位住址
}
//联系人
message PeopleInfo {
string name = 1;
int32 age = 2;
message Phone {
string number = 1;
enum PhoneType {
MB = 0;
TEL = 1;
}
PhoneType type = 2;
}
repeated Phone phone = 3;
google.protobuf.Any data = 4;
oneof other_Ways {
string qq = 5;
string weixin = 6;
}
map<string,string> remark = 7; //备注
}
//通讯录:
message Contacts {
repeated PeopleInfo contacts = 1;
}
编译
protoc --cpp_ out=. contacts.proto
contacts.pb.h更新的部分代码展示:
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
// map<string, string> remark = 7;
int remark_size() const;
void clear_remark();
const ::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >&
remark() const;
::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >*
mutable_remark();
};
上述的代码中,对于Map类型的字段: .
●清空map: clear_ 方法
●设置和获取:获取方法的方法名称与小写字段名称完全相同。设置方法为mutable_ 方法,返回
值为Map类型的指针,这类方法会为我们开辟好空间,可以直接对这块空间的内容进行修改。
更新write.cc (通讯录2.4),更新内容如下:
for(int i = 1;; ++i) {
cout << "请输⼊备注" << i << "标题 (只输入回车完成备注): ";
string remark_key;
getline(cin,remark_key);
if(remark_key.empty()) {
break;
}
cout << "请输入备注:" << i << "内容:";
string remark_value;
getline(cin,remark_value);
people->mutable_remark()->insert({remark_key,remark_value});
}
更新read.cc (通讯录2.4),更新内容如下:
if(people.remark_size()) {
cout << "备注信息:" << endl;
}
for(auto it = people.remark().cbegin(); it != people.remark().cend(); ++it) {
cout << " " << it->first << " " << it->second << endl;
}
运行截图:
到此,我们对通讯录2.x要求的任务全部完成。在这个过程中我们将通讯录升级到了2.4 版本,同时对ProtoBuf的使用也进一步熟练了 ,并且也掌握了ProtoBuf的proto3语法支持的大部分类型及其使
用,但只是正常使用还是完全不够的。通过接下来的学习,我们就能更进一-步了解到ProtoBuf深入的内容。