Protocol Buffer-nanopb介绍

文章目录

  • 一、需求
  • 二、环境
  • 三、相关概念
    • 3.1 protocol buffer介绍
    • 3.2 nanopb(支持C语言)
    • 3.3 proto文件
  • 四、proto基本语法
    • 4.1 proto文件的定义
    • 4.2 字段规则
    • 4.3 字段类型
    • 4.4 字段编号
    • 4.5 proto语法
    • 4.6 进阶语法
      • 4.6.1 message嵌套
      • 4.6.2 enum关键字
      • 4.6.3 oneof关键字
  • 五、nanopb分析
    • 5.1 nanopb版本下载
    • 5.2 nanopb相关Api
      • 5.2.1 编码相关API
      • 5.2.2 解码相关API
    • 5.3 总体结构
      • 5.3.1 结构图
      • 5.3.2 相关文件
  • 六、nanopb应用
    • 6.1 基于Android平台nanopb应用
      • 6.1.2 定义.proto文件
      • 6.1.3 生成动态库
      • 6.1.4 临时文件
      • 6.1.5 测试用例
    • 6.2 基于Windows平台的nanopb应用
      • 6.2.1 环境配置
      • 6.2.2 临时文件生成
      • 6.2.3 测试用例
  • 七、遗留问题
  • 八、代码仓库
  • 九、参考资料

一、需求

  1. 了解.proto文件的配置语法规则
  2. 目前平台上关于protocol buffer的使用例子较少

二、环境

  1. 版本:Android 12
  2. 平台:展锐 SPRD8541E

三、相关概念

3.1 protocol buffer介绍

        protocol buffer是一种google开发的,语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于通信协议、数据存储等。google在2008年7月7号将其作为开源项目对外公布。值得注意的是,proto buffer是以二进制来存储数据的。相对于JSON和XML具有以下优点:

  1. 简洁
  2. 体积小:消息大小只需要XML的1/10 ~ 1/3;
  3. 速度快:解析速度比XML快20 ~ 100倍;
  4. json\xml都是基于文本格式,protobuf是二进制格式;

        protobuf是PB协议使用较广的一个框架,支持C++,JAVA,Python,Ruby,Go,PHP等多种语言。

3.2 nanopb(支持C语言)

        protobuf支持多种语言,但是却不支持纯C语言,而且protobuf的使用笨重,在一些内存紧张的嵌入式设备上不能使用,nanopb是谷歌协议缓冲数据格式的一个纯C实现。它的目标是32位微控制器,但也适用于其他嵌入式系统的严格(< 10kB ROM,< 1kB RAM)内存限制。

3.3 proto文件

        .proto文件是Google Protocol Buffers的核心组成部分,定义了数据的结构和格式。它支持多种基本数据类型和自定义数据类型的定义,可以嵌套定义。每个字段有类型、名称和字段序号三个特性,字段规则定义了字段是单值、重复值还是可选值。在.proto文件定义完成后,需要使用protobuf编译器将其编译成对应语言的代码,然后在代码中使用这些生成的代码文件定义数据类型、序列化和反序列化数据。

四、proto基本语法

4.1 proto文件的定义

如下为一个*.proto文件的基本定义:

4.2 字段规则

字段介绍
required格式良好的 message 必须包含该字段一次(在proto3中已经为兼容性彻底抛弃 required。)
optional格式良好的 message 可以包含该字段零次或一次(不超过一次)。
repeated该字段可以在格式良好的消息中重复任意多次(包括零)。其中重复值的顺序会被保留。

4.3 字段类型

proto类型介绍
double64位浮点数
float32位浮点数
int32使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint32。
int64使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint64。
uint32使用可变长度编码。
uint64使用可变长度编码。
sint32使用可变长度编码。符号整型值。这些比常规int32s编码负数更高效。
sint64使用可变长度编码。符号整型值。这些比常规int64s编码负数更高效。
fixed32总是四字节。如果值通常大于228,则比uint 32更高效
fixed64总是八字节。如果值通常大于256,则比uint64更高效
sfixed32总是四字节。
sfixed64总是八字节。
bool布尔类型
string字符串必须始终包含UTF - 8编码或7位ASCII文本
bytes可以包含任意字节序列

