proto3 语法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/xiaoma_bk/article/details/101365192

1. 前言

Protocol Buffer是Google的语言中立的,平台中立的,可扩展机制的,用于序列化结构化数据 - 对比XML,但更小,更快,更简单。您可以定义数据的结构化,然后可以使用特殊生成的源代码轻松地在各种数据流中使用各种语言编写和读取结构化数据。

2. 定义消息可惜

假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:

syntax = "proto3";
 
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
  • SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。

2.1 指定字段类型

在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。

2.2 分配标识号

  • 在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。
  • 注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
  • 最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。

2.3 指定字段规则

所指定的消息字段修饰符必须是如下之一:

  • singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)。
  • repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。

在proto3中,repeated的标量域默认情况虾使用packed。

你可以了解更多的pakced属性在 协议编码区中找到更多的.

2.4 添加更多消息类型

  • 在一个.proto文件中可以定义多个消息类型。
    在定义多个相关的消息的时候,这一点特别有用 - 例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
 
message SearchResponse {
 ...
}

2.5 添加注释

要为.proto文件添加注释,请使用C / C ++ - 样式//和/* … */语法。如:

/* SearchRequest表示搜索查询,带有分页选项
 * 表明响应中包含哪些结果。*/
message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

2.6 保留标识符(Reserved)

  • 如果你通过删除或者注释所有域来更新 消息类型,则未来用户可以在对类型进行自己的更新时重用字段编号。
  • 如果你使用旧版本加载相同的.proto文件这会导致严重的问题,包括数据损坏、隐私错误等等。
  • 现在有一种确保不会发生这种情况的方法就是指定保留标识符reserved(和/或名称,这也可能导致JSON序列化问题),protocol buffer 的编译器会警告未来尝试使用这些域标识符的用户。
message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

请注意,您不能在同一reserved语句中混合字段名称和字段编号。

2.7 你的生成是什么.proto?

当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
  • 对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
  • 对go来说,编译器会位每个消息类型生成了一个.pd.go文件。
  • 对于Ruby来说,编译器会为每个消息类型生成了一个.rb文件。
  • javaNano来说,编译器输出类似域java但是没有Builder类
  • 对于Objective-C来说,编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件,.proto文件中的每一个消息有一个对应的类。
  • 对于C#来说,编译器会为每个消息类型生成了一个.cs文件,.proto文件中的每一个消息有一个对应的类。

您可以按照所选语言的教程(即将推出的proto3版本)了解有关为每种语言使用API的更多信息。有关更多API详细信息,请参阅相关API参考(proto3版本即将推出)。

9.默认值

当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
对于strings,默认是一个空string
对于bytes,默认是一个空的bytes
对于bools,默认是false
对于数值类型,默认是0
对于枚举,默认是第一个定义的枚举值,必须为0;
对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide

对于可重复域的默认值是空(通常情况下是对应语言中空列表)。

注:对于标量消息域,一旦消息被解析,就无法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如,比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位,这个值不应该被序列化传输。
查看generated code guide选择你的语言的默认值的工作细节。

3. 类型

3.1 枚举

  • 在定义消息类型时,您可能希望其中一个字段只有一个预定义的值列表。
    例如,假设要为每一个SearchRequest消息添加一个 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点:通过向消息定义中添加一个枚举(enum)并且为每个可能的值定义一个常量就可以了。

在下面的示例中,我们添加了一个带有所有可能值的enum调用Corpus,以及一个类型的字段Corpus:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

3.2 标量类型

.proto type notes c++ type}
double
float
INT32 使用可变长度编码。编码负数的效率低 - 如果您的字段可能有负值,请改用sint32。
int64 使用可变长度编码。编码负数的效率低 - 如果您的字段可能有负值,请改用sint64。
uint32 使用可变长度编码。
uint64 使用可变长度编码。
SINT32 使用可变长度编码,比常规int32更有效编码负数
SINT64 使用可变长度编码,比常规int64更有效编码负数
fixed32 总是4个字节,值大于228时比uint32更有效
fixed64 总是八个字节。如果值通常大于2 56,则比uint64更有效。
sfixed32 总是四个字节 INT32
sfixed64 总是八个字节 int64
bool
string 字符串必须始终包含UTF-8编码或7位ASCII文本。
bype 可以包含任意字节序列。 string

