首页 > 其他分享 >protobuf学习笔记

protobuf学习笔记

时间:2023-08-08 10:44:38浏览次数:54  
标签:FieldDescriptor const protobuf 笔记 学习 field message Message

1下载protoc编译器

源代码和可执行文件下载:下载地址

可根据不同的系统,下载对应的可执行文件,用于编译.proto文件

示例C++的命令方式为: protoc.exe --cpp_out=./ demo.proto,就可以生成对应的demo.pb.h和demo.ph.cc源代码

安装vcpkg

下载地址

for windows:

> git clone https://github.com/microsoft/vcpkg
> .\vcpkg\bootstrap-vcpkg.bat

通过vcpkg对protobuf源代码进行编译,参数对应的库文件

使用方法:

vcpkg search protobuf // 搜索需要的包
vcpkg install protobuf:x64-windows // 表示下载protobuf,用于x64-windows
vcpkg install protobuf:x64-windows-static // 表示下载protobuf,用于x64-windows下的静态库
vcpkg install protobuf:x64-windows-static-md // 表示下载protobuf,用于x64-windows下的静态库并且为md编译方式

vcpkg是一个特别棒的工具,针对vc开发者而言,只要通过它,就可以获得任何需要的库文件。

使用cmake编译protobuf源代码

详见地址

就是用git下载ProtoBuf源码,再用cmake进行生成工程项目文件,用vs2019运行工程项目文件,产生protobuf相关的debug或release库文件。

Google C++ 标准库的扩充库 Abseil:Abseil 由 Google 的基础 C ++ 和 Python 代码库组成,包括一些正支撑着如 gRPC、Protobuf 和 TensorFlow 等开源项目并一起 “成长” 的库。目前已开源 C++ 部分,Python 部分将在后续开放。

2proto2和proto3的区别

  • 更简洁
  • 更强调约定而弱化语法
  • 移除required,把optional改名为singular
  • 增加语言支持,如Go、Ruby、JavaNano
  • 移除default选项,在proto3中,默认值有系统决定,全部都是约定好的,不再提供默认值,因为本身提供了默认值,还有就是有时软件也会赋值默认值,这个时候就区分不了了;
  • 枚举类型第一个字段必须是0,是一个约定
  • 移除了对分组的支持
  • 旧代码在解析新增字段时,会把不认识的字段丢弃,再序列化后新增字段没了;
  • 移除对扩展的支持,新增Any类型,有点想C++中的void*,好理解,使用起来逻辑清晰
  • 增加了JSON映射特征
  • - `optional`: - `repeated`: - `map`:
  • reserved fields:
    • reserved 2,3,9 to 11
    • reserved "foo", "bar"

proto3新特征

  • Oneof枚举字段
  • Oneof嵌入Message字段
  • Map字段
  • Any
  • Oneof
  • 枚举
  • 服务
  • 接口
  • Stub

3 ProtoBuf反射原理和使用

3.1 概念

所谓反射机制就是能够在运行时知道任意类的所有属性和方法,能够调用任意对象的任意方法和属性。这种动态获取的信息以及动态调用对象方向的功能称为反射机制。

如果用一句话来总结反射实现的关键,可概括为获取系统元信息。

元信息:即系统自描述信息,用于描述系统本身。举例来讲,即系统有哪些类?类中有哪些字段、哪些方法?字段属于什么类型、方法又有怎样的参数和返回值?

3.2 应用场景

Protobuf是一种常见的数据序列化方式,常常用于后台微服务之间传递数据。

  • 在处理ProtoBuf Message数据时,经常需要根据一个输入的字符串,读取Message中对应属性的取值,并修改取值;
  • 或者给定一个ProtoBuf对象,如何自动遍历该对象的所有字段,将其转换为json格式;
  • 或者在Bigtable中根据pb对象的字段自动写填写列名和对应的value。

在写代码时,经常会遇到一些丑陋的、圈复杂度较高、较难维护的关于 PB 的使用代码:

  • 对字段的必填校验硬编码在代码中:如果需要变更校验规则,则需要修改代码
  • 一个字段一个 if 校验,复杂度较高:对传进来的字段每个字段都进行多种规则校验,例如长度,XSS,正则校验等。
  • 想要获取 PB 中所有的非空字段,形成一个 map<string,string>,需要大量的 if 判断和重复代码;
  • 在后台服务间传递数据,由于模块由不同的人开发,导致相同字段的命名不一样,从一个 PB 中挑选一部分内容到另外一个 PB 中,需要大量的 GET 和 SET 代码。

3.3 调用message的属性和方法

  1. 通过 DescriptorPool 的 FindMessageTypeByName 获得了元信息 Descriptor。
  2. 根据 MessageFactory 实例工厂和Descriptor获得了Message的默认实例Message*指针 default instance
  3. 通过new()构造一个可用的消息对象

3.3.1 根据type name反射自动创建实例

通过字符串"person" 创建新的 person message对象。线程安全

 // 先获得类型的Descriptor .
auto descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("Person");
//利用Descriptor拿到类型注册的instance. 这个是不可修改的.
auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
 // 构造一个可用的消息.
auto instance = prototype->New(); //创建新的 person message对象。

3.4 通过实例instance 的反射接口reflection对其pb字段读写

Reflection主要提供了动态读写 message对象字段的接口,对message对象的自动读写主要通过该类完成。

对每种数据类型,Reflection都提供了一个单独的接口(Get、Set)用于读写字段对应的值
例如对 int32、int64的读操作:输入参数为Message和FieldDescriptor*

  virtual int32  GetInt32 (const Message& message,const FieldDescriptor* field) const = 0;
  virtual int64  GetInt64 (const Message& message, const FieldDescriptor* field) const = 0;

特殊的,对于枚举和嵌套的message:

virtual const EnumValueDescriptor* GetEnum( const Message& message, const FieldDescriptor* field) const = 0;
virtual const Message& GetMessage(const Message& message,
                                    const FieldDescriptor* field,
                                    MessageFactory* factory = NULL) const = 0;

对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum等

void SetInt32(Message * message, const FieldDescriptor * field, int32 value) const
void SetString(Message * message, const FieldDescriptor * field, std::string value) const

获取重复字段的函数如下:

int32 GetRepeatedInt32(const Message & message, const FieldDescriptor * field, int index) const
std::string GetRepeatedString(const Message & message, const FieldDescriptor * field, int index) const
const Message & GetRepeatedMessage(const Message & message, const FieldDescriptor * field, int index) const

获取反射Reflection接口,并且值写入的代码如下:线程安全

auto reflecter = instance.GetReflection();//2.1通过字符串"Person"取得的实例instance
auto field = descriptor->FindFieldByName("name"); // Person这个Message 中有name字段
reflecter->SetString(&instance, field, "小明") ; //反射来设置name字段

通过instance可以获得该实例的反射,该反射可以通过SetString来设置,指定实例,指定field的字段内容。而该field由descriptor通过FindFieldByName获得,第三个参数就是字段需要更新的value值。

3.5反射的例子

serialize_message函数遍历提取message中所有字段以及对应的值,序列化到string中。主要思路就是:

Descriptor得到每个字段的描述符FieldDescriptor:字段名、字段的cpp类型。
通过Reflection的GetXXX接口获取对应的value。

  • 类 FieldDescriptor 介绍

类 FieldDescriptor 的作用主要是对 Message 中单个字段进行描述,包括字段名、字段属性、原始的 field 字段等。

其获取自身信息的函数:

const std::string & name() const; // Name of this field within the message.
const std::string & lowercase_name() const; // Same as name() except converted to lower-case.
const std::string & camelcase_name() const; // Same as name() except converted to camel-case.
CppType cpp_type() const; //C++ type of this field.

其中cpp_type()函数是来获取该字段是什么类型的,在 protobuf 中,类型的类目如下:

enum FieldDescriptor::Type {
  TYPE_DOUBLE = = 1,
  TYPE_FLOAT = = 2,
  TYPE_INT64 = = 3,
  TYPE_UINT64 = = 4,
  TYPE_INT32 = = 5,
  TYPE_FIXED64 = = 6,
  TYPE_FIXED32 = = 7,
  TYPE_BOOL = = 8,
  TYPE_STRING = = 9,
  TYPE_GROUP = = 10,
  TYPE_MESSAGE = = 11,
  TYPE_BYTES = = 12,
  TYPE_UINT32 = = 13,
  TYPE_ENUM = = 14,
  TYPE_SFIXED32 = = 15,
  TYPE_SFIXED64 = = 16,
  TYPE_SINT32 = = 17,
  TYPE_SINT64 = = 18,
  MAX_TYPE = = 18
}

