【ProtoBuf 语法详解】oneof 类型

文章目录

  • [1. oneof 类型](#1. oneof 类型)
  • [2. 升级通讯录至 2.3 版本](#2. 升级通讯录至 2.3 版本)
    • [2.1 更新 contacts.proto 文件](#2.1 更新 contacts.proto 文件)
    • [2.2 更新 write.cc 文件](#2.2 更新 write.cc 文件)
    • [2.3 更新 read.cc 文件](#2.3 更新 read.cc 文件)
    • [2.4 编译运行](#2.4 编译运行)

1. oneof 类型

如果消息中有很多可选字段,并且将来同时只有一个字段会被设置,那么就可以使用 oneof 加强这个行为,也能有节约内存的效果。

2. 升级通讯录至 2.3 版本

通讯录 2.3 版本想新增联系人的其他联系方式,比如 qq 或者 wechat 二选一,我们就可以使用 oneof 字段来加强多选一这个行为。

oneof 字段定义的格式为:

proto 复制代码
oneof 字段名 { 字段1; 字段2; ... }

2.1 更新 contacts.proto 文件

代码如下:

proto 复制代码
syntax = "proto3";
package contacts; // package 是一个可选的声明符, 声明其命名空间

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

// 地址
message Address
{
    string home_address = 1; // 家庭地址
    string unit_address = 2; // 单位地址
}

// 定义联系人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;   // 电话

    google.protobuf.Any data = 4; // 地址

    // 社交联系方式 (多选一)
    oneof other_contact
    {
        string qq = 5;
        string wechat = 6;
    }
}

// 通讯录:修改消息名为ContactBook(大驼峰,避免和字段名contacts冲突)
message ContactBook
{
    repeated PeopleInfo contacts = 1;
}

更新内容如下:

注意:

  • 可选字段中的字段编号,不能与非可选字段的编号冲突。
  • 不能在 oneof 中使用 repeated 字段。
  • 将来在设置 oneof 字段中值时,如果将 oneof 中的字段设置多个,那么只会保留最后一次设置的成员,之前设置的 oneof 成员会自动清除。

然后更新代码:

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

在 contacts.pb.h 更新的代码中,对于 oneof 字段:

  • 会将 oneof 中的多个字段定义为一个枚举类型。
  • 设置和获取:对 oneof 内的字段进行常规的设置和获取即可,但要注意只能设置一个。如果设置多个,那么只会保留最后一次设置的成员。
  • 清空 oneof 字段:clear_ 方法
  • 获取当前设置了哪个字段:_case 方法

2.2 更新 write.cc 文件

代码如下:

cpp 复制代码
#include <iostream>
#include <fstream>
#include "contacts.pb.h"

using namespace std;
using namespace contacts; // 把命名空间展开

// --- 强行补齐缺失的 Arena 模板实现 ---
#include <google/protobuf/any.pb.h>
namespace google
{
    namespace protobuf
    {
        template <>
        Any *Arena::CreateMaybeMessage<Any>(Arena *arena)
        {
            return Arena::CreateMessageInternal<Any>(arena);
        }
    }
}
// ----------------------------------

// 新增联系人
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 = 0;; i++)
    {
        cout << "请输入联系人电话" << i + 1 << " (输入回车即可完成电话新增): ";
        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(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP); // 设置移动电话
            break;
        case 2:
            phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL); // 设置固定电话
            break;
        default:
            cout << "选择有误! " << endl;
            break;
        }
    }

    Address address; // 定义一个地址对象
    
    cout << "请输入联系人家庭地址: ";
    string homeAddress;
    getline(cin, homeAddress);
    address.set_home_address(homeAddress);

    cout << "请输入联系人单位地址: ";
    string unitAddress;
    getline(cin, unitAddress);
    address.set_unit_address(unitAddress);

    // 把Address类型的对象转化为Any类型
    people->mutable_data()->PackFrom(address);

    cout << "选择添加一个其他联系方式 (1、qq号  2、微信号): ";
    int otherContact;
    cin >> otherContact;
    cin.ignore(256, '\n');
    if (1 == otherContact)
    {
        cout << "请输入qq号: ";
        string qq;
        getline(cin, qq);
        people->set_qq(qq);
    }
    else if(2 == otherContact)
    {
        cout << "请输入微信号: ";
        string wechat;
        getline(cin, wechat);
        people->set_wechat(wechat);
    }
    else
    {
        cout << "非法选择, 该项设置失败! " << endl;
    }


    cout << "-----------添加联系人成功-----------" << endl;
}

int main()
{
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    ContactBook 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;
    }
    cout << "write success!" << endl;
    input.close();
    output.close();

    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

更新内容如下:

2.3 更新 read.cc 文件

代码如下:

cpp 复制代码
#include <iostream>
#include <fstream>
#include "contacts.pb.h"

using namespace std;
using namespace contacts; // 把命名空间展开

// --- 强行补齐缺失的 Arena 模板实现 ---
#include <google/protobuf/any.pb.h>
namespace google
{
    namespace protobuf
    {
        template <>
        Any *Arena::CreateMaybeMessage<Any>(Arena *arena)
        {
            return Arena::CreateMessageInternal<Any>(arena);
        }
    }
}
// ----------------------------------

// 打印联系人列表
void PrintContacts(ContactBook& contacts) 
{
    for (int i = 0; i < contacts.contacts_size(); i++) 
    {
        cout << "---------------联系人" << i+1 << "---------------" << endl;

        const PeopleInfo& people = contacts.contacts(i);

        cout << "联系人姓名: " << people.name() << endl;
        cout << "联系人年龄: " << people.age() << endl;

        for (int j = 0; j < people.phone_size() ; j++) 
        {
            // 打印联系人电话
            const PeopleInfo_Phone& phone = people.phone(j);
            cout << "联系人电话" << j+1 << ": " << phone.number(); // 这里不需要换行符

            // 打印联系人电话类型
            cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }

        // 如果data有数据 并且 data的数据类型是Address, 那么就打印联系人地址
        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;
            }
        }

        // 打印qq号或者微信号
        switch (people.other_contact_case())
        {
            case PeopleInfo::OtherContactCase::kQq:
                cout << "qq号: " << people.qq() << endl;
                break;
            case PeopleInfo::OtherContactCase::kWechat:
                cout << "微信号: " << people.wechat() << endl;
                break;
            default:
                break;
        }
    }  
} 