4.4 字段编号

        message 定义中的每个字段都有唯一编号。这些数字以message二进制格式标识你的字段,并且一旦你的message被使用,这些编号就无法再更改。请注意,1到15范围内的字段编号需要一个字节进行编码,编码结果将同时包含编号和类型。16到2047范围内的字段编号占用两个字节。因此,你应该为非常频繁出现的message元素保留字段编号1到15。

4.5 proto语法

        目前proto语法,可以分为proto2版本和proto3版本,proto3在proto2的基础上做了升级与改动,其区别如下:
        https://blog.csdn.net/ymzhu385/article/details/122307593
        Android12上发现采用proto2语法场景较多,本文的话我也将继续沿用proto2语法进行分析。

4.6 进阶语法

4.6.1 message嵌套

        messsage除了能放简单数据类型外,还能存放另外的message类型:

message CarMessage {
    required string name = 1;
    required int32 price = 2;
}

message UserMessage {
    enum Sex {
        WOMAN = 0;
        MAN = 1;
    }
    required string username = 1;
    optional int32 age = 2;
    required Sex sex = 3;
    repeated CarMessage cars = 4;
}

4.6.2 enum关键字

        在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表,我们可以通过enum在消息定义中添加每个可能值的常量来非常简单的执行此操作:

message UserMessage {
    enum Sex {
        WOMAN = 0;
        MAN = 1;
    }
    required string username = 1;
    optional int32 age = 2;
    required Sex sex = 3;
}

4.6.3 oneof关键字

        如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用oneof功能,示例如下:

message OneOfMessage {
    oneof IdData {
         int32 id = 1;
         int32 passport = 2;
    };
}

五、nanopb分析

5.1 nanopb版本下载

nanopb各个版本: https://jpa.kapsi.fi/nanopb/download/

5.2 nanopb相关Api

Protocol指导文档: https://jpa.kapsi.fi/nanopb/docs/index.html

5.2.1 编码相关API

API说明
pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize);构造用于写入内存缓冲区的输出流。
bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);将结构的内容编码为协议缓冲区消息,并将其写入输出流
bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags);使用由标志设置的扩展行为对消息进行编码:
bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct);计算已编码消息的长度。
bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number);以Protocol Buffers二进制格式开始一个字段:编码字段号和数据的类型。
bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field);与pb_encode_tag相同,只是从pb_field_iter_t结构体获取参数。
bool pb_encode_varint(pb_ostream_t *stream, uint64_t value);以可变格式编码有符号或无符号整数。适用于bool、enum、int32、int64、uint32和uint64类型的字段:
bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size);将字符串的长度写入变量,然后写入字符串的内容。适用于bytes和string类型的字段:
bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);对子消息字段进行编码,包括它的大小报头。适用于任何消息类型的字段。

5.2.2 解码相关API

API说明
pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize);用于创建从内存缓冲区读取数据的输入流的辅助函数。
bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct);读取和解码结构的所有字段。读取输入流直到EOF。
bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags);与pb_decode相同,但允许扩展选项。
bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest);读取和解码一个变量编码的整数。
bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest);类似于pb_decode_varint,不同之处在于它对值执行zigzag解码。这对应于协议缓冲区sint32和sint64数据类型。

5.3 总体结构

5.3.1 结构图

Step 1. 第一阶段: MyMessage.proto文件经过编译,会生成MyMessage.pb.c和MyMessage.pb.h临时文件;
Step 2. 第二阶段: 通过Nanopb提供的相关库文件,以及第一个阶段生成的MyMessage.pb.c和MyMessage.pb.h临时文件,可以编写我们的应用程序User application;
Step 3. 第三阶段: 我们的业务数据Data structures和Protocol Buffers messages的数据,通过Nanopb library提供的编解码方法pb_encode()和pb_decode(), 实现序列化和反序列化的操作。

5.3.2 相关文件

一个标准的nanopb项目,会包含如下文件:

类型文件备注
Nanopb runtime librarypb.h必须有
pb_common.h
pb_common.c
必须有
pb_decode.h
pb_decode.c
编码相关
pb_encode.h
pb_encode.c
解码相关
Protocol descriptionMyMessage.proto必须有
MyMessage.pb.c
MyMessage.pb.h
编译后自动生成