serialize_message遍历提取message中****所有字段以及对应的值代码如下:

void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {
    //获取描述Descriptor*和反射Reflection*
    const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
    const google::protobuf::Reflection* reflection = message.GetReflection();
     
    //遍历消息的所有字段  
    for (int i = 0; i < descriptor->field_count(); ++i) {
        // 获得单个字段的描述FieldDescriptor*
        const google::protobuf::FieldDescriptor* field = descriptor->field(i);
        bool has_field = reflection->HasField(message, field);
        
        if (has_field) {
            //arrays not supported
            assert(!field->is_repeated());
            switch (field->cpp_type()) {
            //宏定义 CASE_FIELD_TYPE(cpptype, method, valuetype) 写case
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\
                case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
                    valuetype value = reflection->Get##method(message, field);\
                    int wsize = field->name().size();\
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
                    serialized_string->append(field->name().c_str(), field->name().size());\
                    wsize = sizeof(value);\
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
                    serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\
                    break;\
                }

                // 利用宏定义 CASE_FIELD_TYPE 写所有简单数据类型的case
                CASE_FIELD_TYPE(INT32, Int32, int);
                CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
                CASE_FIELD_TYPE(FLOAT, Float, float);
                CASE_FIELD_TYPE(DOUBLE, Double, double);
                CASE_FIELD_TYPE(BOOL, Bool, bool);
                CASE_FIELD_TYPE(INT64, Int64, int64_t);
                CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
#undef CASE_FIELD_TYPE
                case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
                    int value = reflection->GetEnum(message, field)->number();
                    int wsize = field->name().size();
                    //写入name占用字节数
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    //写入name
                    serialized_string->append(field->name().c_str(), field->name().size());
                    wsize = sizeof(value);
                    //写入value占用字节数
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    //写入value
                    serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
                    break;
                }
                case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
                    std::string value = reflection->GetString(message, field);
                    int wsize = field->name().size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(field->name().c_str(), field->name().size());
                    wsize = value.size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(value.c_str(), value.size());
                    break;
                }
                //递归 序列化 嵌套Message
                case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
                    std::string value;
                    int wsize = field->name().size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(field->name().c_str(), field->name().size());
                    const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
                    serialize_message(submessage, &value);
                    wsize = value.size();
                    serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
                    serialized_string->append(value.c_str(), value.size());
                    break;
                }
            }
        }
    }
}

4反射机制

4.1 Message

一般使用通过Message的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor/Reflection。

const google::protobuf::Reflection* pReflection = pMessage->GetReflection();
const google::protobuf::Descriptor* pDescriptor = pMessage->GetDescriptor();
const ::google::protobuf::Descriptor* pDescriptor =
      google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(msg_name);

4.2 Descriptor

Descriptor是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto文件内容等。
在类 Descriptor 中,可以通过如下方法获取类 FieldDescriptor:

const FieldDescriptor* field(int index) const; // 根据定义顺序索引获取,即从0开始到最大定义的条目
const FieldDescriptor* FindFieldByNumber(int number) const; // 根据定义的message里面的顺序值获取(option string name=3,3即为number)
const FieldDescriptor* FindFieldByName(const string& name) const; // 根据field name获取
const FieldDescriptor* Descriptor::FindFieldByLowercaseName(const std::string & lowercase_name)const; // 根据小写的field name获取
const FieldDescriptor* Descriptor::FindFieldByCamelcaseName(const std::string & camelcase_name) const; // 根据驼峰的field name获取

4.3 FieldDescriptor

FieldDescriptor描述message中的单个字段,例如字段名,字段属性(optional/required/repeated)等。
对于proto定义里的每种类型,都有一种对应的C++类型

const std::string & name() const; // Name of this field within the message.
CppType cpp_type() const; //C++ type of this field.
bool is_required() const; // 判断字段是否是必填
bool is_optional() const; // 判断字段是否是选填
bool is_repeated() const; // 判断字段是否是重复值
int number() const; // Declared tag number.
int index() const; //Index of this field within the message's field array, or the file or extension scope's extensions array.

4.4 Reflection

Reflection主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成

读操作和嵌套的message:

virtual int32  GetInt32 (const Message& message, const FieldDescriptor* field) const = 0;
virtual int64  GetInt64 (const Message& message, const FieldDescriptor* field) const = 0;
	  // See MutableMessage() for the meaning of the "factory" parameter.
