protobuf —— 快速上手

news2024/11/15 20:05:30

protobuf —— 快速上手

  • 创建 .proto 文件
    • 添加注释
    • 指定proto3语法
    • package 声明符
    • 定义消息(message)
  • 定义消息字段
      • 字段定义基本格式
      • 字段名称命名规范
      • 字段类型
      • 字段唯一编号
      • 示例
    • 转换关系
      • 示例:增加姓名和年龄字段
    • 字段唯一编号
      • 字段编号范围
      • 编码效率
      • 实践指导
  • 编译 contacts.proto 文件,生成 C++ 文件
    • 编译命令
  • 编译 contacts.proto 文件后会生成什么
      • 序列化注意事项
  • 序列化和反序列化

我们今天来快速上手protobuf的用法,我们会用一个简单的通讯录来进行讲解:

如果还没有装好protobuf的小伙伴可以点击这里:

https://blog.csdn.net/qq_67693066/article/details/139215057

如果大家使用Linux的话,并且对版本没有太多要求的,可以直接用Linux的软件包来安装,Centos和Ubuntu都可以,我这里以Centos为例:
在这里插入图片描述
Ubuntu就换成apt,安装的版本是14的,也是21年发布的版本,也不错。

创建 .proto 文件

装好protobuf之后,我们用vscode连接,大家可以下载一个支持protobuf语法的插件,我下的是这一个:
在这里插入图片描述

然后创建一个后缀为.proto的文件:
在这里插入图片描述
创建 .proto 文件时,文件命名应该使用全小写字母命名,多个字母之间用 _ 连接。 例如:lower_snake_case.proto

添加注释

向文件添加注释,可使使用 // 或者 /* ... */
在这里插入图片描述

指定proto3语法

Protocol Buffers 语言版本3,简称 proto3,是 .proto 文件的最新语法版本。Proto3 简化了 Protocol Buffers 语言,既易于使用,又能在更多编程语言中运用自如。它支持使用 Java、C++、Python 等多种语言生成 Protocol Buffer 代码。

.proto 文件里,需使用 syntax = "proto3"; 来指定文件遵循 proto3 语法,这条声明必须位于除去注释内容的首行。如果未作指定,编译器将默认采用 proto2 语法。
例如,在通讯录 1.0 的 contacts.proto 文件中,指定使用 proto3 语法的方式如下所示:
在这里插入图片描述

package 声明符

在 Protocol Buffers(protobuf)的 .proto 文件中,package 是一个可选但强烈推荐的声明语句。它扮演着命名空间的角色,用于组织和分隔不同的消息类型定义,确保即便在导入多个 .proto 文件到同一个项目时,消息类型之间也不会发生名称冲突。简而言之,通过指定独一无二的包名,可以有效避免定义的消息类型重名问题。

以“通讯录 1.0”项目的文件为例,为了明确该文件中消息类型的所属命名空间,可以在文件开头添加如下的 package 声明:
在这里插入图片描述

定义消息(message)

在分布式系统和网络通信中,消息(message)扮演着至关重要的角色,它是定义数据交换格式的基础。为何需要定义消息? 主要原因有二:

  1. 协议定制:网络通信依赖于双方遵守的协议,这些协议定义了数据如何封装和解析。通过在.proto文件中定义消息,您可以精确地描述数据的结构,包括字段类型、名称及其顺序。这样,Protocol Buffers工具就能自动生成对应语言的源代码,实现序列化(将数据结构转换为字节流以便网络传输)和反序列化(将接收到的字节流还原为数据结构)功能。比如TCP/IP协议中的报文头和数据部分,就是一个典型的结构化数据示例。
  1. 数据持久化:当数据需要被存储到诸如数据库这类持久化存储介质时,清晰、统一的数据结构变得尤为重要。消息定义不仅帮助组织数据,还能确保数据的一致性和高效存储。通过将数据封装在消息中,可以更容易地映射到数据库的表结构或文档模型中,简化数据的存取逻辑。