六、nanopb应用

6.1 基于Android平台nanopb应用

        基于Android平台,创建一个c程序,用于测试nanopb的使用规则。文末附上相关demo仓库地址。

6.1.2 定义.proto文件

        定义.proto文件,定义数据结构与格式。

syntax = "proto2";
...
message CarMessage {
    required string name = 1;
    required int32 price = 2;
}
message PetMessage {
    required string name = 1;
}
message UserMessage {
    enum Sex {
        WOMAN = 0;
        MAN = 1;
    }
    required string username = 1;
    optional int32 age = 2;
    required Sex sex = 3;
    repeated CarMessage cars = 4;
    optional PetMessage pets = 5;
}

6.1.3 生成动态库

        将proto相关文件打包成libprototest动态库,以便于需要使用的模块去引用。(之前想将proto直接编译到对应的测试程序,但是export_proto_headers等相关编译标识未找到,导致无法正常编译,故将其先编译成一个动态库)

cc_library {
    name: "libprototest",
    srcs: [
        "proto/simple.proto",
    ],
    ...
    proto: {
        type: "nanopb-c-enable_malloc-32bit",
        export_proto_headers: true,
    },
    vendor: true,
}

6.1.4 临时文件

        libprototest模块编译后,会根据.proto文件的数据结构,生成一个临时文件simple.pb.csimple.pb.h(临时文件路径:out\soong\ .intermediates\vendor\sprd\proprietories-source\rild\protocol\libprototest\android_vendor.31_arm_armv8-a_cortex-a53_static\gen\proto\vendor\sprd\proprietories-source\rild\protocol\proto\),相关文件也有备份到Demo代码仓库。

@simple.pb.h
...
typedef enum _UserMessage_Sex {
    UserMessage_Sex_WOMAN = 0,
    UserMessage_Sex_MAN = 1
} UserMessage_Sex;
#define _UserMessage_Sex_MIN UserMessage_Sex_WOMAN
#define _UserMessage_Sex_MAX UserMessage_Sex_MAN
#define _UserMessage_Sex_ARRAYSIZE ((UserMessage_Sex)(UserMessage_Sex_MAN+1))

/* Struct definitions */
typedef struct _CarMessage {
    char name[100];
    int32_t price;
/* @@protoc_insertion_point(struct:CarMessage) */
} CarMessage;

typedef struct _PetMessage {
    char name[100];
/* @@protoc_insertion_point(struct:PetMessage) */
} PetMessage;
...
typedef struct _UserMessage {
    char username[200];
    bool has_age;
    int32_t age;
    UserMessage_Sex sex;
    pb_callback_t cars;
    bool has_pets;
    PetMessage pets;
/* @@protoc_insertion_point(struct:UserMessage) */
} UserMessage;
...
/* Struct field encoding specification for nanopb */
extern const pb_field_t SimpleMessage_fields[5];
extern const pb_field_t CarMessage_fields[3];
extern const pb_field_t PetMessage_fields[2];
extern const pb_field_t UserMessage_fields[6];
...

6.1.5 测试用例

        message嵌套使用测试,其相关流程如下:

