前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【protobuf】三、proto3语法详解② -- enum、Any、oneof、map类型

【protobuf】三、proto3语法详解② -- enum、Any、oneof、map类型

作者头像
利刃大大
发布2025-02-03 08:06:47
发布2025-02-03 08:06:47
18600
代码可运行
举报
文章被收录于专栏:csdn文章搬运csdn文章搬运
运行总次数:0
代码可运行

Ⅰ. enum类型

一、定义规则

.proto 文件中枚举类型的书写规范为:

  • 枚举类型名称:使用驼峰命名法,首字母大写。 例如: MyEnum
  • 枚举内常量值名称:全大写字母,多个字母之间用 _ 连接。例如: ENUM_CONST = 0;

​ 我们可以定义一个名为 PhoneType 的枚举类型,定义如下:

代码语言:javascript
代码运行次数:0
复制
enum PhoneType {
	MOBILE_PHONE = 0; // 移动电话
	TELEPHONE = 1;	  // 固定电话
}

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

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

二、同名枚举值的注意事项

​ 将两个“具有相同枚举值名称”的枚举类型放在单个 .proto 文件下测试时,编译后会报错:“某某某常量已经被定义”,所以这里要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
  • 单个 proto 文件下,最外层枚举类型和嵌套枚举类型,不算同级。
  • 多个 proto 文件下,若一个文件引入了其他文件,且每个文件 未全部声明 package,每个 proto 文件中的枚举类型都在最外层,算同级。
  • 多个 proto 文件下,若一个文件引入了其他文件,且每个文件 都声明了 package,不算同级。
代码语言:javascript
代码运行次数:0
复制
// ---------------------- 情况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; // 移动电话 
}

三、通讯录升级版2.1

​ 下面我们通过对项目的推进,演示如何使用 enum 类型!

​ 这里我们主要对 2.0 版本进行手机号类型的添加,并且在写入的时候加上输入手机号类型的步骤,以及读取时候打印出手机号类型的步骤!

下面各文件中注释内容就是对 2.0 版本新增的内容,并且 makefile 文件不变的话这里就不展示了

contacts.proto 文件:

代码语言:javascript
代码运行次数:0
复制
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 文件:

代码语言:javascript
代码运行次数:0
复制
#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 文件:

代码语言:javascript
代码运行次数:0
复制
#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;
}

Ⅱ. Any类型

一、介绍

protobuf 中的 Any 类型提供了一组接口来帮助我们方便地序列化、反序列化以及操作不同类型的消息。Any 类型的接口主要包括封装、解封装(解码)、获取类型信息等操作,它的实现位于 /usr/include/google/protobuf/any.h 中:

protobuf 中的 Any 类型是一种灵活的消息类型,允许你将任意类型的消息嵌入到一个其他消息中,它能够封装任何符合 protobuf 定义的消息,因此适用于需要动态扩展的场景。

​ 使用 Any 类型的时候,proto 文件中要引入 google/protobuf/any.proto,然后**定义对象时候要 google.protobuf**,如下所示:

代码语言:javascript
代码运行次数:0
复制
syntax = "proto3";
package contacts2_2;

import "google/protobuf/any.proto"	// 引入any.proto文件

message tmp {
	google.protobuf.Any data = 1;   // 定义时候要声明google.protobuf
}
1. PackFrom() 接口

PackFrom() 用于将一个消息对象序列化为 Any 类型。

代码语言:javascript
代码运行次数:0
复制
bool PackFrom(const google::protobuf::Message& message);
  • 参数:接受一个 google::protobuf::Message 类型的 对象这是所有 protobuf 消息的基类)。
  • 返回值:如果成功序列化,返回 true;否则返回 false

​ 示例:

代码语言:javascript
代码运行次数:0
复制
google::protobuf::Any any;
User user;
user.set_name("John");
user.set_age(30);

any.PackFrom(user);  // 将 user 消息封装进 any 字段中
2. UnpackTo() 接口

UnpackTo() 用于从 Any 类型反序列化出具体的消息对象。这个接口和上面的 PackFrom() 基本是配套的,就像 write()read() 的关系一样!

代码语言:javascript
代码运行次数:0
复制
bool UnpackTo(google::protobuf::Message* message) const;
  • 参数:接受一个指向 google::protobuf::Message 类型的**指针**,表示反序列化目标消息对象。
  • 返回值:如果成功反序列化,返回 true;否则返回 false

​ 示例:

代码语言:javascript
代码运行次数:0
复制
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;
}
3. Is() 接口

Is() 用于检查 Any 类型中存储的消息是否与给定的消息类型匹配。

代码语言:javascript
代码运行次数:0
复制
template <typename T>
bool Is() const;
  • 返回值:如果 Any 存储的消息是给定的类型 T,返回 true,否则返回 false

​ 示例:

代码语言:javascript
代码运行次数:0
复制
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;
}

二、通讯录升级版2.2

​ 下面我们通过对项目的推进,演示如何使用 Any 类型!

​ 这里我们主要对 2.1 版本新增联系人的地址信息,该地址信息使用 Any 类型来存储!

下面各文件中注释内容就是对 2.1 版本新增的内容,并且 makefile 文件不变的话这里就不展示了

contacts.proto 文件:

代码语言:javascript
代码运行次数:0
复制
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 文件:

代码语言:javascript
代码运行次数:0
复制
#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 文件:

代码语言:javascript
代码运行次数:0
复制
#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;
}

Ⅲ. oneof类型

一、定义与使用介绍

​ 在 Protobuf 中,oneof 是一种节省存储和增强消息灵活性的机制,它 允许一个消息中的多个字段共享同一存储空间oneof 类型的字段互斥,这意味着在 同一时间只能设置其中的一个字段

​ 这个场景其实挺常见的,比如我们同时要保存一个联系方式,而你可能有多个账号比如微信号和 QQ 号,那么你只需要选择存储其中一个联系方式即可,还能有多种的选择方式,更加的方便!

oneof 用于在消息中定义一组互斥字段,语法如下:

代码语言:javascript
代码运行次数:0
复制
message Example {
	// ...
	
    oneof field_name {
        int32 field1 = 1;  // 注意编号要承接上面,这个作用域不是独立的!
        string field2 = 2;
        bool field3 = 3;
    }
}
  • field_nameoneof 的名称,可以是任意合法的标识符。
  • 字段类型:可以是任意有效的 Protobuf 类型,包括基本类型(int32string 等)或消息类型。
  • 字段编号:每个字段都需要唯一的编号。

有如下规则:

  • 设置一个字段时,oneof 中其他字段会被自动清除。也就是说设置多个字段后,只有最后一个被存储下来了!
  • 如果未设置任何字段,oneof 被视为 未设置
  • oneof 中的字段 不能被声明为 repeated 类型
  • 通常我们 使用 _case 方法来获取当前设置了哪个字段,然后进行 if 判断即可!

二、通讯录升级版2.3

​ 下面我们通过对项目的推进,演示如何使用 oneof 类型!这里我们主要对 2.2 版本新增其它联系方式!

下面各文件中注释内容就是对 2.2 版本新增的内容,并且 makefile 文件不变的话这里就不展示了

contacts.proto 文件:

代码语言:javascript
代码运行次数:0
复制
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 文件:

代码语言:javascript
代码运行次数:0
复制
#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 文件:

代码语言:javascript
代码运行次数:0
复制
#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 类型是一种用来表示键值对集合的内建数据类型,类似于其他编程语言中的哈希表或字典。它适合用于存储动态数量的键值对数据,比如配置项、属性列表等。

​ 基本语法如下:

代码语言:javascript
代码运行次数:0
复制
map<key_type, value_type> field_name = field_number;
  1. key_type:键的类型,可以是以下标量类型之一:
    • 整数类型:int32, int64, uint32, uint64, fixed32, fixed64, sfixed32, sfixed64, sint32, sint64
    • 字符串:string
    • 注意:键不支持 boolfloatdoublebytes 或消息类型
  2. value_type:值的类型,可以是任何有效的 Protobuf 类型,包括:
    • 标量类型(如 int32string 等)
    • 嵌套消息类型
    • 甚至是另一个 map 类型
  3. map 不能用 repeated 修饰
  4. map 中的元素是 无序

二、通讯录升级版2.4

​ 下面我们通过对项目的推进,演示如何使用 map 类型!这里我们主要对 2.3 版本新增其它联系方式!

下面各文件中注释内容就是对 2.3 版本新增的内容,并且 makefile 文件不变的话这里就不展示了!我们这里主要是新增备注信息,用 map 进行存储!

contacts.proto 文件:

代码语言:javascript
代码运行次数:0
复制
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 文件:

代码语言:javascript
代码运行次数:0
复制
#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 文件:

代码语言:javascript
代码运行次数:0
复制
#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 的使用也进一步熟练了,并且也掌握了 ProtoBufproto3 语法支持的大部分类型及其使用,但只是正常使用还是完全不够的。通过接下来的学习,我们就能更进一步了解到 ProtoBuf 深入的内容!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. enum类型
    • 一、定义规则
    • 二、同名枚举值的注意事项
    • 三、通讯录升级版2.1
  • Ⅱ. Any类型
    • 一、介绍
      • 1. PackFrom() 接口
      • 2. UnpackTo() 接口
      • 3. Is() 接口
    • 二、通讯录升级版2.2
  • Ⅲ. oneof类型
    • 一、定义与使用介绍
    • 二、通讯录升级版2.3
  • Ⅳ. map类型
    • 一、定义
    • 二、通讯录升级版2.4
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档