virtual const Message& GetMessage(const Message& message, 
								  const FieldDescriptor* field, 
								  MessageFactory* factory = NULL) const = 0;

对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum等。

void SetInt32(Message * message, const FieldDescriptor * field, int32 value) const

读repeated类型字段:

int32 GetRepeatedInt32(const Message & message, const FieldDescriptor * field, int index) const
std::string GetRepeatedString(const Message & message, const FieldDescriptor * field, int index) const
const Message & GetRepeatedMessage(const Message & message, const FieldDescriptor * field, int index) const

写repeated类型字段:

void SetRepeatedInt32(Message * message, const FieldDescriptor * field, int index, int32 value) const
void SetRepeatedString(Message * message, const FieldDescriptor * field, int index, std::string value) const
void SetRepeatedEnumValue(Message * message, const FieldDescriptor * field, int index, int value) const // Set an enum field's value with an integer rather than EnumValueDescriptor. more..

新增重复字段

void AddInt32(Message * message, const FieldDescriptor * field, int32 value) const
void AddString(Message * message, const FieldDescriptor * field, std::string value) const

5特征工程使用

有了上面的知识,我们如何使用到自己的工程中呢。

5.1 定义proto文件

首先我们定义一个proto文件test_refactor.proto

syntax = "proto3";
package test.refactor;

option cc_generic_services = true;

message item_info {    // item 信息 
    int32 source               = 1;
    repeated int32 newsTypes   = 2;
    string name = 3;
};

message user_info {    // 用户信息
    int32 type           = 1;
    repeated int32 sex   = 2;
    string imei = 3;
};

message item_req {
    item_info item = 1;
    user_info user = 2;
};

message refactor_reqs {
    item_req req = 1;
}
  • 业务场景是所有的特征都包括在message的refactor_reqs中,利用这个message我们可以获取到对应的Descriptor

    const ::google::protobuf::Descriptor* descriptor =
    google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("test.refactor.refactor_reqs");

  • 获取对应field,通过name获取对应需要获取的FieldDescriptor,如获取item信息的数据,写为req.item

    field_descriptor = descriptor->FindFieldByName(“item”);

  • 最终每次获取的时候,我们获取的数据都是填充到test::refactor::refactor_reqs refactor_reqs中。

5.2

代码(略),见工程Demo/TestProto3

6 反射的两种主要用途

6.1 通过proto对象的名字来创建一个对象(json→pb)

即通过proto对象的完整字符串路径,可以创建一个对象,并根据field的名字对数据进行读写操作,实现从json到pb的转化。

#include <iostream>
#include <string>
#include "person.pb.h"
 
using namespace std;
 
/*
    Descriptor/FieldDescriptor位于descriptor.h文件;
    Message/Reflection 位于message.h文件
    以上四个类都位于 namespace google::protobuf下.
*/
using namespace google::protobuf;
 
typedef tencent::Person T;
 
Message* createMessage(const std::string& typeName);
 
int main()
{
	//通过Descriptor类的full_name函数获取相应结构的type_name
	std::string type_name = T::descriptor()->full_name();
    cout << "type_name:" << type_name << endl;
 
 
    //根据type name创建相应的message对象 new_person
    Message* new_person = createMessage(type_name);
    assert(new_person != NULL);//指针为null向stderr打印一条信息
    assert(typeid(*new_person) == typeid(tencent::Person::default_instance()));
    cout << "new_person:" << new_person->DebugString() << endl;
 
 
    //接下来使用DescriptorPool类的FindMessageTypeByName方法通过type_name查到元信息Descriptor*
    const Descriptor* descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name);
	cout << "FindMessageTypeByName() = " << descriptor << endl;
	cout << "T::descriptor()         = " << T::descriptor() << endl;
	cout << endl;
 
	// 再用MessageFactory::generated_factory() 找到 MessageFactory 对象
	const Message* prototype = MessageFactory::generated_factory()->GetPrototype(descriptor);
	cout << "GetPrototype()        = " << prototype << endl;
    cout << "T::default_instance() = " << &T::default_instance() << endl;
    cout << endl;
 
    //再然后我们实例化出一个实例
    //dynamic_cast:将基类的指针或引用安全第一转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。
    T* new_obj = dynamic_cast<T*>(prototype->New());
    cout << "prototype->New() = " << new_obj << endl;
    cout << endl;
 
    /*--------接下来看看反射接口怎么用--------*/
    //获取这个message的反射接口指针
    const Reflection* reflecter = new_obj->GetReflection();
 
    //通过name查找filed
    const FieldDescriptor* field = descriptor->FindFieldByName("name");
 
    //设置这个field的字段值
    std::string str1 = "shuozhuo";
    reflecter->SetString(new_obj, field, str1);
 
    //取出这个field的值
    std::cout << "\"name\" field is:" << reflecter->GetString(*new_obj,field)<< std::endl;
}
 
 
/*
    本函数的作用就是根据type name 自动创建具体的protobuf message对象;
*/
Message* createMessage(const std::string& typeName)
{
  Message* message = NULL;
  const Descriptor* descriptor = DescriptorPool::generated_pool()->FindMessageTypeByName(typeName);
  if (descriptor)
  {
    const Message* prototype = MessageFactory::generated_factory()->GetPrototype(descriptor);
    if (prototype)
    {
      message = prototype->New();
    }
  }
  return message;
}