void test_nest(void){
    RLOGD("lzq add for test_nest START >>>>>>>>\n");
    /*************************写入数据***************************/
    //Step 1.创建写入数据
    UserInfo userinfo;
    strcpy(userinfo.username,"linzhiqin");
    userinfo.age = 18;
    userinfo.has_age = true;
    strcpy(userinfo.cars[0].name,"BMW");
    userinfo.cars[0].price = 380000;
    strcpy(userinfo.cars[1].name,"Benz");
    userinfo.cars[1].price = 450000;
    strcpy(userinfo.cars[2].name,"Audi");
    userinfo.cars[2].price = 280000;
    userinfo.car_num = 3;

    //Step 2.写入数据赋值给编码相关对象
    uint8_t encodeBuffer[1024] = {0};
    int encodeBufferLen = 0;
    UserMessage pack_user = UserMessage_init_zero;
    strcpy(pack_user.username,userinfo.username);
    pack_user.age = userinfo.age;
    pack_user.has_age = userinfo.has_age;
    pack_user.sex = UserMessage_Sex_MAN;
    //strcpy(pack_user.pets.name,"Ragdoll");
    pack_user.cars.funcs.encode = carEncode;//编码回调函数
    pack_user.cars.arg = &userinfo;

    //Step 3.数据编码
    pb_ostream_t o_stream = {0};
    o_stream = pb_ostream_from_buffer(encodeBuffer, 1024);
    if(pb_encode(&o_stream, UserMessage_fields, &pack_user) == false){
        printf("encode failed\n");
        return;
    }
    encodeBufferLen = o_stream.bytes_written;

    /**************************读取数据**************************/

    //Step 4.创建解码相关对象
    UserInfo userinfo2;
    memset(&userinfo2,0,sizeof(UserInfo));
    UserMessage unpack_user = UserMessage_init_zero;
    unpack_user.cars.funcs.decode = carDecode;//解码回调
    unpack_user.cars.arg = &userinfo2;

    //Step 5.数据解码&打印
    pb_istream_t i_stream = {0};
    i_stream = pb_istream_from_buffer(encodeBuffer, encodeBufferLen);
    if(pb_decode(&i_stream, UserMessage_fields, &unpack_user) == true){
        strcpy(userinfo2.username,unpack_user.username);
        //strcpy(userinfo2.pets.name,unpack_user.pets.name);
        if(unpack_user.has_age) {
            userinfo2.age = unpack_user.age;
        }

        printf("\n");
        printf("UserInfo.pets.name = %s\n", userinfo2.pets.name);
        printf("UserInfo.username = %s\n", userinfo2.username);
        printf("UserInfo.age = %d\n", userinfo2.age);
        printf("UserInfo.sex = %d\n", unpack_user.sex);
        for(int i=0;i<userinfo2.car_num;i++)
            printf("CarInfo name:%s score:%d\n",userinfo2.cars[i].name,userinfo2.cars[i].price);
    }

    RLOGD("lzq add for test_nest END >>>>>>>>\n");
}

打印结果:

6.2 基于Windows平台的nanopb应用

6.2.1 环境配置

