【ProtoBuf 语法详解】Any 类型

文章目录

  • [1. Any 类型](#1. Any 类型)
  • [2. 升级通讯录至 2.2 版本](#2. 升级通讯录至 2.2 版本)
    • [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. Any 类型

字段还可以声明为 Any 类型,可以理解为泛型类型。使用时可以在 Any 中存储任意消息类型。Any 类型的字段也用 repeated 来修饰。

Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include 目录下查找所有 google 已经定义好的 .proto 文件。

该文件所在的目录如下:

bash 复制代码
# 进入该目录
cd /usr/local/protobuf/include/google/protobuf/

# 查看文件
cat any.proto

文件内容如下:

接下来,我们继续对通讯录进行升级,我们可以使用 any 类型的字段来存储联系人的地址信息。

2. 升级通讯录至 2.2 版本

2.1 更新 contacts.proto 文件

代码如下所示:

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

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

更新内容如下所示:

然后进行编译:

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

在 contacts.pb.h 代码中,对于 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

2.2 更新 write.cc 文件

代码如下:

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 = 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 << "-----------添加联系人成功-----------" << 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; // 把命名空间展开

// 打印联系人列表
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;
            }
        }
    }  
} 


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 编译运行

当你编译运行以后,如果提示报错:

bash 复制代码
[edison@vm-centos:~/myCode/proto3]$ make
g++ -o write write.cc contacts.pb.cc -std=c++11 -lprotobuf
/tmp/cc4zOG7m.o: In function `google::protobuf::Any* google::protobuf::MessageLite::CreateMaybeMessage<google::protobuf::Any>(google::protobuf::Arena*)':
write.cc:(.text._ZN6google8protobuf11MessageLite18CreateMaybeMessageINS0_3AnyEEEPT_PNS0_5ArenaE[_ZN6google8protobuf11MessageLite18CreateMaybeMessageINS0_3AnyEEEPT_PNS0_5ArenaE]+0x14): undefined reference to `google::protobuf::Any* google::protobuf::Arena::CreateMaybeMessage<google::protobuf::Any>(google::protobuf::Arena*)'
collect2: error: ld returned 1 exit status
make: *** [write] Error 1

那么需要在 write.ccread.cc 文件中添加以下代码,放在所有 #include 语句的下方,main 函数之前。

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

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

此时就完成了。

相关推荐
喵叔哟1 小时前
5. 【Blazor全栈开发实战指南】--Blazor组件基础
开发语言·javascript·ecmascript
无忧.芙桃2 小时前
C++11的部分内容(上)
c++
海奥华22 小时前
Rust初步学习
开发语言·学习·rust
小璐资源网2 小时前
C++中如何正确区分`=`和`==`的使用场景?
java·c++·算法
卢锡荣2 小时前
LDR6021Q 车规级 Type‑C PD 控制芯片:一芯赋能,边充边传,稳驭全场景
c语言·开发语言·ios·计算机外设·电脑
、BeYourself2 小时前
Scala 基础语法
开发语言·scala
AMoon丶2 小时前
C++模版-函数模版,类模版基础
java·linux·c语言·开发语言·jvm·c++·算法
SugarFreeOixi2 小时前
Matlab多个图窗重叠问题解决,平铺函数TileFigs
开发语言·matlab
ryan007liu3 小时前
shell 批量执行locust 脚本压测
linux·服务器·压力测试