以“通讯录1.0”为例,为“联系人”创建一个消息定义,就是预先规划好联系人信息的结构,比如姓名、电话号码、电子邮箱等,使得在实际编码时,可以直接利用Protocol Buffers生成的类来操作这些数据,无需手动处理序列化和解析的细节。这样,无论是网络间的数据交换,还是数据库的存储读取,都能保持高效和一致。
.proto 文件中定义⼀个消息类型的格式为:

message 消息类型名{
}

在这里插入图片描述

简单来说,message有点像C++中的class进行属性的封装。

定义消息字段

字段定义基本格式

字段定义遵循以下基本格式:

字段类型 字段名 = 字段唯一编号;

字段名称命名规范

  • 全小写字母:字段名应全部使用小写字母,以保持一致性并符合protobuf的命名约定。
  • 使用下划线_连接:如果字段名由多个单词组成,单词间应使用下划线分隔,提高可读性。例如,first_name而非firstName

字段类型

字段类型分为标量数据类型和特殊类型两大类:

  • 标量数据类型包括但不限于:

    • int32, int64: 整型
    • float, double: 浮点数
    • bool: 布尔值
    • string: 字符串
    • bytes: 二进制数据
  • 特殊类型

    • 枚举(enum): 自定义的枚举类型,用于限定某个字段的取值范围。
    • 其他消息类型: 引用其他已定义的消息类型作为字段类型,实现复杂数据结构的嵌套。

字段唯一编号

  • 编号要求:每个字段都应分配一个唯一的整数编号,用于在序列化和反序列化过程中识别字段。
  • 编号范围:通常情况下,编号1到15的字段在编码时较为节省空间,因为它们可以用一个字节编码(如果字段没有被省略)。16及以上的编号需要更多的字节来编码。
  • 不可变更性:一旦消息发布并被使用,字段编号就不应该被修改或重新分配,因为这将破坏与旧版本的兼容性。新增字段应分配新的编号,而避免更改现有字段编号。

示例

下面是一个简单的Person消息定义示例:

syntax = "proto3";

message Person {
  int32 id = 1;          // 唯一ID
  string first_name = 2;  // 名
  string last_name = 3;   // 姓
  int32 age = 4;          // 年龄
  bool is_student = 5;    // 是否为学生
}

在这个例子中,Person消息包含了几个基本的标量数据类型的字段,并且每个字段都分配了一个唯一的编号。

转换关系

这个表格更清晰地展示.proto文件中定义的消息字段类型及其与C++语言类型的对应关系:

.proto TypeC++ TypeNotes
doubledouble-
floatfloat-
int32int32_t使用变长编码[1]。负数编码效率较低,如果字段可能为负值,建议使用sint32
int64int64_t使用变长编码[1]。负数编码效率较低,如果字段可能为负值,建议使用sint64
uint32uint32_t使用变长编码[1]
uint64uint64_t使用变长编码[1]
sint32int32_t使用变长编码[1]。符号整型,负值编码效率高于常规int32
sint64int64_t使用变长编码[1]。符号整型,负值编码效率高于常规int64
fixed32uint32_t定长4字节。如果值通常大于2^28,则比uint32更高效。
fixed64uint64_t定长8字节。如果值通常大于2^56,则比uint64更高效。
sfixed32int32_t定长4字节。
sfixed64int64_t定长8字节。
boolbool-
stringstd::string包含UTF-8和ASCII编码的字符串,长度不超过2^32。
bytesstd::string可包含任意字节序列,长度不超过2^32。

[1] 变长编码指的是Protocol Buffers使用一种可变长度的编码方式(如VarInt编码),这种编码方式对于较小数值占用空间较少,但大数值会占用更多字节。对于fixed32fixed64sfixed32sfixed64,它们使用固定长度编码,不论数值大小,始终占用指定的字节数。

示例:增加姓名和年龄字段