6.2 通过Message初始化和获取成员变量的值(pb→json)

这个工作就是需要将2581C的数据转换成xml文件,此处用了到反射机制。

1、MessageLite类

所有message的接口类,从名字看就是lite的message,普通的message也是他的子类。

MessageLite是和“轻量级”的message(仅仅提供encoding+序列化,没有reflection和descriptors)。在确定可以使用“轻量级”message的场景下,可以在.proto文件中增加配置(option optimize_for = LITE_RUNTIME;),来让protocol compiler产出MessageLite类型的类,这样可以节省runtime资源。

注:lite 低热量的、淡的、轻量级的。

2、Message类

接口类,在MessageLite类的基础上增加了descriptor和reflection。

3、MessageFactory类

接口类,来找到MessageFactory对象,他能创建程序编译的时候所链接的全部protobuf Message types。其提供的GetPrototype方法可以找到具体的Message Type的default instance。 底层封装了GeneratedMessageFactory类。

4、DescriptorPool类

用 DescriptorPool::generated_pool() 找到一个 DescriptorPool 对象,它包含了程序编译的时候所链接的全部 protobuf Message types。然后通过其提供的 FindMessageTypeByName 方法即可根据type name 查找到Descriptor。

5、GeneratedMessageFactory类

继承自MessageFactory,singleton模式。

6、Descriptor类

描述一种message的meta信息(注意:不是单独的message对象)。构造函数是private类型,必须通过DescriptorPool(friend类)来构造。

const成员

const FileDescriptor* file_: 描述message所在的.proto文件信息
const Descriptor* containing_type_:如果在proto定义中,这个message是被其它message所包含,那么这个字段是上一级message的descriptor*;如果没有被包含,那么是NULL
const MessageOptions* options_: 定义在descriptor.proto,从注释看是用来和老版本proto1中MessageSet做拓展,可以先不去关注涉及extension的部分。

非const成员如下:

int field_count_:当前field包含的field的个数
FieldDescriptor* fields_: 以连续数组方式保存的所有的fieds
int nested_type_count_: 嵌套类型数量
Descriptor* nested_types_: message中嵌套message
int enum_type_count_: 内部enum的个数
EnumDescriptor* enum_types_: enum类型的连续内存起始地址

7、FileDescriptor类

描述整个.proto文件信息,其中包含:

1、依赖.proto文件信息:
int dependency_count_;
const FileDescriptor** dependencies_;
 
2、当前.proto文件包含的message信息:
int message_type_count_;
Descriptor* message_types_;
 
3、当前.proto文件包含的所有symbol(各种discriprot)的tables:
const FileDescriptorTables* tables_;

8、FileDescriptor类

描述一个单独的field,构造函数为private,也必须由DescriptorPool(friend类)构造。通过包含这个field的message的descriptor的函数(Descriptor::FindFieldByName())获得。

9、EnumDescriptor类

描述在.proto文件中定义的enum类型

7 生成稳定的protobuf库

7.1 vcpkg

  • 生成x86静态md版本
    vcpkg install protobuf:x86-windows-static-md
  • 生成x86静态mt版本
    vcpkg install protobuf:x86-windows-static
  • 生成x86动态版本
    vcpkg install protobuf