int main() 
{
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    ContactBook contacts;

    // 读取本地已存在的通讯录文件(以二进制方式读取)
    fstream input("contacts.bin", ios::in | ios::binary);
    if (!contacts.ParseFromIstream(&input)) 
    {
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }
    // 打印通讯录列表
    PrintContacts(contacts);

    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

更新内容如下:

2.4 编译运行

make 编译以后,新增一个联系人。

以上就是 oneof 语法的详细用法。

相关推荐
文公子WGZ2 小时前
将java 21切换成java 25
java·开发语言
一直都在5722 小时前
Java序列化和反序列化
java·开发语言
样例过了就是过了2 小时前
LeetCode热题100 搜索二维矩阵
数据结构·c++·算法·leetcode·矩阵
2401_831920742 小时前
C++与Qt图形开发
开发语言·c++·算法
GIS阵地2 小时前
Warning 1: PROJ: proj_create_from_database
数据库·c++·mybatis·qgis·开源gis·pyqgis
重庆兔巴哥2 小时前
如果Java环境变量配置不成功,应该怎么办?
java·开发语言
良木生香2 小时前
【C++初阶】:C++入门相关知识(3):引用 & inline内联函数 & nullptr相关概念
开发语言·c++
西野.xuan2 小时前
【一篇即毕业系列】C++的volatile关键字从基础到通天。
java·jvm·c++
泯仲2 小时前
从零起步学习MySQL 第十六章:MySQL 分库分表的考量策略
开发语言·mysql