假设我们要在一个消息中增加name(字符串类型)和age(假设为非负整数,使用uint32类型)字段,那么在.proto文件中的定义可能是这样的:

message Person {
  string name = 1;  // 姓名
  uint32 age = 2;   // 年龄
}

这里name字段使用了字符串类型(对应C++中的std::string),age字段使用了无符号32位整型(对应C++中的uint32_t),并为每个字段分配了唯一的编号。

字段唯一编号

这里还要说明一下字段唯一编号:

在Protocol Buffers(protobuf)中,字段唯一编号的选取是一个关键设计决策,因为它直接影响到消息的序列化效率和兼容性。下面是关于字段编号范围及编码的一些关键点:

字段编号范围

字段编号的有效范围是从1到536,870,911(即2^29 - 1)。但是,需要注意的是,19000至19999这一区间内的编号是被protobuf协议实现预留的,不允许用户直接使用。尝试使用这些预留编号会导致编译时错误,提醒开发者这些编号不可用,因为它们在protobuf内部有特殊的用途。

编码效率

  • 编号与编码大小:为了优化序列化后的消息体积,protobuf采用了变长编码。其中,编号小于等于15的字段仅需1个字节来编码编号和类型信息;编号在16至2047之间的字段需要2个字节;更大的编号则需要更多的字节。这意味着,编号较小的字段在序列化时更为高效。
  • 优化建议:鉴于此,建议将频繁出现且长度较短的字段分配编号在1至15之间,以减少消息的整体大小。同时,应保留一部分低编号以备未来可能添加的常用字段。

实践指导

  • 避免预留区间:确保在定义.proto文件时,字段编号避开19000至19999这个预留区间。
  • 编号策略:合理规划字段编号,考虑当前及未来的扩展性。频繁使用的字段应给予更低的编号以优化效率,同时留出一定编号空间供后续扩展使用。

通过遵循上述原则,可以确保你的protobuf消息定义既高效又具有良好的向前兼容性,便于维护和升级。

编译 contacts.proto 文件,生成 C++ 文件

编译命令

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto

protoc 是 Protocol Buffers(protobuf)的编译器,用于将 .proto 文件转换为目标编程语言的源代码文件。您给命令行参数示例中,各部分的含义如下:

  • --proto_path=IMPORT_PATH-I=IMPORT_PATH:
  • 作用:指定 .proto 文件的搜索路径。当你在 .proto 文件中使用 import 语句引入其他 .proto 文件时,protoc 会根据这个路径去查找这些被引用的文件。
  • 参数说明IMPORT_PATH 是一个或多个目录路径,可以多次使用该选项指定多个路径。如果不指定,默认会在当前目录下查找。
  • --cpp_out=DST_DIR
  • 作用:指定编译生成的C++源代码输出目录。当您希望将 .proto 文件编译为C++代码时,使用这个选项。
  • 参数说明DST_DIR 是您希望输出C++源代码的目录路径。protoc 会在这个目录下生成对应的 .pb.cc(源文件)和 .pb.h(头文件),这些文件包含了序列化和反序列化消息所需的函数和其他必要的代码。
  • path/to/file.proto
  • 作用:指定需要编译的 .proto 文件路径。
  • 说明:这是您要编译的protobuf定义文件的完整或相对路径。protoc 会读取这个文件并根据指定的输出选项(如 --cpp_out)生成目标语言的代码。

综上,整个命令的含义是:使用 protoc 编译器,从指定的导入路径中查找任何被导入的 .proto 文件,然后将 path/to/file.proto 文件编译为C++源代码,并将生成的文件输出到 DST_DIR 目录下。这样做的目的是为了让开发者能够方便地将protobuf消息类型集成到他们的C++项目中。

举个例子
当然,让我们通过一个具体的例子来进一步说明如何使用这些参数。假设你有一个项目结构如下:

/my_project/
|-- protos/
|   |-- message.proto
|-- src/
|-- CMakeLists.txt
|-- main.cpp

