前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【protobuf】四、proto3语法详解③ -- 默认值 && 消息更新规则 && option选项

【protobuf】四、proto3语法详解③ -- 默认值 && 消息更新规则 && option选项

作者头像
利刃大大
发布2025-02-08 14:08:45
发布2025-02-08 14:08:45
10400
代码可运行
举报
文章被收录于专栏:csdn文章搬运csdn文章搬运
运行总次数:0
代码可运行
Ⅰ. 默认值

​ 反序列化消息时,如果被反序列化的二进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:

  • 对于 字符串,默认值为空字符串
  • 对于 字节,默认值为空字节
  • 对于 布尔值,默认值为 false
  • 对于 数值类型,默认值为 0
  • 对于 枚举,默认值是第一个定义的枚举值, 必须为 0
  • 对于 消息字段,未设置该字段。它的取值是依赖于语言
  • 对于 设置了 repeated 的字段 的默认值是空的( 通常是相应语言的一个空列表 )
  • 对于 消息字段 、 oneof 字段 和 any 字段C++Java 语言中都有 has_ 方法来检测当前字段是否被设置;而对于 标量数据类型,在 proto3 语法下是不会生成 has_ 方法的!

Ⅱ. 消息的更新规则

一、更新规则

​ 如果现有的消息类型已经不再满足我们的需求,例如需要扩展一个字段,在不破坏任何现有代码的情况下更新消息类型非常简单。遵循如下规则即可:

  1. 新增内容
    • 禁止新增任何已有字段的字段编号。
  2. 修改内容
    • int32uint32int64uint64bool 是完全兼容的。可以从这些类型中的一个改为另一个,而不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采用与 C++ 一致的处理方案(例如若将 64 位整数当做 32 位进行读取,它将被截断为 32 位)。
    • sint32sint64 相互兼容但不与其他的整型兼容。
    • stringbytes 在合法 UTF-8 字节前提下也是兼容的。
    • bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。
    • fixed32sfixed32 兼容, fixed64 与 **sfixed64**兼容。
    • enumint32uint32int64uint64 兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语言采用不同的处理方案:例如,未识别的 proto3 枚举类型会被保存在消息中,但是当消息反序列化时如何表示是依赖于编程语言的。整型字段总是会保持其的值。
    • oneof
      • 将一个单独的值更改为新 oneof 类型成员之一是安全和二进制兼容的。
      • 若确定没有代码一次性设置多个值那么将多个字段移入一个新 oneof 类型也是可行的。
      • 将任何字段移入已存在的 oneof 类型是不安全的。
  3. 删除内容
    • 若是移除老字段,要保证不再使用移除字段的字段编号。正确的做法是保留字段编号(reserved),以确保该编号将不能被重复使用。不建议直接删除或注释掉字段

二、保留字段 reserved

​ 如果通过【删除】或【注释掉】字段来更新消息类型,未来的用户在添加新字段时,有可能会使用以前已经存在,但已经被删除或注释掉的字段编号,将来使用该 .proto 的旧版本时的程序会引发很多问题:数据损坏、隐私错误等等。

​ 确保不会发生这种情况的一种方法是:使用 reserved 将指定字段的编号或名称设置为保留项 。当我们再使用这些编号或名称时,protobuf 的编译器将会警告这些编号或名称不可用。举个例子:

代码语言:javascript
代码运行次数:0
复制
message Message {
    // 设置保留项:
    reserved 100, 101, 200 to 299;
    reserved "field3", "field4";
    // 注意:不要在一行 reserved 声明中同时声明字段编号和名称,如下所示:
    // reserved 102, "field5";

    // 设置保留项之后,下⾯代码会告警
    int32 field1 = 100; // 告警:Field 'field1' uses reserved number 100
    int32 field2 = 101; // 告警:Field 'field2' uses reserved number 101
    int32 field3 = 102; // 告警:Field name 'field3' is reserved
    int32 field4 = 103; // 告警:Field name 'field4' is reserved
}

