




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Protobuf使用手册第1章 定义.proto 文件首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Mto 文件非常类似 java 或者 C 语言的数据定义,可以使用C或C+风格的注释。下面是一个proto文件的例子。package tutorial;option java_package = "com.example.tutorial"option java_outer_classname = "AddressBookProtos"message Per
2、son required string name = 1;required int32 id = 2; / Unique ID number for this person.optional string email = 3;enum PhoneType MOBILE = 0; HOME = 1; WORK = 2;message PhoneNumber required string number = 1; optional PhoneType type = 2 default = HOME;repeated PhoneNumber phone = 4;/ Our address book
3、file is just one of these.message AddressBook repeated Person person = 1;一个proto文件主要包含package定义、message定义和属性定义三个部分,还有一些可选项。1.1 定义packagePackage在c+中对应namespace。对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package。1.2 定义messageMessage在C+中对应class。Message中定义的全部属性在class中全部为private的。Message的嵌套使用可以嵌套定义,
4、也可以采用先定义再使用的方式。Message的定义末尾可以采用java方式在不加“;”,也可以采用C+定义方式在末尾加上“;”,这两种方式都兼容,建议采用java定义方式。向.proto文件添加注释,可以使用C/C+/java风格的双斜杠(/) 语法格式。1.3 定义属性属性定义分为四部分:标注+类型+属性名+属性顺序号+默认值,其示意如下所示。标注类型属性名属性顺序号默认值requiredstringname= 1default=”;其中属性名与C+和java语言类似,不再解释;下面分别对标注、类型和属性顺序号加以详细介绍。其中包名和消息名以及其中变量名均采用java的命名规则驼峰
5、式命名法,驼峰式命名法规则见附件1。1.3.1 标注标注包括“required”、“optional”、“repeated”三种,其中required表示该属性为必选属性,否则对应的message“未初始化”,debug模式下导致断言,release模式下解析失败;optional表示该属性为可选属性,不指定,使用默认值(int或者char数据类型默认为0,string默认为空,bool默认为false,嵌套message默认为构造,枚举则为第一个)repeated表示该属性为重复字段,可看作是动态数组,类似于C+中的vector。如果为optional属性,发送端没有包含该属性,则接收端在解
6、析式采用默认值。对于默认值,如果已设置默认值,则采用默认值,如果未设置,则类型特定的默认值为使用,例如string的默认值为”。1.3.2 类型Protobuf的属性基本包含了c+需要的所有基本属性类型。protobuf属性C+属性java属性备注doubledoubledouble固定8个字节floatfloatfloat固定4个字节int32int32int32使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint32int64int64int64使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint64uint32uint32int使用变长编码uint64
7、uint64long使用变长编码sint32int32int采用zigzag压缩,对负数编码效率比int32高sint64int64long采用zigzag压缩,对负数编码效率比int64高fixed32uint32int总是4字节,如果数据>228,编码效率高于unit32fixed64uint64long总是8字节,如果数据>256,编码效率高于unit32sfixed32int32int总是4字节sfixed64int64long总是8字节boolboolbooleanstringstringString一个字符串必须是utf-8编码或者7-bit的ascii编码的文本byt
8、esstringByteString可能包含任意顺序的字节数据 Union类型定义Protobuf没有提供union类型,如果希望使用union类型,可以采用enum和optional属性定义的方式。例如,如果已经定义了Foo、Bar、Baz等message,则可以采用如下定义。message OneMessage enum Type FOO = 1; BAR = 2; BAZ = 3; / Identifies which field is filled in. required Type type = 1; / One of the following will be fil
9、led in. optional Foo foo = 2; optional Bar bar = 3; optional Baz baz = 4;1.3.3 属性顺序号属性顺序号是protobuf为了提高数据的压缩和可选性等功能定义的,需要按照顺序进行定义,且不允许有重复。1.4 其它可选项Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:1. 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。2. 消息级别,这样的
10、选项仅影响某个消息及其包含的所有字段。3. 字段级别,这样的选项仅仅响应与其相关的字段。1.4.1 java_package可选项java_package (file option): 是文件级别的选项,表明生成java类所在的包。如果在.proto文件中没有明确的声明java_package,就采用默认的包名。当然了,默认方式产生的 java包名并不是最好的方式,按照应用名称倒序方式进行排序的。如果不需要产生java代码,则该选项将不起任何作用。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/example/foo子目录中。如:option java_package =
11、"com.example.foo"1.4.2 java_outer_classname可选项java_outer_classname (file option): 是文件级别的选项,表明想要生成Java类的名称。如果在.proto文件中没有明确的java_outer_classname定义,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_to生成的java类名为FooBar.java),如果不生成java代码,则该选项不起任何作用。如:option java_outer_classname = "Ponyc
12、opter"注:主要是因为Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C+则不存在此限制。因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static ject.LYPhoneMessage.*。1.4.3 *_generic_services可选项cc_generic_services, java_generic_services, py_generic_services
13、 (file options): 在C+、java、python中protocol buffer编译器是否应该基于服务定义产生抽象服务代码。由于历史遗留问题,该值默认是true。但是自2.3.0版本以来,它被认为通过提供代码生成 器插件来对RPC实现更可取,而不是依赖于“抽象”服务。/ This file relies on plugins to generate service code.option cc_generic_services = false;option java_generic_services = false;option py_generic_services = fa
14、lse;1.4.4 message_set_wire_format可选项message_set_wire_format (message option):如果该值被设置为true,该消息将使用一种不同的二进制格式来与Google内部的MessageSet的老格式相兼容。对于Google外部的用户来说,该选项将不会被用到。如下所示:message Foo option message_set_wire_format = true; extensions 4 to max;1.4.5 import可选项Import可选项用于包含其它proto文件中定义的message或enum类型等。标准格式如下
15、import “to”;使用时,import的文件必须与当前文件处于同一个文件夹下,protoc无法完成不处于同一个文件夹下的import选项。1.4.6 optimize_foroptimize_for (fileoption): 是文件级别的选项,可以被设置为 SPEED, CODE_SIZE,or LITE_RUNTIME,缺省情况下是SPEED。这些值将通过如下的方式影响C+及java代码的生成:SPEED (default): protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。CODE_SIZE:
16、protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这种方式经常用在一些包含大量的.proto文件而且并不盲目追求速度的 应用中。LITE_RUNTIME: protocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf)。这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产
17、生的方法实现与SPEED模式不相上下,产生的类通过实现 MessageLite接口,但它仅仅是Messager接口的一个子集。option optimize_for = CODE_SIZE;1.4.7 packedpacked (field option): 如果该选项在一个整型基本类型上被设置为真,则采用更紧凑的编码方式。当然使用该值并不会对数值造成任何损失。在2.3.0版本之前,解析器将会忽略那些 非期望的包装值。因此,它不可能在不破坏现有框架的兼容性上而改变压缩格式。在2.3.0之后,这种改变将是安全的,解析器能够接受上述两种格式,但是在 处理protobuf老版本程序时,还是要多留意一
18、下。repeated int32 samples = 4 packed=true;1.4.8 defaultdefault = default_value: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如: optional int32 result_per_page = 3 default = 10。1.5 大数据量使用建议在使用过程中发现,对于大数据量的协议报文(
19、循环超过10000条),如果repeated修饰的属性为对象类型(诸如message 、Bytes、string等称为“对象类型”,其余的诸如int32、int64、float等等称为“原始类型”)时,效率非常低,而且占用的进程内存也非常大,建议采用如下方式优化。1.5.1 repeated message类型在message 中对repeated 标识的 message类型的字段需要做大量ADD操作时,可以考虑尽量避免嵌套message或者减少嵌套的message个数。实例如下所示:->按行存取按列存取1.5.2 repeated raw类型在message中对repeated 标识的
20、原始数据类型的字段需要做大量ADD操作(例如超过3千)时,可以考虑预分配数据空间,避免重复大量地分配空间。实例如下所示:->未预分配空间预分配空间1.5.3 repeated Bytes类型在protobuf中,Bytes基于C+ STL中的string实现,因为string内存管理的原因,程序空间往往较大。所以应用如果有很多repeated Bytes类型的字段的话,进程显示耗用大量内存,这与vector<string>的情况基本一致。1.6 Protocol Buffer消息升级原则 在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级
21、,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下: 1. 不要修改已经存在字段的标签号。 2. 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。 3. 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。 4. int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和
22、sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。 5. optional和repeated限定符也是相互兼容的。第2章 编译 .proto 文件可以通过定义好的.proto文件来生成Java、Python、C+代码,需要基于.proto文件运行protocol buffer编译器protoc。运行的命令如下所示:protoc -proto_path=IMPORT_PATH -cpp
23、_out=DST_DIR -java_out=DST_DIR -python_out=DST_DIR path/to/toMPORT_PATH声明了一个.proto文件所在的具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以 对-proto_path 写多次,它们将会顺序的被访问并执行导入。-I=IMPORT_PATH是它的简化形式。当然也可以提供一个或多个输出路径:-cpp_out 在目标目录DST_DIR中产生C+代码,可以在 /cpp-generated.html中查看更多。-java_out 在目标目录DST_DIR中产生Java代码,可以在 /java-ge
24、nerated.html中查看更多。-python_out 在目标目录 DST_DIR 中产生Python代码,可以在 /docs/reference/python-generated.html中查看更多。作为一种额外的使得,如果DST_DIR 是以.zip或.jar结尾的,编译器将输出结果打包成一个zip格式的归档文件。.jar将会输出一个 Java JAR声明必须的manifest文件。注:如果该输出归档文件已经存在,它将会被重写,编译器并没有做到足够的智能来为已经存在的归档文件添加新的文 件。你必须提供一个或多个.proto文件作为输入。多个.proto文件能够一次全部声明。虽然这些文件
25、是相对于当前目录来命名的,每个文件必须在一个IMPORT_PATH中,只有如此编译器才可以决定它的标准名称。第3章 使用message3.1 类成员变量的访问在生成的.h文件中定义了类成员的访问方法。例如,对于Person类,定义了name、id、email、phone等成员的访问方法。获取成员变量值直接采用使用成员变量名(全部为小写),设置成员变量值,使用在成员变量名前加set_的方法。对于普通成员变量(required和optional)提供has_方法判断变量值是否被设置;提供clear_方法清除设置的变量值。对于string类型,提供多种set_方法,其参数不同。同时,提供了一个mut
26、able_方法,返回变量值的可修改指针。对于repeated变量,提供了其它一些特殊的方法:l _size方法:返回repeated fieldsl 通过下脚标访问其中的数组成员组l 通过下脚标返回其中的成员的mutable_的方法l _add方法:增加一个成员。/ nameinline bool has_name() const;inline void clear_name();inline const :std:string& name() const;inline void set_name(const :std:string& value);inline void se
27、t_name(const char* value);inline :std:string* mutable_name();/ idinline bool has_id() const;inline void clear_id();inline int32_t id() const;inline void set_id(int32_t value);/ emailinline bool has_email() const;inline void clear_email();inline const :std:string& email() const;inline void set_em
28、ail(const :std:string& value);inline void set_email(const char* value);inline :std:string* mutable_email();/ phoneinline int phone_size() const;inline void clear_phone();inline const :google:protobuf:RepeatedPtrField< :tutorial:Person_PhoneNumber >& phone() const;inline :google:protobu
29、f:RepeatedPtrField< :tutorial:Person_PhoneNumber >* mutable_phone();inline const :tutorial:Person_PhoneNumber& phone(int index) const;inline :tutorial:Person_PhoneNumber* mutable_phone(int index);inline :tutorial:Person_PhoneNumber* add_phone();3.2 标准message方法生成的.h文件中的class都继承自:google:prot
30、obuf:Message类,Message类提供了一些方法可以检查或者操作整个message,包括l bool IsInitialized() const;检查是否所有required变量都已经初始化; l string DebugString() const;返回message的可阅读的表示,主要用于调试程序;l void CopyFrom(const Person& from);使用一个message的值覆盖本message;l void Clear();清空message的所有成员变量值。3.3 编码和解码函数每个message类都提供了写入和读取message数据的方法,包括l
31、 bool SerializeToString(string* output) const;把message编码进output。 l bool ParseFromString(const string& data);从string解码到messagel bool SerializeToArray(char* buf,int size) const;把message编码进数组buf.l bool ParseFromArray(const char* buf,int size);把buf解码到message。此解码方法效率较ParseFromString高很多,所以一般用这种方法解码。l
32、bool SerializeToOstream(ostream* output) const;把message编码进ostreaml bool ParseFromIstream(istream* input);从istream解码到message备注:发送接收端所使用的加码解码方法不一定非得配对,即发送端用SerializeToString 接收端不一定非得用ParseFromString ,可以使用其他解码方法。3.4 简单message生成的C+代码这里先定义一个最简单的message,其中只是包含原始类型的字段。option optimize_for = LITE_RUNTIME; me
33、ssage LogonReqMessage required int64 acctID = 1; required string passwd = 2; 由于我们在MyMessage文件中定义选项optimize_for的值为LITE_RUNTIME,因此由该.proto文件生成的所有C+类的父类均为:google:protobuf:MessageLite,而非:google:protobuf:Message。MessageLite类是Message的父类,在MessageLite中将缺少Protocol Buffer对反射的支持,而此类功能均在Message类中提供了具体的实现。使用LITE
34、版本的Protocol Buffer。这样不仅可以得到更高编码效率,而且生成代码编译后所占用的资源也会更少,至于反射所能带来的灵活性和极易扩展性。下面我们来看一下由message LogonReqMessage生成的C+类的部分声明,以及常用方法的说明性注释。class LogonReqMessage : public :google:protobuf:MessageLite public: LogonReqMessage(); virtual LogonReqMessage(); / implements Message - /下面的成员函数均实现自MessageLite中的虚函数。 /创建
35、一个新的LogonReqMessage对象,等同于clone。 LogonReqMessage* New() const; /用另外一个LogonReqMessage对象初始化当前对象,等同于赋值操作符重载(operator=) void CopyFrom(const LogonReqMessage& from); /清空当前对象中的所有数据,既将所有成员变量置为未初始化状态。 void Clear(); /判断当前状态是否已经初始化。 bool IsInitialized() const; /在给当前对象的所有变量赋值之后,获取该对象序列化后所需要的字节数。 int ByteSize
36、() const; /获取当前对象的类型名称。 :std:string GetTypeName() const; / required int64 acctID = 1; /下面的成员函数都是因message中定义的acctID字段而生成。 /这个静态成员表示AcctID的标签值。命名规则是k + FieldName(驼峰规则) + FieldNumber。 static const int kAcctIDFieldNumber = 1; /如果acctID字段已经被设置返回true,否则false。 inline bool has_acctid() const; /执行该函数后has_acc
37、tid函数将返回false,而下面的acctid函数则返回acctID的缺省值。 inline void clear_acctid(); /返回acctid字段的当前值,如果没有设置则返回int64类型的缺省值。 inline :google:protobuf:int64 acctid() const; /为acctid字段设置新值,调用该函数后has_acctid函数将返回true。 inline void set_acctid(:google:protobuf:int64 value); / required string passwd = 2; /下面的成员函数都是因message中定义
38、的passwd字段而生成。这里生成的函数和上面acctid /生成的那组函数基本相似。因此这里只是列出差异部分。 static const int kPasswdFieldNumber = 2; inline bool has_passwd() const; inline void clear_passwd(); inline const :std:string& passwd() const; inline void set_passwd(const :std:string& value); /对于字符串类型字段设置const char*类型的变量值。 inline void
39、 set_passwd(const char* value); inline void set_passwd(const char* value, size_t size); /可以通过返回值直接给passwd对象赋值。在调用该函数之后has_passwd将返回true。 inline :std:string* mutable_passwd(); /释放当前对象对passwd字段的所有权,同时返回passwd字段对象指针。调用此函数之后,passwd字段对象 /的所有权将移交给调用者。此后再调用has_passwd函数时将返回false。 inline :std:string* release
40、_passwd(); private: . . ;下面是读写LogonReqMessage对象的C+测试代码和说明性注释。void testSimpleMessage() printf("=This is simple message.=n"); /序列化LogonReqMessage对象到指定的内存区域。 LogonReqMessage logonReq; logonReq.set_acctid(20); logonReq.set_passwd("Hello World"); /提前获取对象序列化所占用的空间并进行一次性分配,从而避免多次分配 /而造成
41、的性能开销。通过该种方式,还可以将序列化后的数据进行加密。 /之后再进行持久化,或是发送到远端。 int length = logonReq.ByteSize(); char* buf = new charlength; logonReq.SerializeToArray(buf,length); /从内存中读取并反序列化LogonReqMessage对象,同时将结果打印出来。 LogonReqMessage logonReq2; logonReq2.ParseFromArray(buf,length); printf("acctID = %I64d, password = %sn&
42、quot;,logonReq2.acctid(),logonReq2.passwd().c_str(); delete buf; 3.5 嵌套message生成的C+代码enum UserStatus OFFLINE = 0; ONLINE = 1; enum LoginResult LOGON_RESULT_SUCCESS = 0; LOGON_RESULT_NOTEXIST = 1; LOGON_RESULT_ERROR_PASSWD = 2; LOGON_RESULT_ALREADY_LOGON = 3; LOGON_RESULT_SERVER_ERROR = 4; message Us
43、erInfo required int64 acctID = 1; required string name = 2; required UserStatus status = 3; message LogonRespMessage required LoginResult logonResult = 1; required UserInfo userInfo = 2; /这里嵌套了UserInfo消息。 对于上述消息生成的C+代码,UserInfo因为只是包含了原始类型字段,因此和上例中的LogonReqMessage没有太多的差别,这里也就不在重复列出了。由于LogonRespMessag
44、e消息中嵌套了UserInfo类型的字段,在这里我们将仅仅给出该消息生成的C+代码和关键性注释。class LogonRespMessage : public :google:protobuf:MessageLite public: LogonRespMessage(); virtual LogonRespMessage(); / implements Message - . . /这部分函数和之前的例子一样。 / required .LoginResult logonResult = 1; /下面的成员函数都是因message中定义的logonResult字段而生成。 /这一点和前面的例子基
45、本相同,只是类型换做了枚举类型LoginResult。 static const int kLogonResultFieldNumber = 1; inline bool has_logonresult() const; inline void clear_logonresult(); inline LoginResult logonresult() const; inline void set_logonresult(LoginResult value); / required .UserInfo userInfo = 2; /下面的成员函数都是因message中定义的UserInfo字段而
46、生成。 /这里只是列出和非消息类型字段差异的部分。 static const int kUserInfoFieldNumber = 2; inline bool has_userinfo() const; inline void clear_userinfo(); inline const :UserInfo& userinfo() const; /可以看到该类并没有生成用于设置和修改userInfo字段set_userinfo函数,而是将该工作 /交给了下面的mutable_userinfo函数。因此每当调用函数之后,Protocol Buffer都会认为 /该字段的值已经被设置了,
47、同时has_userinfo函数亦将返回true。在实际编码中,我们可以 /通过该函数返回userInfo字段的内部指针,并基于该指针完成userInfo成员变量的初始化工作。 inline :UserInfo* mutable_userinfo(); inline :UserInfo* release_userinfo(); private: . . ;下面是读写LogonRespMessage对象的C+测试代码和说明性注释。void testNestedMessage() printf("=This is nested message.=n"); LogonRespMe
48、ssage logonResp; logonResp.set_logonresult(LOGON_RESULT_SUCCESS); /如上所述,通过mutable_userinfo函数返回userInfo字段的指针,之后再初始化该对象指针。 UserInfo* userInfo = logonResp.mutable_userinfo(); userInfo->set_acctid(200); userInfo->set_name("Tester"); userInfo->set_status(OFFLINE); int length = logonRes
49、p.ByteSize(); char* buf = new charlength; logonResp.SerializeToArray(buf,length); LogonRespMessage logonResp2; logonResp2.ParseFromArray(buf,length); printf("LogonResult = %d, UserInfo->acctID = %I64d, UserInfo->name = %s, UserInfo->status = %dn" ,logonResp2.logonresult(),logonRes
50、p2.userinfo().acctid(),logonResp2.userinfo().name().c_str(),logonResp2.userinfo().status(); delete buf; 3.6 repeated嵌套message生成的C+代码message BuddyInfo required UserInfo userInfo = 1; required int32 groupID = 2; message RetrieveBuddiesResp required int32 buddiesCnt = 1; repeated BuddyInfo buddiesInfo
51、= 2; 对于上述消息生成的代码,我们将只是针对RetrieveBuddiesResp消息所对应的C+代码进行详细说明,其余部分和前面小节的例子基本相同,可直接参照。而对于RetrieveBuddiesResp类中的代码,我们也仅仅是对buddiesInfo字段生成的代码进行更为详细的解释。class RetrieveBuddiesResp : public :google:protobuf:MessageLite public: RetrieveBuddiesResp(); virtual RetrieveBuddiesResp(); . . /其余代码的功能性注释均可参照前面的例子。 / repeated .BuddyInfo buddiesInfo = 2; static const int kBuddiesInfoFieldNumber = 2; /返回数组中成员的数量。 inline int buddiesinfo_size() const; /清空数组中
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 优化会计流程的方法试题及答案
- 外语考试中的安全管理核心内容试题及答案
- 消防工程工程案例分析试题及答案
- 无人机驾驶员试题与解析试题及答案
- 2025年护士考试健康教育试题及答案
- 护士应对突发事件的能力提升试题及答案
- 2024年高级会计考试战术分析试题及答案
- 剖析审计师职业发展试题及答案
- 消防事故应急预案演练计划试题及答案
- 2025年入团考试专业试题及答案评估
- 2025年北京市水务局所属事业单位招聘工作人员101人笔试高频重点提升(共500题)附带答案详解
- 2025届贵州省遵义第四中学高考语文全真模拟密押卷含解析
- 【MOOC】创业基础-暨南大学 中国大学慕课MOOC答案
- 中建专项施工电梯专项施工方案
- 全国各省市一览表
- 餐饮的劳务合同(2篇)
- 山东省潍坊市2023-2024学年高二下学期期末考试 历史 含解析
- 2024-2025学年中职历史世界历史高教版(2023)教学设计合集
- 阿里云:云上数字政府之:政务云统筹运营建设指南
- 鸽巢问题-公开课教案教学设计课件案例试卷题
- 《磷污染的物化处理》笔记
评论
0/150
提交评论