在这个场景中,你有一个名为 message.proto 的 Protocol Buffers 定义文件,位于 protos/ 目录下,你想将它编译成C++代码,并将生成的文件放在 src/ 目录中以便在你的C++项目中使用。

你可以打开终端(命令行界面),并导航到 /my_project/ 目录,然后执行以下命令:

protoc --proto_path=./protos --cpp_out=./src ./protos/message.proto

//或者
protoc -I=./protos --cpp_out=./src ./protos/message.proto

这里发生了什么:

  • --proto_path=./protos 指定了 .proto 文件的搜索路径为当前目录下的 protos/ 目录。如果有 message.protoimport 了其他 .proto 文件,编译器会在这个目录下查找它们。
  • --cpp_out=./src 指定编译生成的C++源代码放置在 src/ 目录中。
  • ./protos/message.proto 是你要编译的具体 .proto 文件路径。

执行完这个命令后, message.proto 定义了一个消息类型,protoc 编译器将在 src/ 目录下生成两个文件:message.pb.ccmessage.pb.h,这些就是你可以包含在C++项目中使用的源代码和头文件,用于处理该消息类型的序列化与反序列化等操作。

编译 contacts.proto 文件后会生成什么

编译 contacts.proto 文件后,会生成所选择语言的代码,我们选择的是C++,所以编译后生成了两个文件: contacts.pb.h contacts.pb.cc 。
在这里插入图片描述

对于编译生成的 C++ 代码,包含了以下内容 :

  • 对于每个 message ,都会生成⼀个对应的消息类。
  • 在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的方法。
  • 编辑器会针对于每个 .proto 文件生成 .h 和 .cc 文件,分别用来存放类的声明与类的实现。

contacts.pb.h 部分代码展示:

 // string name = 1;
  void clear_name();
  const std::string& name() const;
  template <typename ArgT0 = const std::string&, typename... ArgT>
  void set_name(ArgT0&& arg0, ArgT... args);
  std::string* mutable_name();
  PROTOBUF_NODISCARD std::string* release_name();
  void set_allocated_name(std::string* name);
  private:
  const std::string& _internal_name() const;
  inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value);
  std::string* _internal_mutable_name();
  public:

  // int32 age = 2;
  void clear_age();
  int32_t age() const;
  void set_age(int32_t value);
  private:
  int32_t _internal_age() const;
  void _internal_set_age(int32_t value);
  public:

上述的例子中:

  • 每个字段都有设置和获取的方法, getter 的名称与小写字段完全相同,setter 方法以 set_ 开头。
  • 每个字段都有⼀个 clear_ 方法,可以将字段重新设置回 empty 状态。
    contacts.pb.cc 中的代码就是对类声明方法的⼀些实现,在这里就不展开了。

序列化和反序列化方法在哪里呢?在消息类的父类MessageLite 中,提供了读写消息实例的方法,包括序列化方法和反序列化方法:

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

  // Merges this message's unknown field data (if any).  This works whether
  // the message is a lite or full proto (for legacy reasons, lite and full
  // return different types for MessageType::unknown_fields()).
  template <typename MessageType>
  bool MergeFromMessage(const MessageType& message);

  // Serialization.
  bool SerializeToString(std::string* output) const;
  bool SerializeToCodedStream(io::CodedOutputStream* output) const;
  static const UnknownFieldSet& default_instance();

理解您的要求,这里是对于序列化概念及Protocol Buffers(protobuf)中消息序列化方法的解释,稍作调整以供参考:

序列化注意事项

  • 二进制输出:序列化过程将结构化数据转换成紧凑的二进制形式,而非易于阅读的文本格式。这有助于减小程序体积,提高网络传输效率和存储效率。
  • 多样化的序列化方法:Protocol Buffers提供了多种序列化API,包括但不限于SerializeToString()SerializeToOstream()SerializeToArray()等。这些方法虽然输出形式各异——字符串、输出流或字节数组,但核心目的相同:将消息对象转化为二进制数据,适应不同应用场景的需要。
  • 不变性保证:序列化操作通过const成员函数实现,意味着调用序列化函数不会修改消息对象本身的内在状态。数据的转换发生在序列化过程中,并将结果输出到指定的目标(如内存、字符串或流),而不影响原始对象。
  • 深入探索API:欲了解更多关于protobuf消息对象的序列化及其他功能,可查阅protobuf官方文档中的消息API完整列表,那里详尽地介绍了每种方法的使用方法和适用场景,帮助开发者高效利用protobuf的强大功能。

序列化和反序列化

创建⼀个测试文件 main.cc,方法中我们实现:

  • 对⼀个联系人的信息使用 PB 进行序列化,并将结果打印出来。
  • 对序列化后的内容使用 PB 进行反序列,解析出联系人信息并打印出来。
#include <iostream> 
#include "contacts.pb.h" // 引⼊编译⽣成的头⽂件
using namespace std; 
 
int main() 
{ 
    string people_str; 
    {
        // .proto⽂件声明的package,通过protoc编译后,会为编译⽣成的C++代码声明同名的
        // 其范围是在.proto ⽂件中定义的内容
        contacts::PeopleInfo people; 
        people.set_age(20); 
        people.set_name("张珊"); 
        // 调⽤序列化⽅法,将序列化后的⼆进制序列存⼊string中
        if (!people.SerializeToString(&people_str)) { 
        cout << "序列化联系⼈失败." << endl; 
        }
        // 打印序列化结果
        cout << "序列化后的 people_str: " << people_str << endl; 
    }
    
    {
        contacts::PeopleInfo people; 
        // 调⽤反序列化⽅法,读取string中存放的⼆进制序列,并反序列化出对象
        if (!people.ParseFromString(people_str)) { 
        cout << "反序列化出联系⼈失败." << endl; 
        } 
        // 打印结果
        cout << "Parse age: " << people.age() << endl; 
        cout << "Parse name: " << people.name() << endl; 
    }
}

代码书写完成后,编译 main.cc,生成可执行程序 TestProtoBuf :

g++ main.cc contacts.pb.cc -o TestProtoBuf -std=c++11 -lprotobuf

在这里插入图片描述

其中-lprotobuf必加,否则会有连接错误,-std=c++11可以为更高级。

执行TestProtoBuf ,可以看见 people 经过序列化和反序列化后的结果:
在这里插入图片描述

由于 ProtoBuf 是把联系⼈对象序列化成了⼆进制序列,这里用 string 来作为接收二进制序列的容器。所以在终端打印的时候会有换行等⼀些乱码显示。

所以相对于 xml 和 JSON 来说,因为被编码成⼆进制,破解成本增本,ProtoBuf 编码是相对安全的。

最后,总结一下:
在这里插入图片描述

  1. 创建 .proto 文件:通过定义message类型,我们详细规划了数据对象的结构和组成部分,包括字段名称、类型及其编码规则,为后续的通信协议奠定基础。
  2. 通过 protoc 工具将 .proto 文件转换为编程语言绑定代码protoc 编译器读取.proto文件并生成目标语言(如C++、Java、Python等)的接口和实现代码。这些代码被组织在头文件(.h)中声明接口,源文件(.cc或其他后缀)中实现细节,方便开发者在项目中直接调用。
  3. 整合生成的接口到项目中,实现数据操作和消息的编解码:将编译得到的头文件包含到项目源代码中,即可利用预生成的类和方法来实例化消息对象,设置和检索字段值。同时,利用内置的序列化方法(如SerializeToStringParseFromString等)轻松地在二进制格式与消息对象之间转换,支撑了数据在网络间的高效传输与存储需求。这样,开发者便能集中精力于业务逻辑,而不必关注底层的数据序列化和协议细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1706020.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ADC模数转换器的简介及参数详解