​ 还要再说一下的是:因为使用了 reserved 关键字,ProtoBuf 在编译阶段就拒绝了我们使用已经保留的字段编号!

三、未知字段

​ 在 protobuf 中,【未知字段】是指接收到的数据中包含的字段,这些字段未在接收端的 .proto 文件定义中描述。比如我们在服务端的 .proto 文件中添加了新字段,这些字段可能没来得及在客户端那边更新,所以客户端那边的 .proto 文件并没有该新字段,那么在接收数据的时候,这个新字段就会被客户端处理为未知字段!

protobuf 的设计允许在不同版本之间进行扩展,而不破坏现有功能,这也是未知字段的一个重要应用场景。

  1. 产生原因
    • 当一个消息被序列化后发送到接收端,如果接收端的 .proto 定义缺少某些字段(例如因为版本较旧),这些字段就被视为未知字段。
  2. 行为特点
    • 未知字段不会导致解析失败,protobuf 的解析器会跳过这些字段。
    • 在接收端,如果消息重新序列化,未知字段将被保留。
    • 如果消息被修改并重新序列化,修改的内容不会包含未知字段。
  3. 作用
    • 向后兼容性:允许老版本的程序正常接收新版本的数据,而不需要理解新增字段。
    • 向前兼容性:新版本的程序可以处理旧版本的数据,不受未知字段的影响。
  4. 管理未知字段
    • protobufC++Java 实现中,支持以某种方式读取、操作或清除未知字段。
    • 例如:
      • C++ 中,UnknownFieldSet 类可以用于访问和操作未知字段。
      • 在新版的 proto3 中,默认情况下未知字段不会被保留。
1. 未知字段的获取

​ 下面是消息相关类的关系图:

在这里插入图片描述
在这里插入图片描述

​ 通常我们通过 google::protobuf::Message 来获取 Reflection 对象和 Description 对象,然后通过它们提供的接口比如 Reflection.GetUnknownFields() 来获取未知字段的集合,每个集合又是一个 UnknownFieldSet 对象,通过它的内部接口即可实现一些操作!

2. MessageLite 类介绍(了解)

MessageLite 从名字看是轻量级的 Message仅仅提供序列化、反序列化功能

​ 类定义在 google 提供的 message_lite.h 中。

3. Message 类介绍(了解)

​ 我们自定义的消息类,都是继承自 Message 类。Message 最重要的两个接口是 GetDescriptorGetReflection,可以获取该类型对应的 Descriptor 对象指针 和 Reflection 对象指针!

​ 类定义在 google 提供的 message.h 中。

代码语言:javascript
代码运行次数:0
复制
// google::protobuf::Message 部分代码展示
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;
4. Descriptor 类介绍(了解)

Descriptor 类是对 message 类型定义的描述,包括 message 的名字、所有字段的描述、原始的 .proto 文件内容等。

​ 类定义在 google 提供的 descriptor.h 中。

代码语言:javascript
代码运行次数:0
复制
// 部分代码展⽰
class PROTOBUF_EXPORT Descriptor : private internal::SymbolBase {
    string& name() const	 // 获取消息类型的名称
    int field_count() const; // 获取消息中的字段数量
    
    // 通过不同方式获取字段描述符
    const FieldDescriptor* field(int index) const;
    const FieldDescriptor* FindFieldByNumber(int number) const;
    const FieldDescriptor* FindFieldByName(const std::string& name) const;
    const FieldDescriptor* FindFieldByLowercaseName(const std::string& lowercase_name) const;
    const FieldDescriptor* FindFieldByCamelcaseName(const std::string& camelcase_name) const;
    
    int enum_type_count() const; // 获取消息类型中枚举类型的数量
    