(1)Windows版本: Windows 10 专业版
(2)gcc版本: gcc version 8.1.0(https://sourceforge.net/projects/mingw-w64/)
(3)C程序IDE: January 2024 (version 1.86)(插件: C/C++、Code Runner)
(4)nanopb版本: nanopb-0.4.8-windows-x86.zip(https://jpa.kapsi.fi/nanopb/download/)

6.2.2 临时文件生成

Step 1. 解压nanopb文件夹 解压nanopb-0.4.8-windows-x86.zip文件,其相关内容如下:

Step 2. 新增.proto文件 在nanopb-0.4.8-windows-x86.zip文件文件夹下,新增simple.proto文件,相关内容如下:

syntax = "proto2";

message SimpleMessage {
    enum Sex {
        WOMAN = 0;
        MAN = 1;
    }
    required int32 code = 1;
    optional string msg = 2;
    repeated int32 data = 3;
    required Sex sex = 4;
}

Step 3. 设置系统环境变量 将nanopb-0.4.8-windows-x86\generator-bin设置为系统全局变量,方便引用;

Step 4. 生成临时文件 进入当前文件夹下,通过如下指令生成simple.pb.c和simple.pb.h:

protoc --nanopb_out=. simple.proto

Step 5. nanopb程序关键文件 nanopb 将需要保存的参数写在.proto文件里面,然后生成对应的*.pb.h和*.pb.c 文件。nanopb程序需要将如下几个关键文件拷贝到对应工程:

6.2.3 测试用例

#include <stdio.h>
#include <stdlib.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "simple.pb.h"

/******************** test_simple *************************/
/*
 * 简单测试
 */
void test_simple(){
    SimpleMessage req;
    memset(&req, 0, sizeof(SimpleMessage));
    req.code = 1109;
    req.sex = SimpleMessage_Sex_MAN;
    size_t encodedSize = 0;
    if (!pb_get_encoded_size(&encodedSize, SimpleMessage_fields, &req)) {
        exit(0);
    }
    uint8_t *buffer = (uint8_t *)calloc(1, encodedSize);
    if (buffer == NULL) {
        exit(0);
    }

    pb_ostream_t stream = pb_ostream_from_buffer(buffer, encodedSize);
    if (!pb_encode(&stream, SimpleMessage_fields, &req)) {
        exit(0);
    }
    /**************************读取数据**************************/
    SimpleMessage message = SimpleMessage_init_zero;
    pb_istream_t stream2 = pb_istream_from_buffer(buffer, encodedSize);
    if (!pb_decode(&stream2, SimpleMessage_fields, &message))
    {
        exit(0);
    }
    printf("code = %d | sex = %d \n",message.code,message.sex);
}

int main(int argc, char **argv) {
    //简单测试
    test_simple();
    ...
}

打印结果:

七、遗留问题

  1. const pb_field_t UserMessage_fields[6] 数组的数据打印异常(nanopb例子using_union_messages)
  2. message多级嵌套除了使用repeated关键字,采用回调函数处理外,不知道是否有其他处理方式?

八、代码仓库

Demo地址: https://gitee.com/linzhiqin/protocol

九、参考资料

https://zhuanlan.zhihu.com/p/494788890#nanopb%E7%9A%84%E4%BD%BF%E7%94%A8

https://www.jianshu.com/p/6f68fb2c7d19

https://blog.csdn.net/hsy12342611/article/details/129517588

https://www.jianshu.com/p/bdd94a32fbd1

https://blog.csdn.net/Gefangenes/article/details/131319610

参考例子:
https://blog.csdn.net/du2005023029/article/details/130861308

VsCode调试C程序
https://blog.csdn.net/ABYSS_CL/article/details/119961975

nanopb在window平台的使用
https://www.cnblogs.com/ymchen/p/16861605.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Flink精讲】Flink状态及Checkpoint调优

RocksDB大状态调优 RocksDB 是基于 LSM Tree 实现的&#xff08;类似 HBase&#xff09; &#xff0c;写数据都是先缓存到内存中&#xff0c; 所以 RocksDB 的写请求效率比较高。 RocksDB 使用内存结合磁盘的方式来存储数据&#xff0c;每 次获取数据时&#xff0c;先从内存中 …

什么是高可用架构

一、什么是高可用 在运维中&#xff0c;经常听到高可用&#xff0c;那么什么是高可用架构呢&#xff1f;通俗点讲&#xff0c;高可用就是在服务故障&#xff0c;节点宕机的情况下&#xff0c;业务能够保证不中断&#xff0c;服务正常运行。 举个例子&#xff0c;支付宝&#…

GS069——直流有刷电机调速电路 通过外接电阻网络,改变与之相接的 VMOS 管的输出,达到控制电动工具 转速的作用。 功耗小,电源电压范围宽。

GS069电动工具直流调速电路是CMOS专用集成电路&#xff0c;具有电源电压范 围宽、功耗小、抗干扰能力强等特点。通过外接电阻网络&#xff0c;改变与之相接 的VMOS 管的输出&#xff0c;达到控制电动工具转速的作用。该电路输出幅值宽&#xff0c; 频率变化小&#xff0c;占空比…

vue ts html 中如何遍历 Enum 类型构建页面结构

vue ts html 中如何遍历 Enum 类型构建页面结构 一、需求 定义了一个 Enum 用来标记菜单类型&#xff1a; enum EnumMenuType {目录 1,菜单,按钮,外链 }你得 Enum 知道它的序号是随第一个定义的值自动增长的 现在想在 ElementUI 界面的 radio-group 中遍历它&#xff0c;…

聚集高速托盘类四向穿梭车ASRV|一车跑全仓可获得10000个货位的HEGERLS智能搬运机器人

随着国内外制造业加速转型升级&#xff0c;越来越多的企业需要进行物流智能化升级&#xff0c;但是往往受到仓库面积、高度、形状等现实条件的限制&#xff0c;以及市场不确定性因素的影响。因此&#xff0c;相对于投资传统的自动化立体库&#xff0c;企业更倾向于选择智能化、…

HarmonyOS—低代码开发Demo示例

接下来为大家展示一个低代码开发的JS工程的Demo示例&#xff0c;使用低代码开发如下华为手机介绍列表的HarmonyOS应用/服务示例。 1.删除模板页面中的控件后&#xff0c;选中组件栏中的List组件&#xff0c;将其拖至中央画布区域&#xff0c;松开鼠标&#xff0c;实现一个List组…

[设计模式Java实现附plantuml源码~行为型] 撤销功能的实现——备忘录模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

formality:set_constant应用

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 往期文章链接: formality:形式验证流程 scan mode func的功能检查需要把scan mode设置成0。

python Matplotlib Tkinter-->导出pdf报表

环境 python:python-3.12.0-amd64 包: matplotlib 3.8.2 reportlab 4.0.9 import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk import tkinter as tk import tkinter.messagebox as messagebox impor…

逆变器专题(9)-光伏并网逆变器PQ控制

相应仿真原件请移步资源下载 原理 跟网型并网逆变器通常采用PQ功率环并网&#xff0c;采用功率环并网通常可以进行设计其功率因数 如图所示&#xff0c;通过控制有功功率、无功功率的值&#xff0c;即可实现对功率因数的控制&#xff0c;通常情况下在并网时可将无功功率控为0&…

虚拟机JVM

虚拟机 1、定义jvm 假想计算机 运行在操作系统之上 和硬件之间没有直接交互 包括 一套字节码指令、寄存器、栈、垃圾回收、堆 一个存储方法域 jvm:承担一个翻译工作&#xff0c;动态的将java代码编译成操作系统可以识别的机器码。 从软件层面屏蔽了不同操作系统在底层硬件与指…

js实现鼠标拖拽改变div大小的同时另一个div宽度也变化

实现效果如下图所示 源码如下 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>.box {width: 100%;height: 300px; display: flex;}/*左侧div样式*/.left {width: calc(30% - 5px); /*左侧初始…

naive-ui-admin BasicTable 列表操作栏显示图标icon

效果图 在使用BasicTable的页面添加引用&#xff0c;这里随便弄了个icon import { GameController } from "vicons/ionicons5" 自定义列 const actionColumn reactive({width: 180,title: "操作",key: "action",fixed: "right",ren…

【高德地图】Android高德地图控件交互详细介绍

&#x1f4d6;第5章 与地图控件交互 ✅控件交互&#x1f9ca;缩放按钮&#x1f9ca;指南针&#x1f9ca;定位按钮&#x1f9ca;地图Logo ✅手势交互&#x1f9ca;缩放手势&#x1f9ca;滑动手势&#x1f9ca;旋转手势&#x1f9ca;倾斜手势&#x1f9ca;指定屏幕中心点的手势操…

Facebook的虚拟社交愿景:元宇宙时代的新起点

在当今数字化时代&#xff0c;社交媒体已经成为人们生活中不可或缺的一部分。而随着科技的不断进步和社会的发展&#xff0c;元宇宙已经成为了人们关注的热点话题之一。作为社交媒体的领军企业之一&#xff0c;Facebook也在积极探索虚拟社交的未来&#xff0c;将其视为元宇宙时…

社交媒体变革者:剖析Facebook对在线互动的贡献

随着数字化时代的蓬勃发展&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分。在这个领域的发展中&#xff0c;Facebook作为先行者和领导者&#xff0c;对在线互动的演变和发展产生了深远的影响。本文将深入剖析Facebook在社交媒体领域的贡献&#xff0c;以及它对在…

贪心算法(算法竞赛、蓝桥杯)--排队接水问题

1、B站视频链接&#xff1a;A25 贪心算法 P1223 排队接水_哔哩哔哩_bilibili 题目链接&#xff1a;排队接水 - 洛谷 #include <bits/stdc.h> using namespace std; struct node{int t,id;//接水时间&#xff0c;编号bool operator<(node &b){return t<b.t;} …

2024-02-23(Spark)

1.RDD的数据是过程数据 RDD之间进行相互迭代计算&#xff08;Transaction的转换&#xff09;&#xff0c;当执行开启后&#xff0c;代表老RDD的消失 RDD的数据是过程数据&#xff0c;只在处理的过程中存在&#xff0c;一旦处理完成&#xff0c;就不见了。 这个特性可以最大化…

Lua速成(2)

一、流程控制 Lua 编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码&#xff0c;在条件为 false 时执行其他指定代码。 控制结构的条件表达式结果可以是任何值&#xff0c;Lua认为false和nil为假&#xff0c;true和非nil为真。 …