ADC全称是Analog-to-Digital Converter模数转换器&#xff0c;一般我们把模拟信号(Analog signal) 用A来进行简写&#xff0c;数字信号(digital signal) 用D来表示。是用于将模拟形式的连续信号转换为数字形式的离散信号的一类设备。 今天我们主要说ADC的参数&#xff0c;我们把…

安全阀检测要求标准:如何提高检测效率与准确性?

安全阀&#xff0c;作为承压设备的重要保护元件&#xff0c;其性能的稳定性和可靠性直接关系到设备的运行安全。 因此&#xff0c;对安全阀进行定期、规范的检测显得尤为重要。接下来&#xff0c;佰德将围绕安全阀的检测要求标准&#xff0c;从检测前准备工作到检测报告与记录…

【网络服务】正向代理和反向代理到底是什么意思

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 前言 在学习和…

kubeadm引导欧拉系统高可用的K8S1.28.X

文章目录 一. 核心组件架构二. 有状态与无状态应用三. 资源对象3.1 规约与状态3.2 资源的分类-元数据,集群,命名空间3.2.1 元数据3.2.2 集群资源 3.3 命名空间级3.3.1 pod3.3.2 pod-副本集3.3.3 pod-控制器 四. Kubeadm安装k8s集群4.1 初始操作4.2 ~~所有节点安装Docker&#x…

使用虚拟卡注册亚马逊店铺亲测墨西哥、北美都可以亲测~~

这几天测试了使用虚拟信用卡注册墨西哥与北美站的店铺&#xff0c;成功下店&#xff0c;总有人说会被扫&#xff0c;其实去年12月费就有使用卡注册店铺&#xff0c;至今还是好的 当然也不是完全都没有可能店铺不会挂&#xff0c;挂的时候提供账单就好了&#xff0c;直接找客服…

设计模式——工厂三兄弟之简单工厂

1.业务需求 ​ 大家好&#xff0c;我是菠菜。在介绍这期简单工厂设计模式前&#xff0c;我们先来看看这样的需求&#xff1a;制作一个简单的计算器&#xff0c;能够实现控制台输入俩个数以及运算符完成运算。 2.初步实现 实现第一版思路&#xff1a; ​ 创建计算器类&…

k8s污点及容忍度+实践

k8s污点及容忍度实践 前言 一、标签、注解和污点1、如何查询污点及一个容器或者工作负载的标签注解a、通过describe命令查看一个节点的详细信息 2、标签查询修改 二、污点和容忍度容忍度&#xff08;Tolerations&#xff09;2、taint / label命令及解释 三、为应用程序设置干扰…

NameSilo + Cloudflare 给网站加个域名(附 NameSilo 购买域名优惠码)

网站做好了之后,下一步就是买域名 在国内买域名的话,还需要备案,个人名下备案好像是还有限制,我就去 NameSilo 上面买的 在买之前,对比过几家 比如: godaddy/namecheap/cloudflare 本来是倾向于在 godaddy 上面买的,因为它支持支付宝支付,但是在详细看的时候,发现如果购买一年…

腾讯云COS上传文件出现的问题

1、没有配置 ObjectMetadata 的文件长度 腾讯云COS上传文件出现数据损坏问题_no content length specified for stream data. strea-CSDN博客 2、 使用 FileInputStream使用完没有及时关闭导致报错 ClientAbortException: java.nio.channels.ClosedChannelException 添加…

AI Agent:自主性、反应性与交互性的融合,将颠覆软件行业

Agent来袭&#xff1a;AI如何变身软件界的超级英雄&#xff1f; ©作者|Zhongmei 来源|神州问学 前言 “AI Agent不仅会彻底改变计算机的使用方式&#xff0c;它还将颠覆软件行业&#xff0c;是一个对科技行业的冲击波&#xff0c;是一场自‘输入命令到点击图标’变革之后…

266 基于matlab的FCM+KFCM模糊C均值聚类分析算法

