【ProtoBuf 语法详解】enum 类型

文章目录


1. enum 类型

1.1 定义规则

该语法支持我们定义枚举类型并使用。

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

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

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

proto 复制代码
enum PhoneType {
  MP = 0;    // 移动电话
  TEL = 1;   // 固定电话
}

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

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

1.2 定义时注意

将两个 "具有相同枚举值名称" 的枚举类型放在单个 .proto 文件下测试时,编译后会报错:某某某常量已经被定义!

所以这里要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
  • 单个 .proto 文件下,最外层枚举类型和嵌套枚举类型,不算同级。
  • 多个 .proto 文件下,若一个文件引入了其他文件,且每个文件都未声明 package,每个 proto 文件中的枚举类型都在最外层,算同级。
  • 多个 .proto 文件下,若一个文件引入了其他文件,且每个文件都声明了 package,不算同级。

情况一:同级枚举类型包含相同枚举值名称

代码如下:

proto 复制代码
enum PhoneType {
  MP = 0;    // 移动电话
  TEL = 1;   // 固定电话
}

enum PhoneTypeCopy {
  MP = 0;    // 移动电话    // 编译后报错: MP 已经定义
}

情况二:不同级枚举类型包含相同枚举值名称

代码如下:

proto 复制代码
// 用法正确

enum PhoneTypeCopy {
  MP = 0;    // 移动电话    
}

message Phone {
  string number = 1;    // 电话号码
  enum PhoneType {
    MP = 0;    // 移动电话
    TEL = 1;   // 固定电话
  }
}

情况三:多文件下都未声明 package

proto 复制代码
// phone1.proto
import "phone1.proto"
enum PhoneType {
  MP = 0;    // 移动电话    // 编译后报错: MP 已经定义
  TEL = 1;   // 固定电话
}

// phone2.proto
enum PhoneTypeCopy {
  MP = 0;    // 移动电话
}

情况四:多文件下都声明了 package

proto 复制代码
// phone1.proto
import "phone1.proto"
package phone1;
enum PhoneType {
  MP = 0;    // 移动电话    // 用法正确
  TEL = 1;   // 固定电话
}

// phone2.proto
package phone2;
enum PhoneTypeCopy {
  MP = 0;    // 移动电话
}

2. 升级通讯录至 2.1 版本

2.1 更新 proto 文件

更新 contacts.proto(通讯录 2.1),新增枚举字段并使用,更新内容如下:

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

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

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

如下图所示:

然后直接编译即可:

此时在 contacts.pb.h 的代码中:

  • 对于在 .proto 文件中定义的枚举类型,编译生成的代码中会含有与之对应的枚举类型、校验枚举值是否有效的方法 IsValid、以及获取枚举值名称的方法 _Name
  • 对于使用了枚举类型的字段,包含设置和获取字段的方法,已经清空字段的方法 clear_

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;
        }
    }

    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;
        }
    }  
} 


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

运行结果如下:先添加第一个联系人

然后再添加第二个联系人,当我们输入错误的电话类型的时候,可以看到此时电话类型被设置成了 MP。

原因就是:我们在 read.cc 代码中进行反序列化的时候,会为李四设置一个默认的电话类型,也就是 MP(因为我们在 .proto 文件中,对 MP 定义的常量值为 0,这个值就是默认的)

相关推荐
恒创科技HK2 小时前
海外服务器回国线路对比,带宽选择指南
运维·服务器
senijusene2 小时前
TCP并发服务器与I/O多路复用(select)
服务器·网络协议·tcp/ip
i学长的猫2 小时前
PM2 管理 Cloudflared 隧道 Neo-mac 及后台运行
linux·编辑器·vim
记录无知岁月2 小时前
【Linux】bash脚本使用
linux·bash
花间相见2 小时前
【JAVA基础01】——类和对象
java·开发语言·python
在等晚安么2 小时前
每日八股文
java·八股
lclcooky2 小时前
Spring中的IOC详解
java·后端·spring
GIOTTO情2 小时前
2026小红书投流新规下,基于Infoseek API的媒介投放自动化方案
java·linux·开发语言
FIT2CLOUD飞致云2 小时前
新增QQ频道与vLLM管理功能,1Panel v2.1.4版本发布
linux·服务器·ai·开源·1panel