Protobuf:复杂类型接口
Protobuf:复杂类型接口
- package
- 字段规则
- 复杂类型
- enum
- Any
- oneof
- map
本博客基于proto3
语法,讲解protobuf
中的复杂类型。
package
在.proto
文件中,支持导入其它.proto
文件的内容,例如:
test.proto
:
syntax = "proto3";
package test_pkg;import "other.proto";message Person {int32 id =1;string name = 2;other_pkg.Address addr = 3;
}
other.proto
:
syntax="proto3";
package other_pkg;message Address {string country = 1;string city = 2;
}
在test.proto
文件中,使用了other.proto
的内容。想要实现多个文件的效果,需要通过improt
导入文件:
import 文件名.proto
随后在导入的文件中,使用包名.变量名
的形式使用变量,比如other_pkg.Address
。
字段规则
如果想在protobuf
中实现一个数组,它使用了一种字段规则的修饰方式来实现数组。
消息字段类型可以用以下规则修饰:
singular
:消息中该字段可以出现0
次或1
次,字段默认使用该规则repeated
:消息中该字段可以出现任意次数,包括0
次
简单来说,对于singular
变量只能存储一个值,如果输入新的值,后来的值会把原先的值覆盖。而对于repeated
变量,其可以存储多个值,所以值都会被保留,其实也就是数组。
示例:
syntax = "proto3";
package test_pkg;message Person {int32 id = 1;string name = 2;repeated string phone = 3;
}
此处的phone
字段被设置为了repeated
,可以理解为一个string
的数组,在pb.h
文件中,该字段包含以下接口:
// repeated string phone = 3;
int phone_size() const;
private:
int _internal_phone_size() const;
public:
void clear_phone();
const std::string& phone(int index) const;
std::string* mutable_phone(int index);
void set_phone(int index, const std::string& value);
void set_phone(int index, std::string&& value);
void set_phone(int index, const char* value);
void set_phone(int index, const char* value, size_t size);
std::string* add_phone();
void add_phone(const std::string& value);
void add_phone(std::string&& value);
void add_phone(const char* value);
void add_phone(const char* value, size_t size);
可以看到,相比于一般的string
类型,被repeated
修饰后,多出了很多下标操作,这里挑几个接口出来讲解:
int phone_size() const
:获取当前数组的长度。const std::string& phone(int index) const
:通过下标获取值的操作,获取该变量的const
引用。std::string* mutable_phone(int index)
:获取指定下标元素的指针,可以通过指针修改该元素。
void set_phone(int index, const std::string& value);
void set_phone(int index, std::string&& value);
void set_phone(int index, const char* value);
void set_phone(int index, const char* value, size_t size);
这四个接口用于设置指定下标的值,四种函数重载效果是一样的,只是传入字符串的形式不同。
std::string* add_phone()
:数组末尾增加一个空元素,并返回指向该元素的指针,可以通过指针修改该元素。
void add_phone(const std::string& value);
void add_phone(std::string&& value);
void add_phone(const char* value);
void add_phone(const char* value, size_t size);
这四个接口直接插入一个指定元素到数组末尾,重载是为了兼容不同形式的字符串。
被repeat
修饰的变量接口总结:
接口 | 功能 |
---|---|
xxx_size() | 获取数组的长度 |
xxx(index) | 获取指定下标的元素,不可修改 |
muteble_xxx(index) | 获取指定下标元素的指针,可修改 |
set_xxx(index, value) | 设置指定下标的元素为value |
add_xxx() | 尾插一个元素到数组,返回指向该元素的指针,可修改 |
add_xxx(value) | 尾插value 到数组 |
复杂类型
enum
enum
是枚举类型,其列举多个常量,方便后续使用。
定义一个枚举,表示有哪些人物种类:
enum Type{STU = 0;TEACHER = 1;OTHER = 2;
}
此处枚举了三个常量,分别表示学生,老师,其它。
枚举类型有以下要求:
0
值必须存在,且要作为第一个元素- 在语言中,定义枚举变量后,如果指定变量值,第一个元素
0
是默认值 - 枚举的常量值在
32
位整数内,但是负数无效
示例:
syntax = "proto3";
package test_pkg;enum Type{STU = 0;TEACHER = 1;OTHER = 2;
}message Person {int32 id = 1;string name = 2;repeated string phone = 3;Type type = 4;
}
编译后,在.pb.h
文件可以找到以下枚举类型:
enum Type : int {STU = 0,TEACHER = 1,OTHER = 2,Type_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::min(),Type_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<int32_t>::max()
};
此处使用了C++11
的强枚举类型语法,enum Type : int
指定底层类型为int
。除去在.proto
文件中指定的三个成员,还增加了两个其它成员,最后两个成员用于做边界值判断,限制成员的值只在int32
范围内。
紧接着的是部分枚举类型的专有接口:
bool Type_IsValid(int value);
constexpr Type Type_MIN = STU;
constexpr Type Type_MAX = OTHER;
constexpr int Type_ARRAYSIZE = Type_MAX + 1;const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* Type_descriptor();
template<typename T>
inline const std::string& Type_Name(T enum_t_value)
inline bool Type_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name, Type* value);
Type_IsValid
:判断整数是否是enum
内部的成员之一Type_Name
:输入枚举值,返回枚举值对应的字符串名称Type_parse
:输入字符串和枚举值,判断两者是否匹配
再往后,在Person
类中,定义了枚举的通用接口:
// .test_pkg.Type type = 4;
void clear_type();
::test_pkg::Type type() const;
void set_type(::test_pkg::Type value);
可以看到,枚举类型的get
和set
接口还是比较简单的。
enum
接口总结:
接口 | 功能 |
---|---|
xxx_IsValid() | 判断整数是否是enum 内部的成员之一 |
xxx_Name(value) | 输入枚举值,返回枚举值对应的字符串名称 |
xxx_Parse(string, value) | 输入字符串和枚举值,判断两者是否匹配 |
xxx() | 获取当前枚举变量的值 |
set_xxx(value) | 设置枚举的值 |
Any
假设现在要给Person
类定义一个info
字段,用于存储该人的具体信息。但是由于Person
可以是学生,可以是老师,这两种人存储的info
是不同的,如何让一个字段存储不同的值,成为一个泛型?
此处Any
类型就排上用场了,Any
可以存储任何其它类型的message
,相当于一个泛型。
syntax = "proto3";
package test_pkg;import "google/protobuf/any.proto";message test{google.protobuf.Any info = 1;
}
Any
的本质是一个message
,写在google/protobuf/any.proto
中,使用Any
类型需要import
导包。在类内使用类型时,也需要指定包名google.protobuf.Any
。
引入泛型后,当前Person
类如下:
syntax = "proto3";
package test_pkg;import "google/protobuf/any.proto";enum Type{STU = 0;TEACHER = 1;OTHER = 2;
}message stuInfo {string className = 1; // 班级int32 score = 2; // 成绩
}message teacherInfo {string subject = 1; // 科目int32 jobAge = 2; // 工龄
}message Person {int32 id = 1;string name = 2;repeated string phone = 3;Type type = 4;google.protobuf.Any info = 5;
}
Person
的info
字段用于存储具体信息,对于学生来说,要存储班级和成绩。而对于老师来说,要存储所教的科目和工龄。此时将这些信息分别定义在stuInfo
和teacherInfo
中,在Person
内引入一个Any
字段info
,这个info
就可以存储任意其它的message
,自然也包括stuInfo
和teacherInfo
。
编译后,在.pb.h
中Any
的接口如下:
// .google.protobuf.Any info = 5;
bool has_info() const;
private:
bool _internal_has_info() const;
public:
void clear_info();
const ::PROTOBUF_NAMESPACE_ID::Any& info() const;
PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_info();
::PROTOBUF_NAMESPACE_ID::Any* mutable_info();
void set_allocated_info(::PROTOBUF_NAMESPACE_ID::Any* info);
has_info()
:检查该字段是否为空clear_info()
:清空该字段的值info()
:获取该字段的值release_info()
:获取字段内的值后,清空字段内的值mutable_info()
:返回指向元素的指针,可通过指针修改元素
另外的,还有几个重要的接口,在google/protobuf/any.pb.h
中:
bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message);bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message);template<typename T>
bool Is();
这三个接口是Any
最核心的接口:
PackFrom
:将一个message
类型转化为Any
类型UnpackTo
:将一个Any
类型转化回message
Is
:传入模板参数,判断当前Any
存储的类型是否和模板参数一样
这个用法有点复杂,示例:
#include <iostream>
#include "test.pb.h"using namespace std;
using namespace test_pkg;int main()
{test_pkg::Person ps;ps.set_id(123456);ps.set_name("张三");// repeat字段,可以设置多个值ps.add_phone("123456");ps.add_phone("654321");ps.set_type(Type::STU); // 学生类型stuInfo stuinfo;stuinfo.set_classname("六年级一班");stuinfo.set_score(95);google::protobuf::Any any_message;if (any_message.PackFrom(stuinfo)) *ps.mutable_info() = any_message;stuInfo getStuinfo;if (ps.info().Is<stuInfo>())ps.info().UnpackTo(&getStuinfo);return 0;
}
首先初始化了一个Person
,随后初始化了该对象的初始信息,确定该对象是一个学生。所以他的info
要存储一个stuInfo
类型,用于表示详细信息。
stuInfo stuinfo;
stuinfo.set_classname("六年级一班");
stuinfo.set_score(95);
这段代码初始化了一个stuinfo
对象。
随后将这个stuInfo
对象设置到ps.info
成员中:
google::protobuf::Any any_message;
if (any_message.PackFrom(stuinfo)) *ps.mutable_info() = any_message;
设置Any
成员时,首先要定义一个Any
对象,然后通过PackFrom
函数,把stuInfo
对象转化为Any
对象。如果PackFrom
返回true
,说明转化成功,此时any_message
内部就已经是stuInfo
的内容了。最后通过*ps.mutable_info() = any_message
设置info
字段为any_message
对象。
如果后续想要把Any
对象变回stuInfo
对象:
stuInfo getStuinfo;
if (ps.info().Is<stuInfo>())ps.info().UnpackTo(&getStuinfo);
定义一个getStuinfo
接收返回值。随后执行ps.info().Is<stuInfo>()
,判断当前的info
内存储的变量类型是不是stuInfo
,如果是,那么执行ps.info().UnpackTo(&getStuinfo)
,将info
的内容解析到getStuinfo
中。
不论用Any
存储或提取任何一个message
类型,都是这一套逻辑。
Any
接口总结:
接口 | 功能 |
---|---|
has_xxx() | 检查该字段是否为空 |
clear_xxx() | 清空该字段的值 |
xxx() | 获取该字段的值 |
release_xxx() | 获取字段内的值后,清空字段内的值 |
mutable_xxx() | 返回指向元素的指针,可通过指针修改元素 |
PackFrom(message) | 将一个message 类型转化为Any 类型 |
UnpackTo(&message) | 将一个Any 类型转化回message |
Is<messageType>() | 传入模板参数,判断当前Any 存储的类型是否和模板参数一样 |
oneof
有时候,message
中的字段在多个中选择一个,就可以使用oneof
类型。
示例:
message Person {int32 id = 1;string name = 2;oneof contact {int32 qq = 3;int32 tel = 4;}
}
此处的Person
,增加了一个联系方式contact
,用户可以在qq
和tel
中二选一。
在oneof
内部的字段编号,不能与外部的message
冲突,比如qq
和tel
的字段编号就不可以是1
和2
。
另外的,在oneof
内部,不允许使用repeated
修饰字段,比如这样:
message Person {int32 id = 1;string name = 2;oneof contact {repeated int32 qq = 3; // errrepeated int32 tel = 4; // err}
}
如果设置了repeated
,该修饰会失效,变量依然只能保存一个值。
对以下protoc
代码编译:
message Person {int32 id = 1;string name = 2;oneof contact {int32 qq = 3;int32 tel = 4;}
}
结果:
// int32 qq = 3;
bool has_qq() const;
private:
bool _internal_has_qq() const;
public:
void clear_qq();
int32_t qq() const;
void set_qq(int32_t value);public:
// int32 tel = 4;
bool has_tel() const;
private:
bool _internal_has_tel() const;
public:
void clear_tel();
int32_t tel() const;
void set_tel(int32_t value);void clear_contact();
看起来这个oneof
好像没有生效?此处就好像是单独定义了qq
和tel
两个字段,也没有什么oneof
的相关方法。
其实这个地方,qq
和tel
确实是直接当作两个字段处理的,但是两个字段有点像C语言
中的联合体,最后一次设置的是那个变量,那么存储的就是哪一个类型。
示例:
test_pkg::Person ps;
ps.set_id(123456);
ps.set_name("张三");
ps.set_qq(111111);
ps.set_tel(222222);if (ps.has_qq())cout << "qq: " << ps.qq() << endl;if (ps.has_tel())cout << "tel: " << ps.tel() << endl;
定义了一个Person
变量后,先后设置了qq(111111)
和tel(222222)
,随后通过has_xxx
方法分别检测两个值,随后输出。
输出结果:
tel: 222222
最后只输出了tel
,因为后来的tel
覆盖了qq
,两者是二选一的关系。
当然,oneof
类型也不是完全没有接口,比如这个接口:
void clear_contact();
在需要清除oneof
内部的值的时候,如果不确定具体是哪一个类型,就无法确定是caler_qq
还是clear_tel
,如果一个个通过has
判断还是有点麻烦的,就可以通过clear_contact
一键清除。
所有的oneof
的字段都被保存在一个枚举中:
enum ContactCase {kQq = 3,kTel = 4,CONTACT_NOT_SET = 0,
};ContactCase contact_case() const;
此处的kQq
和kTel
分别就是之前设置的qq
和tel
,而CONTACT_NOT_SET
表示未设置参数。
其实在判断当前oneof
存储的是哪一个字段时,不用一个个进行has_xxx
的判断。通过contact_case
函数,会返回一个枚举类型,只需要判断枚举类型是哪一个变量即可:
switch (ps.contact_case())
{case Person::ContactCase::kQq:// 处理qq字段break;case Person::ContactCase::kTel:// 处理tel字段break;case Person::ContactCase::CONTACT_NOT_SET:// 没有字段被设置break;
}
oneof
接口总结:
接口 | 功能 |
---|---|
clear_xxx() | 不论字段存储的是什么,清空该字段的值 |
xxx_case() | 返回当前存储的类型的枚举值 |
map
map
用于创建一个键值对的映射关系,语法:
map<key_tyep, value_type> map_name = N;
其实语法和C++
的std::map
是一样的。
map
有以下注意点:
key_type
:可以是处理float
和bytes
之外的任意标量类型value_type
:可以是任意类型map
中的元素是无序的
示例:
message score {map<string, int32> chinese = 1;map<string, int32> english = 2;map<string, int32> math = 3;
}
这是一个记录学生成绩的表格,三个成员分别代表不同科目的成绩。
编译结果以chinese
为例:
// map<string, int32> chinese = 1;
int chinese_size() const;
private:
int _internal_chinese_size() const;
public:
void clear_chinese();
private:
const ::PROTOBUF_NAMESPACE_ID::Map< std::string, int32_t >&_internal_chinese() const;
::PROTOBUF_NAMESPACE_ID::Map< std::string, int32_t >*_internal_mutable_chinese();
public:
const ::PROTOBUF_NAMESPACE_ID::Map< std::string, int32_t >&chinese() const;
::PROTOBUF_NAMESPACE_ID::Map< std::string, int32_t >*mutable_chinese();
chinese_size()
:获取map
内部元素的个数clear_chinese()
:清空map
的元素chinese()
:获取map
的const
引用mutable_chinese()
:获取map
的指针,可通过指针修改map
此处map
的类型不是std::map
,而是protobuf
自己封装的::PROTOBUF_NAMESPACE_ID::Map
,但是其用法和std::map
没有很大区别。
::PROTOBUF_NAMESPACE_ID::Map
也重载了operator[]
,拿到变量后,可以直接通过[]
进行访问。
示例:
test_pkg::Score sc;auto chinese_mp = *sc.mutable_chinese();
auto english_mp = *sc.mutable_english();
auto math_mp = *sc.mutable_math();chinese_mp["张三"] = 66;
chinese_mp["李四"] = 88;
chinese_mp["王五"] = 98;english_mp["张三"] = 97;
english_mp["李四"] = 77;
english_mp["王五"] = 82;math_mp["张三"] = 46;
math_mp["李四"] = 25;
math_mp["王五"] = 79;cout << "张三-chinese: " << chinese_mp["张三"] << endl;
cout << "张三-english: " << english_mp["张三"] << endl;
cout << "张三-math: " << math_mp["张三"] << endl;
通过auto chinese_mp = *sc.mutable_chinese()
,获取到map
,由于mutable_chinese
返回的是一个指针,所以还要进行解引用。
随后通过chinese_mp[]
设置与获取元素了。
输出结果:
张三-chinese: 66
张三-english: 97
张三-math: 46
map
接口总结:
接口 | 功能 |
---|---|
xxx_size() | 获取map 内部元素的个数 |
clear_xxx() | 清空map 的元素 |
xxx() | 获取map 的const 引用 |
mutable_xxx() | 获取map 的指针,可通过指针修改map |
map.operator[] | 直接通过[] 获取与设置元素 |
相关文章:
Protobuf:复杂类型接口
Protobuf:复杂类型接口 package字段规则复杂类型enumAnyoneofmap 本博客基于proto3语法,讲解protobuf中的复杂类型。 package 在.proto文件中,支持导入其它.proto文件的内容,例如: test.proto: syntax …...
Git Push 深度解析:命令的区别与实践
目录 命令一:git push origin <branch-name>命令二:git push Factory_sound_detection_tool test工作流程:两者的主要区别实践中的应用总结 Git 是一种分布式版本控制系统,它允许用户对代码进行版本管理。在 Git 中…...

大数据开发基础实训室设备
大数据实验实训一体机 大数据实验教学一体机是一种专为大数据教育设计的软硬件融合产品,其基于华为机架服务器进行了调优设计,从而提供了卓越的性能和稳定性。这一产品将企业级虚拟化管理系统与实验实训教学信息化平台内置于一体,通过软硬件…...
【数据结构】string(C++模拟实现)
string构造 string::string(const char* str):_size(strlen(str)) {_str new char[_size 1];_capacity _size;strcpy(_str, str); }// s2(s1) string::string(const string& s) {_str new char[s._capacity 1];strcpy(_str, s._str);_size s._size;_capacity s._cap…...

【笔记】I/O总结王道强化视频笔记
文章目录 从中断控制器的角度来理解整个中断处理的过程复习 处理器的中断处理机制**中断驱动I/O方式** printf——从系统调用到I/O控制方式的具体实现1轮询方式下输出一个字符串(程序查询)中断驱动方式下输出一个字符串中断服务程序中断服务程序与设备驱动程序之间的关系 DMA方…...
XML XSLT:转换与呈现数据的力量
XML XSLT:转换与呈现数据的力量 XML(可扩展标记语言)和XSLT(XML样式表转换语言)是现代信息技术中不可或缺的工具,它们在数据交换、存储和呈现方面发挥着重要作用。本文将深入探讨XML和XSLT的概念、应用及其在信息技术领域的重要性。 XML:数据交换的标准 XML是一种用于…...

ES6总结
1.let和const以及与var区别 1.1 作用域 var: 变量提升(Hoisting):var 声明的变量会被提升到其作用域的顶部,但赋值不会提升。这意味着你可以在声明之前引用该变量(但会得到 undefined)。 con…...

晶体匹配测试介绍
一、晶体参数介绍 晶体的电气规格相对比较简单,如下: 我们逐一看看每个参数, FL就是晶体的振动频率,这个晶体是24.576MHz的。 CL就是负载电容,决定了晶体频率是否准确,包括外接的实际电容、芯片的等效电容以及PCB走线的寄生电容等,核心参数。 Frequency Tolerance是…...

超声波清洗机靠谱吗?适合学生党入手的四款眼镜清洗机品牌推荐!
有没有学生党还不知道双十一买什么?其实可以去看看超声波清洗机,说实话它的实用性真的很高,对于日常用于清洗眼镜真的非常合适,不仅可以帮助大家节约时间而且还能把眼镜清洗的干净透亮,接下来我就来为大家带来四款好用…...

Java生成图片_基于Spring AI
Spring AI 优势 过去,使用Java编写AI应用时面临的主要困境是没有统一且标准的封装库,开发者需自行对接各个AI服务提供商的接口,导致代码复杂度高、迁移成本大。如今,Spring AI Alibaba的出现极大地缓解了这一问题,它提…...

程序传入单片机的过程,以Avrdude为例分析
在市场上有各式各样的单片机,例如Arduino,51单片机,STM等。通常,我们都用其对应的IDE软件进行单片机的编程。这些软件既负责将程序代码转写成二进制代码,即机器语言,也负责将该二进制代码导入单片机。与此同…...

用YOLO和LLM增强的OCR
虽然最近我花了很多时间在大型语言模型 (LLM) 上进行实验,但我对计算机视觉的热情始终未减。因此,当我有机会将两者融合在一起时,我迫不及待地想要立即开始。在 Goodreads 上扫描书籍封面并将其标记为已读一直感觉有点神奇,我很兴…...

开源的云平台有哪些?
开源云平台为用户提供了构建、管理和运行云基础设施及应用的能力,同时允许社区参与开发和改进。以下是一些知名的开源云平台: 1. OpenStack 简介:OpenStack:一个广泛使用的开源云平台,它由多个组件组成,提…...

Spring Boot学习资源库:微服务架构的加速器
3 系统分析 3.1可行性分析 在进行可行性分析时,我们通常根据软件工程里方法,通过四个方面来进行分析,分别是技术、经济、操作和法律可行性。因此,在基于对目标系统的基本调查和研究后,对提出的基本方案进行可行性分析。…...
Pi4+wfb-ng+8812au
sudo apt update将如下文件拷入树莓派4 linux-6f921e98008589258f97243fb6658d09750f0a2f.tar.gz libsodium.zip rtl8812au.zip wfb-ng_Pi4_2.zip 安装libsodium unzip libsodium.zip cd libsodium ./configure make && make check sudo make install#安装8812AU驱动 …...

基于单片机的非接触智能测温系统设计
本设计主要由单片机STC8A8K64S4A12、OLED显示屏、非接触式测温模块MLX9061、无线通讯模块ESP8266以及声光报警模块等构成。系统通过非接触式测温模块MLX9061测量当前人员温度,温度通过OLED显示屏进行实时显示,当被测温度高于阈值,声光报警模块…...

第二十三篇:网络拥塞了,TCP/IP如何解决的?
一.显示拥塞通知 当发生网络拥塞时,发送主机应该减少数据包的发送量。作为IP上层协议,TCP虽然也能控制网络拥塞,不过它是通过数据包的实际损坏情况来判断是否发生拥塞。然而这种方法不能在数据包损坏之前减少数据包的发送量。 为了解决这个…...

登录注册静态网页实现(HTML,CSS)
实现效果图 实现效果 使用HTML编写页面结构,CSS美化界面,点击注册,跳转到注册界面,均为静态网页,是课上的一个小作业~ 使用正则表达式对输入进行验证,包括邮箱格式验证,用户名格式验证。 正则…...

基于FPGA的以太网设计(二)
一.以太网硬件架构概述 前文讲述了以太网的一些相关知识,本文将详细讲解以太网的硬件架构 以太网的电路架构一般由MAC、PHY、变压器、RJ45和传输介质组成,示意图如下所示: PHY:Physical Layer,即物理层。物理层定义了…...

OJ在线评测系统 后端微服务架构 注册中心 Nacos入门到启动
注册中心 服务架构中的注册中心是一个关键组件,用于管理和协助微服务之间的通信。注册中心的主要职责是服务的注册和发现,确保各个微服务能够相互找到并进行调用。 主要功能: 服务注册:微服务在启动时,将自身信息&am…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...