3.3 .使用其他消息类型

  • 你可以将其他消息类型用作字段类型。
  • 例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在相同的.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段,如:
message SearchResponse {
  repeated Result results = 1;
}
 
message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

3.4 嵌套类型

你可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

如果你想在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

3.5 更新一个消息类型

如果一个已有的消息格式已无法满足新的需求——如,要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心!更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。

  • 不要更改任何已有的字段的数值标识。
  • 如果你增加新的字段,使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就可以以适当的方式和旧代码产生的数据交互。相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉。注意,未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的(这和proto2中的行为是不同的,在proto2中未定义的域依然会随着消息被序列化)
  • 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用(更好的做法可能是重命名那个字段,例如在字段前添加“OBSOLETE_”前缀,那样的话,使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号)。
  • int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样(例如,如果把一个64位数字当作int32来 读取,那么它就会被截断为32位的数字)。
  • sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
  • string和bytes是兼容的——只要bytes是有效的UTF-8编码。
  • 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
  • fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
  • 枚举类型与int32,uint32,int64和uint64相兼容(注意如果值不相兼容则会被截断),然而在客户端反序列化之后他们可能会有不同的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但是他的表示方式会依照语言而定。int类型的字段总会保留他们的

3.6 Any

Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型,你需要导入import google/protobuf/any.proto

import "google/protobuf/any.proto";
 
message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

3.7 默认值

解析消息时,如果编码消息不包含特定的单数元素,则解析对象中的相应字段将设置为该字段的默认值。这些默认值是特定于类型的:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于bools,默认值为false。
  • 对于数字类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0
  • 对于消息字段,未设置该字段。它的确切值取决于语言。有关详细信息, 请参阅生成的代码指

重复字段的默认值为空(通常是相应语言的空列表)。

请注意,对于标量消息字段,一旦解析了消息,就无法确定字段是否显式设置为默认值(例如,是否设置了布尔值false)或者根本没有设置:您应该记住这一点在定义消息类型时。例如,false如果您不希望默认情况下也发生这种行为,那么在设置为时,没有一个布尔值可以启用某些行为。还要注意的是,如果一个标消息字段被设置为默认值,该值将不会在电线上连载。
有关默认值如何在生成的代码中工作的更多详细信息,请参阅所选语言的生成代码指南

4. 导入定义

  • 在上面的示例中,Result消息类型在同一文件中定义SearchResponse- 如果要用作字段类型的消息类型已在另一个.proto文件中定义,该怎么办?
  • 您可以.proto通过导入来使用其他文件中的定义。要导入其他.proto人的定义,请在文件顶部添加import语句:
import“myproject / other_protos.proto”;
  • 默认情况下,您只能使用直接导入.proto文件中的定义。但是,有时您可能需要将.proto文件移动到新位置。.proto现在,您可以.proto在旧位置放置一个虚拟文件,以使用该import public概念将所有导入转发到新位置,而不是直接移动文件并在一次更改中更新所有调用站点。import public任何导入包含该import public语句的proto的人都可以传递依赖关系。例如:
// new.proto
// All definitions are moved here
// old.proto
//This is the proto that all clients are importing.
import public“new.proto”;
import“other.proto”;
// client.proto
import "old.proto";
//您使用old.proto和new.proto中的定义,但不使用other.proto

协议编译器使用-I/ --proto_pathflag 在协议编译器命令行中指定的一组目录中搜索导入的文件 。如果没有给出标志,它将查找调用编译器的目录。通常,您应该将–proto_path标志设置为项目的根目录,并对所有导入使用完全限定名称。

5.oneof

  • 如果您有一个包含许多字段的消息,并且最多只能同时设置一个字段,则可以使用oneof功能强制执行此行为并节省内存。

  • 除了一个共享内存中的所有字段之外,其中一个字段类似于常规字段,并且最多可以同时设置一个字段。设置oneof的任何成员会自动清除所有其他成员。您可以使用特殊case()或WhichOneof()方法检查oneof中的哪个值(如果有),具体取决于您选择的语言。

