Proto3语法详解01

1.字段规则

消息的字段可以用下面几种规则来修饰:
●singular: 消息中可以包含该字段零次或一次(不超过一次)。proto3语法中,字段默认使用该
规则。
●repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理
解为定义了一个数组。
更新contacts.proto,PeopleInfo 消息中新增phone_ numbers 字段,表示一个联系人有多个
号码,可将其设置为repeated,写法如下:

cpp 复制代码
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 提取出来,单独成为一一个消息:

cpp 复制代码
// -------------------------- 嵌套写法 -------------------------
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

cpp 复制代码
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    message Phone {
        string number = 1;
    }
    repeated Phone phone = 3;
}

可导入其他.proto文件的消息并使用
例如Phone消息定义在phone.proto文件中:

cpp 复制代码
syntax = "proto3";
package phone;

message Phone {
    string number = 1;
}

contacts.proto中的PeopleInfo使用Phone 消息:

cpp 复制代码
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):

cpp 复制代码
//联系人
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    message Phone {
        string number = 1;
    }
    repeated Phone phone = 3;
}

//通讯录:
message Contacts {
    repeated PeopleInfo contacts = 1;
}

接着进行一次编译:

cpp 复制代码
protoc --cpp_out=. contacts.proto 

编译后生成的contacts. pb.h contacts.pb.cc 会将前面的生成文件覆盖掉。
contacts.pb.h更新的部分代码展示

cpp 复制代码
// 新增了 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)

cpp 复制代码
#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:

bash 复制代码
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)

cpp 复制代码
#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:

bash 复制代码
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的枚举类型,定义如下:

cpp 复制代码
enum PhoneType {
    MB = 0;
    TEL = 1;
}

要注意枚举类型的定义有以下几种规则:

  1. 0值常量必须存在,且要作为第一-个元素。这是为了与proto2的语义兼容:第一个元素作为默认
    值,且值为0。
    2.枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
    3.枚举的常量值在32位整数的范围内。但因负值无效因而不建议使用(与编码规则有关)。

3.2定义时注意

将两个'具有相同枚举值 名称'的枚举类型放在 单个.proto文件下测试时,编译后会报错:某某某常
量已经被定义!所以这里要注意:
●同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
●单个.proto 文件下,最外层枚举类型和嵌套枚举类型,不算同级。
●多个.proto文件下,若一个文件引入了其他文件,且每个文件都未声明package,每个proto文
件中的枚举类型都在最外层,算同级。
●多个.proto文件下,若一个文件引入了其他文件,且每个文件都声明了package,不算同级。

cpp 复制代码
// ---------------------- 情况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),新增枚举字段并使用,更新内容如下:

cpp 复制代码
//联系人
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

cpp 复制代码
// 新⽣成的 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)

cpp 复制代码
#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)

cpp 复制代码
#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),更新内容如下:

cpp 复制代码
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更新的部分代码展示:

cpp 复制代码
// 新⽣成的 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部分代码展示:

cpp 复制代码
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)

cpp 复制代码
        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):

cpp 复制代码
        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),更新内容如下:

cpp 复制代码
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更新的部分代码展示:

cpp 复制代码
// 更新的 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),更新内容如下:

cpp 复制代码
        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),更新内容如下:

cpp 复制代码
       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),更新内容如下:

cpp 复制代码
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更新的部分代码展示:

cpp 复制代码
// 更新的 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),更新内容如下:

cpp 复制代码
            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),更新内容如下:

cpp 复制代码
        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深入的内容。

相关推荐
以后不吃煲仔饭11 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师12 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
前端拾光者16 分钟前
利用D3.js实现数据可视化的简单示例
开发语言·javascript·信息可视化
The_Ticker17 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
程序猿阿伟17 分钟前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链
Elastic 中国社区官方博客23 分钟前
Elasticsearch 开放推理 API 增加了对 IBM watsonx.ai Slate 嵌入模型的支持
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
企鹅侠客28 分钟前
ETCD调优
数据库·etcd
Json_1817901448034 分钟前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
傻啦嘿哟35 分钟前
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
开发语言·python·excel
大数据编程之光39 分钟前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink