当前位置: 首页 > news >正文

淘宝搜索框去什么网站做/南昌seo技术外包

淘宝搜索框去什么网站做,南昌seo技术外包,安卓ui用什么软件设计,wordpress如何在地址栏中加网站logo文章目录 C | 定长内存池 | 对象池一、内存池的引入二、代码中的内存池实现 - ObjectPool类(一)整体结构(二)内存分配 - New函数(三)内存回收 - Delete函数 三、内存池在TreeNode示例中的性能测试演示四、脱…

文章目录

  • C++ | 定长内存池 | 对象池
    • 一、内存池的引入
    • 二、代码中的内存池实现 - `ObjectPool`类
      • (一)整体结构
      • (二)内存分配 - `New`函数
      • (三)内存回收 - `Delete`函数
    • 三、内存池在`TreeNode`示例中的性能测试演示
    • 四、脱离malloc,直接在堆上按页申请空间
    • 五、总结
      • (一)内存池的优势
      • (二)代码的思考

C++ | 定长内存池 | 对象池

在C++编程的世界里,内存管理是一个至关重要的话题。今天,我们就来深入研究一下基于下面这段代码的内存池概念。

先上代码:

#include<iostream>
#include<vector>
#include <time.h>
using std::cout;
using std::endl;//定长内存池,一次申请N大小的空间
//template<size_t N>
//class ObjectPool {
//};template<class T>
class ObjectPool {
public:T* New() {T* obj = nullptr;if (_freelist) //_freelist非空,说明有还回来的内存,优先使用还回来的内存//可以使用类似无头结点链表的头删{void* next = *(void**)_freelist;obj = (T*)_freelist;_freelist = next;}else{if (_remainbytes < sizeof(T)) {_remainbytes = 128 * 1024;_memory = (char*)malloc(128 * 1024);if (_memory == nullptr) {throw std::bad_alloc();}}obj = (T*)_memory;		//申请出去的新的空间的起始位置//_memory += sizeof(T);		//新的空间被申请后,剩余空间的起始位置size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objsize;_remainbytes -= sizeof(T);	//剩余的空间}//定位new,显式调用T的构造函数初始化new(obj)T;return obj;}void Delete(T* obj) {//显式调用析构函数清理对象obj->~T();//就相当于无头结点链表的头插*(void**)obj = _freelist;_freelist = *(void**)obj;}
private://void* _memory;//为了方便地址上的加减运算,使用char类型,char类型占一个字节char* _memory = nullptr;	//指向大块内存的指针void* _freelist = nullptr;	//指向待释放的空间size_t _remainbytes = 0;    // 大块内存在切分过程中剩余字节数
};struct TreeNode//二叉树节点
{int _val;TreeNode* _left;TreeNode* _right;TreeNode():_val(0), _left(nullptr), _right(nullptr){}
};void TestObjectPool()
{// 申请释放的轮次const size_t Rounds = 5;// 每轮申请释放多少次const size_t N = 100000;std::vector<TreeNode*> v1;v1.reserve(N);size_t begin1 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i){v1.push_back(new TreeNode);}for (int i = 0; i < N; ++i){delete v1[i];}v1.clear();}size_t end1 = clock();std::vector<TreeNode*> v2;v2.reserve(N);ObjectPool<TreeNode> TNPool;size_t begin2 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i){v2.push_back(TNPool.New());}for (int i = 0; i < N; ++i){TNPool.Delete(v2[i]);}v2.clear();}size_t end2 = clock();cout << "new cost time:" << end1 - begin1 << endl;cout << "object pool cost time:" << end2 - begin2 << endl;
}int main()
{TestObjectPool();return 0;
}

一、内存池的引入

在很多C++程序中,频繁地进行内存的分配和释放(例如使用newdelete操作符)可能会带来性能上的开销,尤其是在处理大量小对象时。内存池(Memory Pool)技术应运而生,它就像是一个预先准备好的内存资源仓库,能够更高效地管理内存的分配和回收。

二、代码中的内存池实现 - ObjectPool

(一)整体结构

我们来看代码中的ObjectPool类,这是一个模板类(template<class T>),这意味着它可以为不同类型的对象提供内存池服务。

template<class T>
class ObjectPool {
public:T* New();void Delete(T* obj);
private://void* _memory;//为了方便地址上的加减运算,使用char类型,char类型占一个字节char* _memory = nullptr;	//指向大块内存的指针void* _freelist = nullptr;	//指向待释放的空间size_t _remainbytes = 0;    // 大块内存在切分过程中剩余字节数
};
  1. 成员变量
    • _memory:这是一个char*类型的指针,初始化为nullptr。它的作用是指向大块内存。选择char*类型是为了方便进行地址的加减运算,因为char类型在内存中占用一个字节。这个大块内存就是我们内存池的核心资源,用来存储对象。
    • _freelist:类型为void*,初始值是nullptr。它就像是一个管理空闲内存块的列表,当对象被释放后,相关的内存块会被添加到这个列表中,以便后续的复用。
    • _remainbytes:这是一个size_t类型的变量,初始化为0。它记录了大块内存在切分过程中剩余的字节数。这个变量对于判断何时需要重新申请大块内存非常关键。

(二)内存分配 - New函数

T* ObjectPool::New() {T* obj = nullptr;if (_freelist) //_freelist非空,说明有还回来的内存,优先使用还回来的内存//可以使用类似无头结点链表的头删{void* next = *(void**)_freelist;obj = (T*)_freelist;_freelist = next;}else{if (_remainbytes < sizeof(T)) {_remainbytes = 128 * 1024;/*如果空间无法被T整除,可能会出现会有一块内存不够一个T对象的空间,所以会导致_remainBytes的结果非零,而空间又不够用了,因此if()中的判定应该是_remainBytes < sizeof(T),即剩余内存不够一个对象大小时,则重开大块空间,以避免越界访问*/_memory = (char*)malloc(128 * 1024);if (_memory == nullptr) {throw std::bad_alloc();}}obj = (T*)_memory;		//申请出去的新的空间的起始位置//_memory += sizeof(T);		//新的空间被申请后,剩余空间的起始位置size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objsize;/*由于指针变量的大小是4个字节(32位)或者8个字节(64位),而回收内存的时候是通过链表回收的,需要存储加一个节点的地址,因此如果当T类型的类型大小小于指针大小的时候无法存储一个地址,例如32位下指针大小4个字节,而char类型1个字节,这时候回收链表中只有一个字节的大小的话显然无法存放一个地址,因此需要作比较,如果类型大小小于指针变量类型大小,则返回一个指针变量的大小。*/_remainbytes -= sizeof(T);	//剩余的空间}//定位new,显式调用T的构造函数初始化new(obj)T;return obj;}
  1. 优先使用空闲内存
    • 当调用New函数来获取一个对象时,首先会检查_freelist是否为空(if (_freelist))。如果_freelist不为空,说明有已经回收但还未重新分配的内存。这时候就会采用类似无头结点链表的头删操作来获取内存块。具体来说,先获取_freelist指向的内存块(通过void* next = *(void**)_freelist;),然后将这个内存块作为新对象的地址(obj = (T*)_freelist;),最后更新_freelist指向该内存块中的下一个空闲内存的指针(_freelist = next;)。
  2. 从大块内存分配
    • 如果_freelist为空,就需要从大块内存_memory中分配空间。在分配之前,会检查_remainbytes是否小于sizeof(T)。如果是,就意味着当前大块内存剩余空间不足以分配一个T类型的对象了。这时候,会重新申请一块128 * 1024字节的内存(_remainbytes = 128 * 1024; _memory = (char*)malloc(128 * 1024);),并且在申请失败时抛出std::bad_alloc异常。
    • 在分配内存时,还有一个很巧妙的地方。由于在回收内存时是通过链表来管理的,需要存储下一个节点的地址,而不同机器上指针类型的大小可能不同(32位机器上为4字节,64位机器上为8字节)。所以在计算分配的字节数时,会比较sizeof(T)sizeof(void*)的大小(size_t objsize = sizeof(T) < sizeof(void*)? sizeof(void*) : sizeof(T);),取较大值来确保能够正确存储下一个空闲对象的指针。然后将新对象的地址赋给obj,并更新_memory_remainbytesobj = (T*)_memory; _memory += objsize; _remainbytes -= sizeof(T);)。
    • 最后,通过定位newnew(obj)T;)显式调用T的构造函数来初始化这个新对象。