基于matlab的FCMKFCM模糊C均值聚类分析算法&#xff0c;输入分割对象&#xff0c;对分割对象进行参数设置&#xff0c;包括分割类别数&#xff0c;核参数。输出分割结果。具有GUI操作界面。程序已调通&#xff0c;可直接运行。 266 FCM KFCM 模糊C均值聚类 GUI - 小红书 (xiaoh…

站内信设计

参考文章&#xff1a;https://cloud.tencent.com/developer/article/1684449 b站站内信业务设计&#xff1a; 消息的类型分为&#xff1a; 1、系统消息 2、、点赞、回复等用户行为之间的消息(事件提醒) 3、用户之间的消息 系统消息 用一个用户消息表可以吗&#xff1f; 可…

主副坐标轴作图(即双坐标轴)

主副坐标轴显示&#xff1a; import numpy as np from matplotlib import pyplot as plt plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus] False xnp.arange(0.1,10,0.01) data1np.exp(x) data2np.log(x) fig,ax1plt.subplots() #subplots一定要带s…

室内也可以用北斗定位?还能用RTK?

室内卫星顾名思义&#xff0c;就是在室内有遮挡环境中的卫星定位技术&#xff0c;众所周知&#xff0c;目前全球几大GNSS定位系统已经很完善&#xff0c;但是GNSS有个致命的弱点&#xff0c;就是地面如果有遮挡就没有信号&#xff0c;在这样的条件下&#xff0c;在室内定位场景…

YOLOv8+PyQt5农作物杂草检测系统完整资源集合(yolov8模型,从图像、视频和摄像头三种路径识别检测,包含登陆页面、注册页面和检测页面)

农作物杂草检测YOLOV8(https://mbd.pub/o/bread/mbd-ZpaTl5tv)_哔哩哔哩_bilibili 资源包含可视化的农作物杂草检测系统&#xff0c;基于最新的YOLOv8训练的农作物杂草检测模型&#xff0c;和基于PyQt5制作的可视化农作物杂草检测系统&#xff0c;包含登陆页面、注册页面和检测…

免费,Python蓝桥杯等级考试真题--第15级(含答案解析和代码)

Python蓝桥杯等级考试真题–第15级 一、 选择题 答案&#xff1a;B 答案&#xff1a;D 解析&#xff1a;集合的并集运算有两种方式&#xff0c;一种是使用“|”运算符进行操作&#xff0c;另一种是使用union()方法来实现&#xff0c;故答案为D。 答案&#xff1a;A 解析&…

STM32启动过程分析

Keil堆栈设置注意事项 一、启动模式 复位方式&#xff1a;上电复位、硬件复位、软件复位 从地址0x0000 0000处取出堆栈指针MSP的初始值&#xff0c;该值就是栈顶地址。从地址0x0000 0004处取出程序计数器指针PC的初始值&#xff0c;该值指向复位后执行的第一条指令。 说白了就…

2024年【危险化学品经营单位安全管理人员】考试报名及危险化学品经营单位安全管理人员找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 危险化学品经营单位安全管理人员考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新危险化学品经营单位安全管理人员找解析题目及答案&#xff01;多做几遍&#xff0c;其实通过危险化学品经营单位安全管…

ArcGIS基本操作-常用的空间分析工具梳理

ArcGIS空间分析工具使用 如果我们在进行科研时需要将研究区地形地貌作为一项指标的话&#xff0c;将可能遇到坡度、坡向、地形起伏度、地表切割深度等因子计算&#xff0c;下面我向大家介绍如何利于ArcGIS软件的空间分析工具&#xff0c;基于高程数据&#xff0c;分析重庆市的…

聊聊如何感知项目引入哪些功能特性

前言 使用过springcloud全家桶朋友&#xff0c;应该知道springcloud涉及的组件很多&#xff0c;为了让开发者快速了解项目引入了springcloud哪些组件&#xff0c;springcloud引入了HasFeatures&#xff0c;配合Actuator&#xff0c;可以让开发者感知到项目引入的组件功能类型、…