    // 通过不同方式获取枚举描述符
    const EnumDescriptor* enum_type(int index) const;
    const EnumDescriptor* FindEnumTypeByName(const std::string& name) const;
    const EnumValueDescriptor* FindEnumValueByName(const std::string& name) const;
}
5. Reflection 类介绍(了解)
  • Reflection 类主要 提供了动态读写消息字段的接口,对消息对象的自动读写主要通过该类完成
  • 提供方法来动态访问/修改 message 中的字段,对每种类型,Reflection 都提供了一个单独的接口用于读写字段对应的值。
  • 针对所有不同的 field 类型 FieldDescriptor::TYPE_*,需要使用不同的 Get()/Set()/Add() 接口。
  • repeated 类型需要使用 GetRepeated()/SetRepeated() 接口,不可以和非 repeated 类型接口混用。
  • message 对象只可以被由它⾃⾝的 reflection(message.GetReflection()) 来操作。
  • 类中还包含了访问/修改未知字段的方法。
  • 类定义在 google 提供的 message.h 中。
代码语言:javascript
代码运行次数:0
复制
// 部分代码展⽰
class PROTOBUF_EXPORT Reflection final {
    const UnknownFieldSet& GetUnknownFields(const Message& message) const;
    UnknownFieldSet* MutableUnknownFields(Message* message) const;
    bool HasField(const Message& message, 
                  const FieldDescriptor* field) const;
    int FieldSize(const Message& message, 
                  const FieldDescriptor* field) const;
    void ClearField(Message* message, 
                    const FieldDescriptor* field) const;
    bool HasOneof(const Message& message,
    const OneofDescriptor* oneof_descriptor) const;
    void ClearOneof(Message* message,
    const OneofDescriptor* oneof_descriptor) const;
    const FieldDescriptor* GetOneofFieldDescriptor(const Message& message, 
                                                   const OneofDescriptor* oneof_descriptor) const;

	// Singular field getters ------------------------------------------
	// These get the value of a non-repeated field. They return the default
	// value for fields that aren't set.
    int32_t GetInt32(const Message& message, const FieldDescriptor* field) const;
    int64_t GetInt64(const Message& message, const FieldDescriptor* field) const;
    uint32_t GetUInt32(const Message& message, const FieldDescriptor* field) const;
    uint64_t GetUInt64(const Message& message, const FieldDescriptor* field) const;
    float GetFloat(const Message& message, const FieldDescriptor* field) const;
    double GetDouble(const Message& message, const FieldDescriptor* field) const;
    bool GetBool(const Message& message, const FieldDescriptor* field) const;
    std::string GetString(const Message& message, const FieldDescriptor* field) const;
    const EnumValueDescriptor* GetEnum(const Message& message, const FieldDescriptor* field) const;
    int GetEnumValue(const Message& message, const FieldDescriptor* field) const;
    const Message& GetMessage(const Message& message, const FieldDescriptor* field, MessageFactory* factory = nullptr) const;
    
    // Singular field mutators -----------------------------------------
    // These mutate the value of a non-repeated field.
    void SetInt32(Message* message, const FieldDescriptor* field, int32_t value) const;
    void SetInt64(Message* message, const FieldDescriptor* field, int64_t value) const;
    void SetUInt32(Message* message, const FieldDescriptor* field, uint32_t value) const;
    void SetUInt64(Message* message, const FieldDescriptor* field, uint64_t value) const;
    void SetFloat(Message* message, const FieldDescriptor* field, float value) const;
    void SetDouble(Message* message, const FieldDescriptor* field, double value) const;
    void SetBool(Message* message, const FieldDescriptor* field, bool value) const;
    void SetString(Message* message, const FieldDescriptor* field, std::string value) const;
    void SetEnum(Message* message, const FieldDescriptor* field, const EnumValueDescriptor* value) const;
    void SetEnumValue(Message* message, const FieldDescriptor* field, int value) const;

    Message* MutableMessage(Message* message, 
                            const FieldDescriptor* field, 
                            MessageFactory* factory = nullptr) const;
    PROTOBUF_NODISCARD Message* ReleaseMessage(Message* message, 
                                               const FieldDescriptor* field, 
                                               MessageFactory* factory = nullptr) const;