推荐参考资料

7 参考

ProtoBuf-反射原理与使用

protobuf反射详解及应用(pb/json相互转换)

C++ protobuf反射特征工程正确姿势

Protobuf反射详解

Protobuf: C++ 产生的代码简析(Proto3)

protobuf反射详解

protobuf反射详解(c++)

C++ 实用技术 - google protobuf反射技术 - 基础API

C++ Descriptor::field方法代码示例

Language Guide (proto 3)

Protocol Buffers Documentation

protobuf c++ 使用map笔记

标签:FieldDescriptor,const,protobuf,笔记,学习,field,message,Message
From: https://www.cnblogs.com/gwzz/p/17613564.html

相关文章

  • win7系统笔记本作为wifi热点提供无线连接
    只有有线没有路由器的可以用win系统的笔记本设置,给手机或者其他的笔记本提供无线连接 步骤如下:首先确认你的无线网卡可以使用。在开始菜单中依次找到“所有程序”--“附件”--“命令提示符”,右键“以管理员身份运行”。如下图所示:在“命令提示符”里输入“netshwlansethost......
  • 论文解读:《基于深度多核学习的用于识别 DNA n4 -甲基胞嘧啶位点的高阶模糊推理系统》
    Title:Adeepmultiplekernellearning-basedhigher-orderfuzzyinferencesystemforidentifyingDNAN4-methylcytosinesites期刊:InformationSciences中科院分区:一区(计算机科学技术)影像因子:8.1↓0.133文章链接:https://doi.org/10.1016/j.ins.2023.01.149Websever:Github:......
  • 笔记 | 类数组与数组扁平化
    一、类数组Array-like在日常中能接触到的类数组有这么几个:参数对象arguments;通过querySelector获取的NodeList;NodeList对象是节点集合,NodeList可以使用for...of来迭代,在一些情况下,NodeList是一个实时合集;通过函数:getElementsByTagNamegetElementsByClass......
  • JavaScript 基础(1) - 笔记
    1JavaScript基础1.1JavaScript是什么1.JavaScript(是什么?)是一种运行在客户端(浏览器)的编程语言,实现人机交互效果。2.作用(做什么?)网页特效(监听用户的一些行为让网页做出对应的反馈)表单验证(针对表单数据的合法性进行判断)数据交互(获取后台的数据,渲染到前端)服务端编程(node.js......
  • 扫描线学习笔记
    0.写在前面扫描线好闪,拜谢扫描线1.问题的引入在一个二维的坐标系上,给出多个矩形,求他们的面积并2.问题的分析假设我们有这么一张图你要求这三个矩形的面积并,可以考虑容斥原理,但这样会TLE但总之,他最终的结果是围成了一个多边形那你不妨考虑,重新分割这个最终的图形那......
  • c#关于终止thread 学习经典
    C#多线程学习笔记之(abort与join配合使用)转载:************   原文中的评论,有便于理解的内容*************************C#多线程学习笔记之(abort与join配合使用)  今天刚开始学多线程,尽管以前用过一点点,但是只是照着网上代码抄,没有真正理解,现在回过头来想研究研究,......
  • tensorflow猫狗大战笔记
    第一步:数据集的加工importcv2importos#使用os.walk()函数遍历指定文件夹train及其所有子文件夹。dir='train'#读取图片路径的设定需要在程序文件里建立train文件夹将需要更改尺寸的图片放入forroot,dirs,filesinos.walk(dir):#forroot,dirs,filesinos.walk......
  • webpack学习
    目录1.Webpack的作用?2.Node中的CommonJS规范3.包管理工具-NPM4.Node的工具集-path/url/util/zlib5.Node的文件操作能力-fs6.Node的缓冲(Buffer)和流(stream)7.Node的事件机制-EventEmitter8.Node的HTTP处理-请求与响应9.Node的事件循环-EventLoop10.Node的进程集群-Cluster1.Webpack......
  • 【安全学习之路】Day41
    ......
  • Vue中Router笔记学习整理
    1:摘要:  Vue中的Router是Vue.js框架中的一个核心插件,用于实现单页应用(SPA)的前端路由管理。它允许你在应用中定义不同的URL路径与对应的组件之间的映射,以便在不刷新整个页面的情况下,实现页面间的切换和数据加载。主要功能包括以下几个方面:声明式路由:你可以通过定义路由......