15.1使用Oneof

要在您中定义oneof,请.proto使用oneof关键字后跟您的oneof名称,在这种情况下test_oneof:

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

然后你可以增加oneof字段到 oneof 定义中. 你可以增加任意类型的字段, 但是不能使用repeated 关键字.

在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API指南中找到oneof API介绍.

15.2 Oneof 特性

设置oneof会自动清楚其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.

SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());
  • 如果解析器遇到同一个oneof中有多个成员,只有最会一个会被解析成消息。

  • oneof不支持repeated.

  • Reflection API适用于其中一个字段

    • 如果使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃, 因为sub_message 已经通过set_name()删除了
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here 

    • 同样在C ++中,如果你有Swap()两个消息与oneofs,每个消息最终将与另一个消息结果:在下面的例子中,msg1将有一个sub_message,msg2并将有一name。
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

5.3 向后兼容性问题

添加或删除其中一个字段时要小心。如果检查oneof返回的值None/ NOT_SET,这可能意味着oneof尚未设置或已在不同版本的oneof的被设置为一个字段。没有办法区分,因为没有办法知道线上的未知字段是否是其中一个成员。

6.Map(映射)

如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:

map<key_type, value_type> map_field = N;

其中key_type可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type可以是任意类型。

例如,如果你希望创建一个project的映射,每个Projecct使用一个string作为key,你可以像下面这样定义:

map<string, Project> projects = 3;
  • Map的字段可以是repeated。
  • 序列化后的顺序和map迭代器的顺序是不确定的,所以你不要期望以固定顺序处理Map
  • 当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序。
  • 从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key。

生成map的API现在对于所有proto3支持的语言都可用了,你可以从API指南找到更多信息。

6.1.向后兼容性问题

map语法序列化后等同于如下内容,因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的:

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

任何支持映射的协议缓冲区实现都必须生成和接受上述定义可以接受的数据。

7.包

当然可以为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:

package foo.bar;
message Open { ... }

在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:

message Foo {
  ...
  required foo.bar.Open open = 1;
  ...
}

包的声明符会根据使用语言的不同影响生成的代码。

  • 对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中; - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package;
  • 对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。
  • 对于Go,包可以被用做Go包名称,除非你显式的提供一个option go_package在你的.proto文件中。
  • 对于Ruby,生成的类可以被包装在内置的Ruby名称空间中,转换成Ruby所需的大小写样式 (首字母大写;如果第一个符号不是一个字母,则使用PB_前缀),例如Open会在Foo::Bar名称空间中。
  • 对于javaNano包会使用Java包,除非你在你的文件中显式的提供一个option java_package。
  • 对于C#包可以转换为PascalCase后作为名称空间,除非你在你的文件中显式的提供一个option csharp_namespace,例如,Open会在Foo.Bar名称空间中

7.1 包及名称的解析

  • Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。

  • ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。

8 定义服务(Service)

  • 如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如,想要定义一个RPC服务并具有一个方法,该方法能够接收 SearchRequest并返回一个SearchResponse,此时可以在.proto文件中进行如下定义:
service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

最直观的使用protocol buffer的RPC系统是gRPC一个由谷歌开发的语言和平台中的开源的PRC系统,gRPC在使用protocl buffer时非常有效,如果使用特殊的protocol buffer插件可以直接为您从.proto文件中产生相关的RPC代码。

如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC实现,你可以从proto2语言指南中找到更多信息

还有一些第三方开发的PRC实现使用Protocol Buffer。参考第三方插件wiki查看这些实现的列表。

20.JSON 映射

官方文档

  • Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。
  • 如果JSON编码的数据丢失或者其本身就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。
  • google的protobuf对象转json,不能直接使用FastJson之类的工具进行转换,原因是protobuf生成对象的get方法,返回的类型有byte[],而只有String类型可以作为json的key。google有提供专门的架包,方便protobuf与json之间相互转换。方法如下:

1、添加转换用的maven依赖:

<dependency>
   <groupId>com.googlecode.protobuf-java-format</groupId>
   <artifactId>protobuf-java-format</artifactId>
   <version>1.2</version>
</dependency>  

2、protobuf转json的方法

 // protobuf 转 json
 Message.Builder message = Message.newBuilder();
 String json = JsonFormat.printToString(message.build());

3、json转protobuf的方法

//json 转 protobuf
    try {
        JsonFormat.merge(json, message);
    } catch (ParseException e) {
        e.printStackTrace();
    }

21 生成您的类

根据实际工作需要,生成以下对应语言的自定义消息类型Java,Python,C ++,Go, Ruby, Objective-C,或C#的.proto文件,你需要运行protobuf 编译器protoc上.proto。如果尚未安装编译器,请下载该软件包并按照自述文件中的说明进行操作。对于Go,您还需要为编译器安装一个特殊的代码生成器插件:您可以在GitHub上的golang / protobuf存储库中找到这个和安装说明。

Protobuf 编译器的调用如下:

protoc --proto_path = IMPORT_PATH --cpp_out = DST_DIR --java_out = DST_DIR --python_out = DST_DIR --go_out = DST_DIR --ruby_out = DST_DIR --objc_out = DST_DIR --csharp_out = DST_DIR  path / to / file .proto
  • IMPORT_PATH指定.proto解析import指令时在其中查找文件的目录。如果省略,则使用当前目录。可以通过–proto_path多次传递选项来指定多个导入目录; 他们将按顺序搜索。 可以用作简短的形式。 -I=IMPORT_PATH``–proto_path

您可以提供一个或多个输出指令:

  • -cpp_out生成C ++代码DST_DIR。有关更多信息,请参阅C ++生成的代码参考。
  • -java_out生成Java代码DST_DIR。请参阅的Java生成的代码参考更多。
  • -python_out生成Python代码DST_DIR。看到的Python生成的代码的参考更多。
  • -go_out生成Go代码DST_DIR。有关更多信息,请参阅Go生成代码参考。
  • -ruby_out生成Ruby代码DST_DIR。Ruby生成的代码参考即将推出!
  • -objc_out生成Objective-C代码DST_DIR。有关更多信息,请参阅Objective-C生成的代码参考。
  • -csharp_out生成C#代码DST_DIR。有关更多信息,请参阅C#生成的代码参考。
  • -php_out生成PHP代码DST_DIR。看到PHP生成的代码的参考更多。
  • 为了方便起见,如果DST_DIR结束于.zip或.jar,编译器会将输出写入具有给定名称的单个ZIP格式存档文件。.jar输出还将根据Java JAR规范的要求提供清单文件。请注意,如果输出存档已存在,则会被覆盖; 编译器不够智能,无法将文件添加到现有存档中。

  • 您必须提供一个或多个.proto文件作为输入。.proto可以一次指定多个文件。虽然文件是相对于当前目录命名的,但每个文件必须位于其中一个文件中,IMPORT_PATH以便编译器可以确定其规范名称。

2.案例

syntax = "proto3";

package cartographer.mapping.proto;

message CellLimits {
  int32 num_x_cells = 1;
  int32 num_y_cells = 2;
}
syntax = "proto3";

package cartographer.mapping.proto;

import "cartographer/mapping/proto/2d/map_limits.proto";
import "cartographer/mapping/proto/2d/probability_grid.proto";
import "cartographer/mapping/proto/2d/tsdf_2d.proto";

message Grid2D {
  message CellBox {
    int32 max_x = 1;
    int32 max_y = 2;
    int32 min_x = 3;
    int32 min_y = 4;
  }

  MapLimits limits = 1;
  // These values are actually int16s, but protos don't have a native int16
  // type.
  repeated int32 cells = 2;		//repeated 重复
  CellBox known_cells_box = 3;
  oneof grid {
    ProbabilityGrid probability_grid_2d = 4;
    TSDF2D tsdf_2d = 5;
  }
  float min_correspondence_cost = 6;
  float max_correspondence_cost = 7;
}

猜你喜欢

转载自blog.csdn.net/xiaoma_bk/article/details/101365192