    // Repeated field getters ------------------------------------------
    // These get the value of one element of a repeated field.
    int32_t GetRepeatedInt32(const Message& message, const FieldDescriptor* field, int index) const;
    int64_t GetRepeatedInt64(const Message& message, const FieldDescriptor* field, int index) const;
    uint32_t GetRepeatedUInt32(const Message& message,
                               const FieldDescriptor* field, 
                               int index) const;
    uint64_t GetRepeatedUInt64(const Message& message,
    						 const FieldDescriptor* field, 
                               int index) const;
    float GetRepeatedFloat(const Message& message, 
                           const FieldDescriptor* field, 
                           int index) const;
    double GetRepeatedDouble(const Message& message, 
                             const FieldDescriptor* field, 
                             int index) const;
    bool GetRepeatedBool(const Message& message, 
                         const FieldDescriptor* field, 
                         int index) const;
    std::string GetRepeatedString(const Message& message,
    							const FieldDescriptor* field, 
                                  int index) const;
    const EnumValueDescriptor* GetRepeatedEnum(const Message& message,
    										const FieldDescriptor* field,
    										int index) const;
    int GetRepeatedEnumValue(const Message& message, 
                             const FieldDescriptor* field, 
                             int index) const;
    const Message& GetRepeatedMessage(const Message& message,
    							   const FieldDescriptor* field,
    							   int index) const;
    const std::string& GetRepeatedStringReference(const Message& message,
    										  const FieldDescriptor* field,
    										  int index,
    										  std::string* scratch) const;

    // Repeated field mutators -----------------------------------------
    // These mutate the value of one element of a repeated field.
    void SetRepeatedInt32(Message* message,
                          const FieldDescriptor* field, 
                          int index, 
                          int32_t value) const;
    void SetRepeatedInt64(Message* message, 
                          const FieldDescriptor* field, 
                          int index, 
                          int64_t value) const;
    void SetRepeatedUInt32(Message* message, 
                           const FieldDescriptor* field,
                           int index, 
                           uint32_t value) const;
    void SetRepeatedUInt64(Message* message, 
                           const FieldDescriptor* field,
                           int index, 
                           uint64_t value) const;
    void SetRepeatedFloat(Message* message,
                          const FieldDescriptor* field,
                          int index,
                          float value) const;
    void SetRepeatedDouble(Message* message, 
                           const FieldDescriptor* field,
                           int index, 
                           double value) const;
    void SetRepeatedBool(Message* message, 
                         const FieldDescriptor* field,
                         int index, 
                         bool value) const;
    void SetRepeatedString(Message* message, 
                           const FieldDescriptor* field,
                           int index, 
                           std::string value) const;
    void SetRepeatedEnum(Message* message, 
                         const FieldDescriptor* field,
                         int index, 
                         const EnumValueDescriptor* value) const;
    void SetRepeatedEnumValue(Message* message,
                              const FieldDescriptor* field,
                              int index,
                              int value) const;
    Message* MutableRepeatedMessage(Message* message,
                                    const FieldDescriptor* field,
                                    int index) const;

    // Repeated field adders -------------------------------------------
    // These add an element to a repeated field.
    void AddInt32(Message* message, 
                  const FieldDescriptor* field,
                  int32_t value) const;
    void AddInt64(Message* message,
                  const FieldDescriptor* field,
                  int64_t value) const;
    void AddUInt32(Message* message, 
                   const FieldDescriptor* field,
                   uint32_t value) const;
    void AddUInt64(Message* message,
                   const FieldDescriptor* field,
                   iuint64_t value) const;
    void AddFloat(Message* message, 
                  const FieldDescriptor* field,
                  float value) const;
    void AddDouble(Message* message,
                   const FieldDescriptor* field,
                   double value) const;
    void AddBool(Message* message, 
                 const FieldDescriptor* field,
                 bool value) const;
    void AddString(Message* message,
                   const FieldDescriptor* field,
                   std::string value) const;
    void AddEnum(Message* message,
                 const FieldDescriptor* field,
                 const EnumValueDescriptor* value) const;
    void AddEnumValue(Message* message,
                      const FieldDescriptor* field,
                      int value) const;
    Message* AddMessage(Message* message, 
                        const FieldDescriptor* field,
                        MessageFactory* factory = nullptr) const;
    const FieldDescriptor* FindKnownExtensionByName(const std::string& name) const;
    const FieldDescriptor* FindKnownExtensionByNumber(int number) const;
    bool SupportsUnknownEnumValues() const;
};
6. UnknownFieldSet 类介绍(重要)