(三)内存回收 - Delete函数

void ObjectPool::Delete(T* obj) {//显式调用析构函数清理对象obj->~T();//就相当于无头结点链表的头插*(void**)obj = _freelist;_freelist = *(void**)obj;/*之所以使用void类型来强制转换,只因为在不同位机器下,地址的字节位数不同比如在32位下,指针类型的大小是四个字节,而在64位下,指针类型的大小是八个字节因此如果想要通过二级指针来修改obj的指向,同时兼顾不同机器,使用void类型来进行强制类型转换。*/}
  1. 清理对象资源
    • 当调用Delete函数来释放对象时,首先会显式调用对象的析构函数(obj->~T();),这一步确保对象内部的资源被正确清理。
  2. 归还内存到空闲列表
    • 然后,将释放的内存块添加到_freelist的头部,这一操作类似于无头结点链表的头插操作。通过*(void**)obj = _freelist; _freelist = *(void**)obj;来实现。这里使用void*类型进行强制转换是为了在不同位机器下(地址字节位数不同)能够正确地通过二级指针修改obj的指向,从而将对象的内存块添加到空闲内存列表中。

三、内存池在TreeNode示例中的性能测试演示

struct TreeNode//二叉树节点
{int _val;TreeNode* _left;TreeNode* _right;TreeNode():_val(0), _left(nullptr), _right(nullptr){}
};void TestObjectPool()
{// 申请释放的轮次const size_t Rounds = 5;// 每轮申请释放多少次const size_t N = 100000;std::vector<TreeNode*> v1;v1.reserve(N);size_t begin1 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i){v1.push_back(new TreeNode);}for (int i = 0; i < N; ++i){delete v1[i];}v1.clear();}size_t end1 = clock();std::vector<TreeNode*> v2;v2.reserve(N);ObjectPool<TreeNode> TNPool;size_t begin2 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i){v2.push_back(TNPool.New());}for (int i = 0; i < N; ++i){TNPool.Delete(v2[i]);}v2.clear();}size_t end2 = clock();cout << "new cost time:" << end1 - begin1 << endl;cout << "object pool cost time:" << end2 - begin2 << endl;
}
int main()
{TestObjectPool();return 0;
}
  • debug下运行效果:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • release下运行效果:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    可以看到使用对象池的程序运行效率会高不少

  1. 普通的new/delete操作
    • TestObjectPool函数中,首先对普通的newdelete操作进行了测试。创建了一个std::vector<TreeNode*>类型的v1,并预留了N个元素的空间(v1.reserve(N);)。
    • 然后通过两层循环,外层循环Rounds次,内层循环N次。在每次内层循环中,使用new创建一个TreeNode对象并添加到v1中(v1.push_back(new TreeNode);),然后再使用delete释放这些对象(delete v1[i];),最后清空v1向量(v1.clear();)。通过记录这个过程开始的时间(size_t begin1 = clock();)和结束的时间(size_t end1 = clock();),就可以计算出使用普通new/delete操作的耗时。
  2. 使用内存池的操作
    • 接着,创建了一个ObjectPool<TreeNode>类型的对象池TNPool,同样创建了一个std::vector<TreeNode*>类型的v2并预留N个元素的空间。
    • 也是通过类似的两层循环,在每次内层循环中,使用对象池的New函数获取TreeNode对象并添加到v2中(v2.push_back(TNPool.New());),然后使用对象池的Delete函数释放对象(TNPool.Delete(v2[i]);),最后清空v2向量。同样记录这个过程开始的时间(size_t begin2 = clock();)和结束的时间(size_t end2 = clock();),从而得到使用内存池操作的耗时。
    • 最后,通过cout输出两种方式的耗时(cout << "new cost time:" << end1 - begin1 << endl; cout << "object pool cost time:" << end2 - begin2 << endl;)。从这个结果可以直观地看到在频繁创建和销毁TreeNode对象的场景下,使用内存池能够带来明显的性能提升。

四、脱离malloc,直接在堆上按页申请空间

#ifdef _WIN32
#include<windows.h>
#else
// 
#endif// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}

预处理:

  1. #ifdef指令
    • #ifdef是C和C++中的预处理指令。它用于条件编译,在这里的含义是“如果定义了(ifdefif defined的缩写)符号_WIN32”。
    • 在不同的编译环境下,可能会预定义一些特定的符号。在Windows环境下编译时,通常会预定义_WIN32这个符号(在64位Windows下可能还会预定义_WIN64,但这里只关注_WIN32相关的逻辑)。
  2. 包含头文件<windows.h>
    • _WIN32被定义时(即代码在Windows环境下编译),#include <windows.h>会被执行。<windows.h>是一个非常重要的Windows平台的头文件,它包含了大量Windows系统相关的函数声明、数据类型定义、宏定义等内容。例如,Windows下的图形界面编程(使用Windows API)、系统调用、进程和线程相关的操作等很多功能都需要这个头文件中的定义来支持。
  3. #else#endif
    • #else是与#ifdef配合使用的预处理指令,表示“否则”的情况。在这里,如果_WIN32没有被定义(即代码不是在Windows环境下编译),那么会执行#else后面的内容。不过在这段代码中,#else后面只是一个注释(//),没有实际的代码,可能是预留的用于在非Windows环境下(如Linux、macOS等)添加相关代码的地方。
    • #endif#ifdef - #else结构的结束标志,表示条件编译块的结束。如果没有#endif,编译器会报错,因为它不知道条件编译块在哪里结束。

SystemAlloc函数:

  1. 函数声明部分
    • inline static void* SystemAlloc(size_t kpage)
      • inline关键字:这是一个C++ 中的关键字,用于建议编译器将函数内联化。内联函数的主要目的是减少函数调用的开销。当函数被标记为内联时,编译器在编译时会尝试将函数体直接嵌入到调用该函数的地方,而不是像普通函数调用那样进行压栈、跳转等操作。
      • static关键字:在这里表示函数具有内部链接性,即这个函数只能在当前的源文件中被访问,不能被其他源文件访问。
      • void*:表示函数的返回类型是一个通用指针(指向未知类型数据的指针)。
      • SystemAlloc:函数名。
      • size_t kpage:函数接受一个size_t类型(无符号整数类型,通常用于表示对象的大小或数组的长度等)的参数kpage
  2. #ifdef _WIN32条件编译部分(Windows平台下的实现)
    • void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
      • VirtualAlloc是Windows系统中的一个函数,用于在进程的虚拟地址空间中分配内存。
        • 第一个参数0:表示让系统自动选择分配内存的起始地址。
        • 第二个参数kpage << 13:这里使用了左移运算符<<kpage是传入的参数,表示某种数量(可能是页数之类的概念),左移13位相当于将kpage乘以2^13(即8192)。这意味着以页为单位计算要分配的内存大小(在Windows中,内存页大小通常是4KB或8KB,这里假设是8KB,那么kpage就表示要分配的页数)。
        • 第三个参数MEM_COMMIT | MEM_RESERVE:这是内存分配类型的标志组合。MEM_COMMIT表示提交内存,即将物理存储映射到进程的虚拟地址空间;MEM_RESERVE表示保留一块地址空间,这块空间可以被后续的操作(如提交内存)使用。
        • 第四个参数PAGE_READWRITE:表示分配的内存具有可读可写的保护属性。
  3. #else部分(非Windows平台下的占位代码)
    • 在非Windows平台下(即_WIN32未被定义时),这里只是一个注释// linux下brk mmap等,说明在Linux平台下可能会使用brkmmap等系统调用或函数来实现类似的内存分配功能,但目前没有实际的代码实现。
  4. 内存分配失败处理部分
    • if (ptr == nullptr)
      • 这个if语句用于检查内存分配是否成功。如果ptrnullptr(即VirtualAlloc函数在Windows平台下分配内存失败,或者在非Windows平台下如果有实现且分配失败时也应该遵循类似的逻辑),则表示内存分配失败。
    • throw std::bad_alloc();
      • 当内存分配失败时,函数会抛出std::bad_alloc异常。std::bad_alloc是C++ 标准库中定义的用于表示内存分配失败的异常类型。当使用new操作符分配内存失败时,也会抛出这个异常。
  5. 函数返回部分
    • return ptr;
      • 如果内存分配成功(在Windows平台下VirtualAlloc成功或者在非Windows平台下假设的brkmmap等操作成功),函数将返回分配得到的内存地址指针。

使用这种方式修改后的代码如下:

#ifdef _WIN32
#include<windows.h>
#else
// 
#endif// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}template<class T>
class ObjectPool {
public:T* New() {T* obj = nullptr;if (_freelist) {void* next = *(void**)_freelist;obj = (T*)_freelist;_freelist = next;}else{if (_remainbytes < sizeof(T)) {_remainbytes = 128 * 1024;//_memory = (char*)malloc(128 * 1024);_memory = (char*)SystemAlloc(_remainbytes >> 13);if (_memory == nullptr) {throw std::bad_alloc();}}obj = (T*)_memory;		//申请出去的新的空间的起始位置//_memory += sizeof(T);		//新的空间被申请后,剩余空间的起始位置size_t objsize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);_memory += objsize;_remainbytes -= sizeof(T);	//剩余的空间}//定位new,显式调用T的构造函数初始化new(obj)T;return obj;}void Delete(T* obj) {obj->~T();*(void**)obj = _freelist;_freelist = *(void**)obj;}
private:char* _memory = nullptr;	//指向大块内存的指针void* _freelist = nullptr;	//指向待释放的空间size_t _remainbytes = 0;    // 大块内存在切分过程中剩余字节数
};

五、总结

(一)内存池的优势

  1. 性能提升
    • 通过避免频繁的系统级内存分配(malloc)和释放(free)操作,内存池减少了内存管理的开销。在处理大量对象的创建和销毁时,这种优势更加明显。就像我们在TestObjectPool函数中的测试结果一样,使用内存池操作TreeNode对象比普通的new/delete操作要快很多。
  2. 内存碎片减少
    • 内存池通过预先分配大块内存,并对其进行有效的管理和复用,减少了内存碎片的产生。这有助于提高内存的利用率,使程序能够更稳定地运行,尤其是在长时间运行的程序或者内存资源紧张的环境中。

(二)代码的思考

  1. 可扩展性
    • 由于ObjectPool是一个模板类,它可以方便地应用于各种类型的对象,只要这些对象的构造函数和析构函数定义正确。这为代码的复用和扩展提供了很大的便利,使我们可以在不同的项目中轻松地使用这个内存池来管理不同类型的对象。
  2. 局限性
    • 内存池的实现也存在一些局限性。例如,它是基于定长内存池的思想,每次重新申请内存时固定为128 * 1024字节。在处理大小差异很大的对象时,可能会导致内存浪费或者频繁重新申请内存的情况。另外,这个代码没有考虑多线程环境下的并发安全问题,如果在多线程中同时使用这个内存池,可能会导致数据竞争和错误的内存管理操作。

相关文章:

C++ | 定长内存池 | 对象池

文章目录 C | 定长内存池 | 对象池一、内存池的引入二、代码中的内存池实现 - ObjectPool类&#xff08;一&#xff09;整体结构&#xff08;二&#xff09;内存分配 - New函数&#xff08;三&#xff09;内存回收 - Delete函数 三、内存池在TreeNode示例中的性能测试演示四、脱…...

python画图|自制渐变柱状图

在前述学习过程中&#xff0c;我们已经通过官网学习了如何绘制渐变的柱状图及其背景。 掌握一门技能的最佳检验方式就是通过实战&#xff0c;因此&#xff0c;本文尝试做一些渐变设计。 前述学习记录可查看链接&#xff1a; Python画图|渐变背景-CSDN博客 【1】柱状图渐变 …...

基于RPA+BERT的文档辅助“悦读”系统 | OPENAIGC开发者大赛高校组AI创作力奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…...

K8S部署流程

一、war打包镜像(survey,analytics,trac系统) 代码打包成war准备tomcat的server.xml文件&#xff0c;修改connector中8080端口为项目的端口 修改前&#xff1a; <Connector port"8080" protocol"HTTP/1.1"connectionTimeout"20000"redirect…...

DevExpress WinForms中文教程:Data Grid - 如何添加或删除行?

本教程介绍DevExpress WinForm的Data Grid控件UI元素和API&#xff0c;它们使您和最终用户能够添加或删除数据行。您将首选学习如何启用内置的数据导航器&#xff0c;然后学习如何使用Microsoft Outlook启发的New Item行添加新记录。最后教程将向您展示基本的API&#xff0c;它…...

u盘格式化后数据能恢复吗?2024年Top4恢复神器来帮忙

在这个电脑和手机满天飞的时代&#xff0c;U盘是我们用来存东西和传文件的得力助手&#xff0c;特别重要。但是&#xff0c;有时候U盘可能会不小心被格式化了&#xff0c;里面的重要文件就不见了。那么&#xff0c;U盘格式化后的数据还能恢复吗&#xff1f;当然可以。今天会告诉…...

深度学习·Argparse

Argparse 命令行选项、参数和子命令解析器 ArgumentParser 命令行传参数->解析参数->获得对应参数 初始化&#xff1a;parser argparse.ArgumentParser(descriptionxxx)添加命令行参数&#xff1a; parser.add_argument("--training_filepath", typestr, he…...

制造企业为何需要PLM系统?PLM系统解决方案对制造业重要性分析

制造企业为何需要PLM系统&#xff1f;PLM系统解决方案对制造业重要性分析 新华社9月23日消息&#xff0c;据全国组织机构统一社会信用代码数据服务中心统计&#xff0c;我国制造业企业总量突破600万家。数据显示&#xff0c;2024年1至8月&#xff0c;我国制造业企业数量呈现稳…...

http协议中的header详细讲解

http协议中的header详细讲解 HTTP 协议和 TCP/IP 协议族内的其他众多的协议相同&#xff0c;用于客户端和服务器之间的通信。 请求访问文本或图像等资源的一端称为客户端&#xff0c;而提供资源响应的一端称为服务器端。 HTTP 协议规定&#xff0c;请求从客户端发出&#xf…...

探索后量子安全:基于格加密技术的未来密码学展望

在信息技术日新月异的今天&#xff0c;量子计算作为下一代计算技术的代表&#xff0c;正逐步从理论走向实践。量子计算的出现对现有的加密体系构成了严重威胁&#xff0c;尤其是基于大数分解和离散对数难题的传统密码学&#xff08;如RSA和Diffie-Hellman协议&#xff09;。为了…...

WPF之UI进阶--完整了解wpf的控件和布局容器及应用

前面三篇有关WPF的基础介绍&#xff0c;分别介绍了wpf与winform的异同&#xff0c;wpf的事件生成和使用以及数据绑定。但我们还缺乏一副好的“皮囊”&#xff0c;所以从这篇开始我们来开始学习wpf的UI相关的内容&#xff0c;首当其冲的就是布局容器。 其实我们知道&#xff0c;…...

unity一键注释日志和反注释日志

开发背景&#xff1a;游戏中日志也是很大的开销&#xff0c;虽然有些日志不打印但是毕竟有字符串的开销&#xff0c;甚至有字符串拼接的开销&#xff0c;有些还有装箱和拆箱的开销&#xff0c;比如Debug.Log(1) 这种 因此需要注释掉&#xff0c;当然还需要提供反注释的功能&am…...

VBA数据库解决方案第十五讲:Recordset集合中单个数据的精确处理

《VBA数据库解决方案》教程&#xff08;版权10090845&#xff09;是我推出的第二套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;是学完字典后的另一个专题讲解。数据库是数据处理的利器&#xff0c;教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…...

甄选范文“论软件需求管理”,软考高级论文,系统架构设计师论文

论文真题 软件需求管理是一个对系统需求变更了解和控制的过程。需求管理过程与需求开发过程相互关联,初始需求导出的同时就要形成需求管理规划,一旦启动了软件开发过程,需求管理活动就紧密相伴。 需求管理过程中主要包含变更控制、版本控制、需求跟踪和需求状态跟踪等4项活…...

Android Studio Dolphin 中Gradle下载慢的解决方法

我用的版本Android Studio Dolphin | 2021.3.1 Patch 1 1.Gradle自身的版本下载慢 解决办法&#xff1a;修改gradle\wrapper\gradle-wrapper.properties中的distributionUrl 将https\://services.gradle.org/distributions为https\://mirrors.cloud.tencent.com/gradle dis…...

Excel实现省-市-区/县级联

数据准备 准备省份-城市映射数据&#xff0c;如下&#xff1a; 新建sheet页&#xff0c;命名为&#xff1a;省-市数据源&#xff0c;然后准备数据&#xff0c;如下所示&#xff1a; 准备城市-区|县映射数据&#xff0c;如下&#xff1a; 新建sheet页&#xff0c;命名为&#x…...

【优化代码结构】函数的参数归一化

某些封装的函数&#xff0c;其参数具有多样性&#xff0c;会导致函数中会增加非常多的分支&#xff0c;比如下面这个 format 函数有如下几种参数方式&#xff0c;其中 formatter 会有很多种情况 date&#xff1a;日期对象formatter&#xff1a; ‘date’&#xff1a;格式化日期…...

CSS中height设置100vh和100%的区别

文章目录 CSS中height设置100vh和100%的区别一、引言二、高度设置的区别1、100%1.1、父元素高度固定1.2、父元素高度未定义 2、100vh2.1、视口高度2.2、不受父元素限制 三、总结 CSS中height设置100vh和100%的区别 一、引言 在前端开发中&#xff0c;我们经常需要设置元素的高…...

红米k60至尊版工程固件 MTK芯片 资源预览 刷写说明 与nv损坏修复去除电阻图示

红米k60至尊版机型代码为:corot。 搭载了联发科天玑9200+处理器。此固件mtk引导为MT6985。博文将简单说明此固件的一些特点与刷写注意事项。对于NV损坏的机型。展示修改校验电阻的图示。方便改写参数等 通过博文了解 1💝💝💝-----此机型工程固件的资源刷写注意事项 2…...

QEMU使用Qemu-Guest-Agent传输文件、执行指令等

简介 之前介绍过qemu传输文件&#xff0c;使用的挂载 / samba方式 &#xff1a;Qemu和宿主机不使用外网进行文件传输。 这是一种方式&#xff0c;这里还有另一种方式&#xff1a;使用Qemu-Guest-Agent&#xff0c;后面简称qga。 官网介绍&#xff1a;https://www.qemu.org/d…...

【漏洞复现】金和OA C6 GeneralXmlhttpPage.aspx Sql注入漏洞

免责声明: 本文旨在提供有关特定漏洞的信息,以帮助用户了解潜在风险。发布此信息旨在促进网络安全意识和技术进步,并非出于恶意。读者应理解,利用本文提到的漏洞或进行相关测试可能违反法律或服务协议。未经授权访问系统、网络或应用程序可能导致法律责任或严重后果…...

复数表示的电场

Exm加是复振幅&#xff0c;这是用复数表示电场&#xff0c;并提取只与空间有关的项复振幅就是复数表示电场&#xff0c;且把与空间xyz有关的量提取出来 经过验证实数E0cos&#xff08;wtδx&#xff09;对t求导&#xff0c;等于E0e^j(wtδx)对t求导再取实部 实数表示电磁波cos…...

常用快捷键整理

用加粗标注的是我个人使用时常用的&#xff0c;其实这个全凭个人喜好&#xff0c;大家可以熟悉一下自己喜欢的&#xff0c;都多试试&#xff0c;把觉得有用的记一下&#xff0c;多使用&#xff0c;后续写代码效率就会提高一些) 常用 VS 运行调试程序快捷键 编译 . 编译程序&a…...

【Transformer】长距离依赖

在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;长距离依赖&#xff08;Long-Range Dependencies&#xff09;指的是在文本中相隔较远的两个或多个元素之间的依赖关系。这些依赖关系可以是语法上的&#xff0c;也可以是语义上的。例如&#xff0c;在句子中&#xff0…...

Git傻傻分不清楚(下)

进入Idea编译器 File -> New -> Project from Version Control -> URL (这个路径是要拉取项目的Github路径哦~) 设置成maven项目...

golang学习笔记27-反射【重要】

本节也是GO核心部分&#xff0c;很重要。包括基本类型的反射&#xff0c;结构体类型的反射&#xff0c;类别方法Kind()&#xff0c;修改变量的值。 目录 一、概念&#xff0c;基本类型的反射二、结构体类型的反射三、类别方法Kind()四、修改变量的值 一、概念&#xff0c;基本…...

利用Puppeteer-Har记录与分析网页抓取中的性能数据

引言 在现代网页抓取中&#xff0c;性能数据的记录与分析是优化抓取效率和质量的重要环节。本文将介绍如何利用Puppeteer-Har工具记录与分析网页抓取中的性能数据&#xff0c;并通过实例展示如何实现这一过程。 Puppeteer-Har简介 Puppeteer是一个Node.js库&#xff0c;提供…...

YOLOv5改进系列(1)——添加CBAM注意力机制

一、如何理解注意力机制 假设你正在阅读一本书&#xff0c;同时有人在你旁边说话。当你听到某些关键字时&#xff0c;比如“你的名字”或者“你感兴趣的话题”&#xff0c;你会自动把注意力从书上转移到他们的谈话上&#xff0c;尽管你并没有完全忽略书本的内容。这就是注意力机…...

无头单向非循环java版的模拟实现

【本节目标】 1.ArrayList的缺陷 2.链表 1. ArrayList的缺陷 上节课已经熟悉了 ArrayList 的使用&#xff0c;并且进行了简单模拟实现。通过源码知道&#xff0c; ArrayList 底层使用数组来存储元素&#xff1a; public class ArrayList<E> extends AbstractList<…...

Bert Score-文本相似性评估

Bert Score Bert Score 是基于BERT模型的一种方法。它通过计算两个句子在BERT模型中的嵌入编码之间的余弦相似度来评估它们的相似度。BERTScore考虑了上下文信息和语义信息&#xff0c;因此能够更准确地衡量句子之间的相似度。 安装 pip install bert-score 使用例子 一个…...