UnknownFieldSet 包含在分析消息时遇到但未由其类型定义的所有字段。若要将 UnknownFieldSet 附加到任何消息,可以调用 Reflection::GetUnknownFields() 接口来操作。

​ 类定义在 unknown_field_set.h 中。

代码语言:javascript
代码运行次数:0
复制
class PROTOBUF_EXPORT UnknownFieldSet {
    inline void Clear();
    void ClearAndFreeMemory();
    inline bool empty() const;
    inline int field_count() const;
    
    inline const UnknownField& field(int index) const;
    inline UnknownField* mutable_field(int index);
    
    // Adding fields ---------------------------------------------------
    void AddVarint(int number, uint64_t value);
    void AddFixed32(int number, uint32_t value);
    void AddFixed64(int number, uint64_t value);
    void AddLengthDelimited(int number, const std::string& value);
    std::string* AddLengthDelimited(int number);
    UnknownFieldSet* AddGroup(int number);
    
    // Parsing helpers -------------------------------------------------
    // These work exactly like the similarly-named methods of Message.
    bool MergeFromCodedStream(io::CodedInputStream* input);
    bool ParseFromCodedStream(io::CodedInputStream* input);
    bool ParseFromZeroCopyStream(io::ZeroCopyInputStream* input);
    bool ParseFromArray(const void* data, int size);
    
    inline bool ParseFromString(const std::string& data) {
    	return ParseFromArray(data.data(), static_cast<int>(data.size()));
    }
    
    // Serialization.
    bool SerializeToString(std::string* output) const;
    bool SerializeToCodedStream(io::CodedOutputStream* output) const;
    static const UnknownFieldSet& default_instance();
};
7. UnknownField 类介绍(重要)

​ 表示未知字段集中的一个字段。该类定义在 unknown_field_set.h 中。

代码语言:javascript
代码运行次数:0
复制
class PROTOBUF_EXPORT UnknownField {
public:
    enum Type {
        TYPE_VARINT,
        TYPE_FIXED32,
        TYPE_FIXED64,
        TYPE_LENGTH_DELIMITED,
        TYPE_GROUP
    };
    inline int number() const;
    inline Type type() const;

    // Accessors -------------------------------------------------------
    // Each method works only for UnknownFields of the corresponding type.
    inline uint64_t varint() const;
    inline uint32_t fixed32() const;
    inline uint64_t fixed64() const;
    inline const std::string& length_delimited() const;
    inline const UnknownFieldSet& group() const;
    inline void set_varint(uint64_t value);
    inline void set_fixed32(uint32_t value);
    inline void set_fixed64(uint64_t value);
    inline void set_length_delimited(const std::string& value);
    inline std::string* mutable_length_delimited();
    inline UnknownFieldSet* mutable_group();
};
8. 打印未知字段样例

​ 下面展示之前 2.x 的简化版本,主要看一下打印未知字段的操作:

代码语言:javascript
代码运行次数:0
复制
void PrintfContacts(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 (const PeopleInfo_Phone& phone : people.phone()) 
        	cout << "电话" << j++ << ": " << phone.number() << endl;

        // 打印未知字段:
        const Reflection* reflection = PeopleInfo::GetReflection(); 		    // 获取reflection对象
        const UnknownFieldSet& unknowSet = reflection->GetUnknownFields(people); // 获取未知字段集合
        for (int j = 0; j < unknowSet.field_count(); j++) {
            // 获取单个未知字段,打印信息
            const UnknownField& unknow_field = unknowSet.field(j);
            cout << "未知字段" << j+1 << ":" 
            	<< " 字段编号: " << unknow_field.number() 
            	<< " 类型: "<< unknow_field.type();
			
            // 根据未知字段的类型,打印对应的值
            switch (unknow_field.type()) {
                case UnknownField::Type::TYPE_VARINT: 
                cout << " 值: " << unknow_field.varint() << endl;
                break;
                case UnknownField::Type::TYPE_LENGTH_DELIMITED:
                cout << " 值: " << unknow_field.length_delimited() << endl;
                break;
            }
        }
	}	
}

// 运行效果如下所示:
姓名:王五
年龄:0
电话1: 110
未知字段1: 字段编号: 4 类型: 0 值: 1112

四、前后兼容性

​ 根据上述的例子可以得出,protbuf 是具有向前兼容的。为了叙述方便,把增加了“生日”属性的 service 称为“新模块”;未做变动的 client 称为 “老模块”。

  • 向前兼容:⽼模块能够正确识别新模块⽣成或发出的协议。这时新增加的“⽣⽇”属性会被当作未知字段(pb 3.5 版本及之后)。
  • 向后兼容:新模块也能够正确识别⽼模块⽣成或发出的协议。

​ 前后兼容的作用:当我们维护一个很庞大的分布式系统时,由于你无法同时升级所有模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。

Ⅲ. 选项 option

.proto 文件中可以声明许多选项,使用 option 标注。选项能影响 proto 编译器的某些处理方式

​ 选项的完整列表在 google/protobuf/descriptor.proto 中定义。部分代码如下所示:

代码语言:javascript
代码运行次数:0
复制
syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } 	// ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... }  // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } 	// 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } 	// oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } 	// 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... }  // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... }   // 服务方法选项 定义在 MethodOptions 消息中
...

​ 由此可见,选项分为 文件级消息级字段级 等等, 但并没有一种选项能作用于所有的类型。

常用选项

optimize_for:该选项为文件选项,可以设置 protoc 编译器的优化级别,分别为 SPEEDCODE_SIZELITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译 .proto 文件后生成的代码内容不同。

SPEEDprotoc 编译器将生成的代码是高度优化的,代码运行效率高,但是由此生成的代码编译后会占用更多的空间。 SPEED 是默认选项

CODE_SIZEprotoc 编译器将生成最少的类,会占用更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运行效率较低。这种方式适合用在包含⼤量的 .proto 文件,但并不盲目追求速度的应用中。

LITE_RUNTIME:生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲 Protocol Buffer 提供的反射功能为代价的,仅仅提供 encoding+序列化 功能,所以我们**在链接 pb 库时仅需链接 libprotobuf-lite**,⽽⾮ libprotobuf。这种模式通常用于资源有限的平台,例如移动手机平台中。

代码语言:javascript
代码运行次数:0
复制
option optimize_for = LITE_RUNTIME;

allow_alias : 允许将相同的常量值分配给不同的枚举常量,用来定义别名。该选项为枚举选项。

代码语言:javascript
代码运行次数:0
复制
enum PhoneType {
    option allow_alias = true;
    MP = 0;
    TEL = 1;
    LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-07,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 默认值
  • Ⅱ. 消息的更新规则
    • 一、更新规则
    • 二、保留字段 reserved
    • 三、未知字段
      • 1. 未知字段的获取
      • 2. MessageLite 类介绍(了解)
      • 3. Message 类介绍(了解)
      • 4. Descriptor 类介绍(了解)
      • 5. Reflection 类介绍(了解)
      • 6. UnknownFieldSet 类介绍(重要)
      • 7. UnknownField 类介绍(重要)
      • 8. 打印未知字段样例
    • 四、前后兼容性
  • Ⅲ. 选项 option
    • 常用选项
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档