C++学习:六个月从基础到就业——内存管理:自定义内存管理(上篇)
C++学习:六个月从基础到就业——内存管理:自定义内存管理(上篇)
本文是我C++学习之旅系列的第二十一篇技术文章,也是第二阶段"C++进阶特性"的第六篇,主要介绍C++中的自定义内存管理技术(上篇)。查看完整系列目录了解更多内容。
引言
在前面的文章中,我们已经探讨了C++标准提供的内存管理工具,包括堆与栈的使用、new
/delete
操作符、内存泄漏的避免、RAII原则以及智能指针。这些机制在大多数应用场景下已经足够使用,但在某些特定情况下,标准内存分配器可能无法满足我们的需求,例如:
- 需要极高的性能,标准分配器造成的开销过大
- 需要特定的内存布局或对齐方式
- 需要减少内存碎片化
- 需要追踪和调试内存使用
- 在特定硬件或嵌入式系统上工作
在这些情况下,自定义内存管理就变得非常重要。本文(上篇)将详细介绍C++中自定义内存管理的基础知识、核心技术以及简单实现,而下篇将会介绍更多高级应用场景和实际项目中的最佳实践。
内存管理基础回顾
在深入自定义内存管理之前,让我们简要回顾一下C++中内存管理的基础知识。
标准内存分配过程
当我们在C++中使用new
运算符时,实际发生了以下步骤:
- 内存分配:调用
operator new
函数分配原始内存 - 对象构造:在分配的内存上调用构造函数
- 返回指针:返回指向新创建对象的指针
类似地,当使用delete
运算符时:
- 对象析构:调用对象的析构函数
- 内存释放:调用
operator delete
函数释放内存
这个过程的关键在于operator new
和operator delete
函数,它们是可以被重载的,这为我们提供了自定义内存管理的入口。
堆内存分配的问题
标准堆内存分配存在一些常见问题:
- 性能开销:每次分配/释放都需要系统调用,开销较大
- 内存碎片化:频繁的分配和释放可能导致内存碎片
- 缓存不友好:随机分配的内存可能分散在不同的缓存行中
- 分配失败处理:需要额外的代码处理内存分配失败
- 调试困难:难以追踪内存泄漏和内存使用模式
自定义内存管理的目标就是解决或缓解这些问题。
自定义内存管理技术
重载全局new和delete运算符
最直接的自定义内存管理方式是重载全局new
和delete
运算符:
#include <iostream>
#include <cstdlib>// 重载全局operator new
void* operator new(std::size_t size) {std::cout << "Global operator new called, size = " << size << " bytes" << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;
}// 重载全局operator delete
void operator delete(void* ptr) noexcept {std::cout << "Global operator delete called" << std::endl;std::free(ptr);
}// 重载数组版本
void* operator new[](std::size_t size) {std::cout << "Global operator new[] called, size = " << size << " bytes" << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;
}void operator delete[](void* ptr) noexcept {std::cout << "Global operator delete[] called" << std::endl;std::free(ptr);
}int main() {// 使用重载的new/deleteint* p1 = new int(42);std::cout << "Value: " << *p1 << std::endl;delete p1;// 使用重载的new[]/delete[]int* arr = new int[10];arr[0] = 10;std::cout << "Array first element: " << arr[0] << std::endl;delete[] arr;return 0;
}
输出结果类似于:
Global operator new called, size = 4 bytes
Value: 42
Global operator delete called
Global operator new[] called, size = 40 bytes
Array first element: 10
Global operator delete[] called
这种方法会影响程序中所有的内存分配,通常用于全局内存跟踪或调试。但要注意,这是一种侵入性很强的方法,可能会影响第三方库的行为,所以在生产环境中需要谨慎使用。
类特定的new和delete重载
对于特定的类,我们可以只重载该类的new
和delete
运算符,这样就不会影响其他部分的内存分配:
class MyClass {
private:int data;public:MyClass(int d) : data(d) {std::cout << "MyClass constructor called with data = " << data << std::endl;}~MyClass() {std::cout << "MyClass destructor called with data = " << data << std::endl;}// 类特定的operator newvoid* operator new(std::size_t size) {std::cout << "MyClass::operator new called, size = " << size << " bytes" << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;}// 类特定的operator deletevoid operator delete(void* ptr) noexcept {std::cout << "MyClass::operator delete called" << std::endl;std::free(ptr);}// 还可以重载数组版本void* operator new[](std::size_t size) {std::cout << "MyClass::operator new[] called, size = " << size << " bytes" << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;}void operator delete[](void* ptr) noexcept {std::cout << "MyClass::operator delete[] called" << std::endl;std::free(ptr);}// 可以添加额外参数版本,如placement newvoid* operator new(std::size_t size, const char* file, int line) {std::cout << "MyClass::operator new called from " << file << ":" << line << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;}// 对应的delete版本void operator delete(void* ptr, const char* file, int line) noexcept {std::cout << "MyClass::operator delete called from " << file << ":" << line << std::endl;std::free(ptr);}
};// 使用宏简化调用
#define DEBUG_NEW new(__FILE__, __LINE__)int main() {// 使用类特定的new/deleteMyClass* obj1 = new MyClass(42);delete obj1;// 使用数组版本MyClass* arr = new MyClass[3]{1, 2, 3};delete[] arr;// 使用带额外参数的版本MyClass* obj2 = DEBUG_NEW MyClass(100);delete obj2;return 0;
}
这种方法只影响特定类的内存分配,适用于有特殊内存需求的类,如频繁创建销毁的小对象,或者需要进行内存使用追踪的类。
placement new
Placement new是C++的一个特殊形式的new
运算符,它允许我们在预先分配的内存上构造对象:
#include <iostream>
#include <new> // 为placement new引入必要的头文件class Complex {
private:double real;double imag;public:Complex(double r = 0, double i = 0) : real(r), imag(i) {std::cout << "Constructor called: " << real << " + " << imag << "i" << std::endl;}~Complex() {std::cout << "Destructor called: " << real << " + " << imag << "i" << std::endl;}void display() const {std::cout << "Complex number: " << real << " + " << imag << "i" << std::endl;}
};int main() {// 分配一块足够大的内存,不调用构造函数char memory[sizeof(Complex)];// 使用placement new在预分配内存上构造对象Complex* ptr = new(memory) Complex(3.0, 4.0);// 使用对象ptr->display();// 显式调用析构函数(不需要delete,因为内存不是通过new分配的)ptr->~Complex();// 演示数组的placement newconstexpr int arraySize = 3;char arrayMemory[sizeof(Complex) * arraySize];Complex* arrayPtr = reinterpret_cast<Complex*>(arrayMemory);// 在预分配内存上构造多个对象for (int i = 0; i < arraySize; ++i) {new(&arrayPtr[i]) Complex(i * 1.0, i * 2.0);}// 使用数组中的对象for (int i = 0; i < arraySize; ++i) {arrayPtr[i].display();}// 显式调用每个对象的析构函数for (int i = arraySize - 1; i >= 0; --i) {arrayPtr[i].~Complex();}return 0;
}
Placement new的主要用途:
- 内存池实现:在预先分配的内存池中构造对象
- 嵌入式系统:在特定内存位置构造对象
- 内存映射文件:在内存映射区域构造对象
- 性能优化:避免额外的内存分配开销
- 自定义内存对齐:在特定对齐的内存上构造对象
需要注意的是,使用placement new时,我们需要手动调用析构函数,并且不使用delete来释放内存(因为内存不是通过new分配的)。
内存池技术
内存池是一种常用的自定义内存管理技术,它通过预先分配大块内存,然后按需分配小块,减少系统调用次数,提高性能。
固定大小对象的内存池
下面是一个用于管理固定大小对象的简单内存池实现:
#include <iostream>
#include <vector>
#include <cassert>template <typename T, size_t BlockSize = 4096>
class FixedSizeAllocator {
private:// 内存块结构struct Block {char data[BlockSize];Block* next;};// 空闲链表的节点结构union Chunk {T object;Chunk* next;};Block* currentBlock;Chunk* freeList;size_t chunksPerBlock;size_t allocatedChunks;public:FixedSizeAllocator() : currentBlock(nullptr), freeList(nullptr), chunksPerBlock(0), allocatedChunks(0) {// 计算每个块中可以容纳的对象数量chunksPerBlock = BlockSize / sizeof(Chunk);assert(chunksPerBlock > 0 && "Block size too small");}~FixedSizeAllocator() {// 释放所有内存块while (currentBlock) {Block* next = currentBlock->next;delete currentBlock;currentBlock = next;}}// 分配内存T* allocate() {// 如果没有空闲块,分配新的内存块if (!freeList) {// 分配新块Block* newBlock = new Block;newBlock->next = currentBlock;currentBlock = newBlock;// 将新块分成多个Chunk并添加到空闲列表Chunk* chunk = reinterpret_cast<Chunk*>(currentBlock->data);freeList = chunk;// 构建空闲链表for (size_t i = 0; i < chunksPerBlock - 1; ++i) {chunk->next = chunk + 1;chunk = chunk->next;}chunk->next = nullptr;}// 从空闲列表中获取一个ChunkChunk* allocatedChunk = freeList;freeList = freeList->next;allocatedChunks++;// 返回指向对象空间的指针return reinterpret_cast<T*>(allocatedChunk);}// 释放内存void deallocate(T* ptr) {if (!ptr) return;// 将指针转换为Chunk并添加到空闲列表的头部Chunk* chunk = reinterpret_cast<Chunk*>(ptr);chunk->next = freeList;freeList = chunk;allocatedChunks--;}// 统计信息size_t getAllocatedChunks() const {return allocatedChunks;}
};// 使用内存池的类
class PooledObject {
private:int data;static FixedSizeAllocator<PooledObject> allocator;public:PooledObject(int d = 0) : data(d) {std::cout << "PooledObject constructor: " << data << std::endl;}~PooledObject() {std::cout << "PooledObject destructor: " << data << std::endl;}// 重载new和delete使用内存池void* operator new(std::size_t size) {assert(size == sizeof(PooledObject));return allocator.allocate();}void operator delete(void* ptr) {if (ptr) {allocator.deallocate(static_cast<PooledObject*>(ptr));}}// 显示分配统计static size_t getAllocatedCount() {return allocator.getAllocatedChunks();}void setData(int d) {data = d;}int getData() const {return data;}
};// 静态成员初始化
FixedSizeAllocator<PooledObject> PooledObject::allocator;int main() {std::cout << "Initial allocated count: " << PooledObject::getAllocatedCount() << std::endl;// 创建一些对象std::vector<PooledObject*> objects;for (int i = 0; i < 10; ++i) {objects.push_back(new PooledObject(i));}std::cout << "After creation, allocated count: " << PooledObject::getAllocatedCount() << std::endl;// 删除一些对象for (int i = 0; i < 5; ++i) {delete objects[i];objects[i] = nullptr;}std::cout << "After partial deletion, allocated count: " << PooledObject::getAllocatedCount() << std::endl;// 创建更多对象for (int i = 0; i < 3; ++i) {objects.push_back(new PooledObject(i + 100));}std::cout << "After more creation, allocated count: " << PooledObject::getAllocatedCount() << std::endl;// 清理剩余对象for (auto obj : objects) {delete obj;}std::cout << "After all deletions, allocated count: " << PooledObject::getAllocatedCount() << std::endl;return 0;
}
这是一个简单的固定大小对象内存池实现,适用于大量创建和销毁相同大小对象的场景,如游戏中的粒子系统、图形渲染中的顶点等。通过内存池,我们可以显著减少内存分配的系统调用次数,从而提高性能。
简单通用内存池
下面是一个简单的通用内存池实现,可以用于分配不同大小的内存块:
#include <iostream>
#include <vector>
#include <map>
#include <cstddef>
#include <cassert>class SimpleMemoryPool {
private:struct MemoryBlock {char* memory;size_t size;size_t used;MemoryBlock(size_t blockSize) : size(blockSize), used(0) {memory = new char[blockSize];}~MemoryBlock() {delete[] memory;}// 尝试分配内存void* allocate(size_t bytes, size_t alignment) {// 计算对齐调整size_t adjustment = alignment - (reinterpret_cast<uintptr_t>(memory + used) & (alignment - 1));if (adjustment == alignment) adjustment = 0;// 检查是否有足够空间if (used + adjustment + bytes > size) {return nullptr; // 块中没有足够空间}// 分配内存void* result = memory + used + adjustment;used += bytes + adjustment;return result;}};std::vector<MemoryBlock*> blocks;std::map<void*, MemoryBlock*> allocations; // 跟踪指针到块的映射size_t blockSize; // 默认块大小size_t totalAllocated;size_t totalUsed;public:SimpleMemoryPool(size_t defaultBlockSize = 4096): blockSize(defaultBlockSize), totalAllocated(0), totalUsed(0) {// 初始化第一个块addBlock(blockSize);}~SimpleMemoryPool() {// 释放所有块for (auto block : blocks) {delete block;}blocks.clear();allocations.clear();}// 分配内存void* allocate(size_t bytes, size_t alignment = 8) {if (bytes == 0) return nullptr;// 对齐大小bytes = (bytes + alignment - 1) & ~(alignment - 1);// 尝试在现有块中分配for (auto block : blocks) {void* memory = block->allocate(bytes, alignment);if (memory) {allocations[memory] = block;totalUsed += bytes;return memory;}}// 无可用空间,创建新块size_t newBlockSize = std::max(blockSize, bytes * 2);MemoryBlock* newBlock = addBlock(newBlockSize);// 尝试在新块中分配void* memory = newBlock->allocate(bytes, alignment);if (memory) {allocations[memory] = newBlock;totalUsed += bytes;return memory;}// 分配失败return nullptr;}// 释放内存(实际上在这个简单实现中不会释放,只是记录)void deallocate(void* ptr) {if (!ptr) return;allocations.erase(ptr);}// 打印池状态void printStatus() const {std::cout << "Memory Pool Status:" << std::endl;std::cout << "Number of blocks: " << blocks.size() << std::endl;std::cout << "Total allocated: " << totalAllocated << " bytes" << std::endl;std::cout << "Total used: " << totalUsed << " bytes" << std::endl;std::cout << "Utilization: " << (totalAllocated > 0 ? (double)totalUsed / totalAllocated * 100.0 : 0)<< "%" << std::endl;}private:// 添加新块MemoryBlock* addBlock(size_t size) {MemoryBlock* block = new MemoryBlock(size);blocks.push_back(block);totalAllocated += size;return block;}
};void simpleMemoryPoolDemo() {SimpleMemoryPool pool;// 分配不同大小的内存void* p1 = pool.allocate(128);void* p2 = pool.allocate(256);void* p3 = pool.allocate(512);void* p4 = pool.allocate(1024);// 检查指针std::cout << "Pointers:" << std::endl;std::cout << "p1: " << p1 << std::endl;std::cout << "p2: " << p2 << std::endl;std::cout << "p3: " << p3 << std::endl;std::cout << "p4: " << p4 << std::endl;// 打印池状态pool.printStatus();// 写入一些数据char* cp1 = static_cast<char*>(p1);for (int i = 0; i < 128; ++i) {cp1[i] = i % 256;}// 读取数据std::cout << "First few bytes of p1: ";for (int i = 0; i < 10; ++i) {std::cout << static_cast<int>(cp1[i]) << " ";}std::cout << std::endl;// 释放一些内存(实际上只是记录,不会真的释放)pool.deallocate(p2);pool.deallocate(p4);// 再次分配void* p5 = pool.allocate(200);std::cout << "p5: " << p5 << std::endl;pool.printStatus();
}int main() {simpleMemoryPoolDemo();return 0;
}
这个简单的内存池实现了一种"只增不减"的策略,即不会释放块中未使用的内存,这样的设计在某些场景下是合理的,尤其是对于内存使用模式比较固定或短暂的应用程序。
内存分析与调试工具
除了自定义内存管理,我们还可以构建内存分析与调试工具,帮助我们追踪内存使用、发现内存泄漏等问题。下面是一个简单的内存追踪器:
#include <iostream>
#include <unordered_map>
#include <string>
#include <mutex>
#include <cstdlib>
#include <vector>// 简单的内存分配跟踪器
class MemoryTracker {
private:struct AllocationInfo {void* address;size_t size;const char* file;int line;bool isArray;AllocationInfo(void* addr, size_t s, const char* f, int l, bool arr): address(addr), size(s), file(f), line(l), isArray(arr) {}};std::unordered_map<void*, AllocationInfo> allocations;std::mutex mutex;size_t totalAllocated;size_t currentAllocated;size_t peakAllocated;size_t allocationCount;// 单例实例static MemoryTracker& getInstance() {static MemoryTracker instance;return instance;}// 私有构造函数(单例模式)MemoryTracker() : totalAllocated(0), currentAllocated(0), peakAllocated(0), allocationCount(0) {}public:// 记录分配static void recordAllocation(void* address, size_t size, const char* file, int line, bool isArray) {std::lock_guard<std::mutex> lock(getInstance().mutex);getInstance().allocations.emplace(address, AllocationInfo(address, size, file, line, isArray));getInstance().totalAllocated += size;getInstance().currentAllocated += size;getInstance().allocationCount++;if (getInstance().currentAllocated > getInstance().peakAllocated) {getInstance().peakAllocated = getInstance().currentAllocated;}}// 记录释放static void recordDeallocation(void* address, bool isArray) {std::lock_guard<std::mutex> lock(getInstance().mutex);auto it = getInstance().allocations.find(address);if (it != getInstance().allocations.end()) {// 检查数组/非数组匹配if (it->second.isArray != isArray) {std::cerr << "Error: Mismatch between new/delete and new[]/delete[]!" << std::endl;std::cerr << "Allocation: " << (it->second.isArray ? "array" : "non-array")<< " at " << it->second.file << ":" << it->second.line << std::endl;std::cerr << "Deallocation: " << (isArray ? "array" : "non-array") << std::endl;}getInstance().currentAllocated -= it->second.size;getInstance().allocations.erase(it);} else {std::cerr << "Error: Attempting to free unallocated memory at " << address << std::endl;}}// 打印内存使用统计static void printStatistics() {std::lock_guard<std::mutex> lock(getInstance().mutex);std::cout << "\n=== Memory Usage Statistics ===" << std::endl;std::cout << "Total Allocated: " << getInstance().totalAllocated << " bytes" << std::endl;std::cout << "Current Allocated: " << getInstance().currentAllocated << " bytes" << std::endl;std::cout << "Peak Allocated: " << getInstance().peakAllocated << " bytes" << std::endl;std::cout << "Allocation Count: " << getInstance().allocationCount << " bytes" << std::endl;std::cout << "Currently Active Allocations: " << getInstance().allocations.size() << std::endl;}// 打印当前内存泄漏static void reportLeaks() {std::lock_guard<std::mutex> lock(getInstance().mutex);if (getInstance().allocations.empty()) {std::cout << "\nNo memory leaks detected." << std::endl;return;}std::cout << "\n=== Memory Leaks Detected ===" << std::endl;std::cout << "Number of leaks: " << getInstance().allocations.size() << std::endl;std::cout << "Total leaked memory: " << getInstance().currentAllocated << " bytes" << std::endl;// 按文件和行号组织泄漏std::unordered_map<std::string, std::vector<AllocationInfo>> leaksByLocation;for (const auto& pair : getInstance().allocations) {const AllocationInfo& info = pair.second;std::string location = std::string(info.file) + ":" + std::to_string(info.line);leaksByLocation[location].push_back(info);}// 打印泄漏信息for (const auto& pair : leaksByLocation) {const std::string& location = pair.first;const std::vector<AllocationInfo>& leaks = pair.second;size_t totalSize = 0;for (const auto& leak : leaks) {totalSize += leak.size;}std::cout << "\nLocation: " << location << std::endl;std::cout << "Leaks: " << leaks.size() << ", Total size: " << totalSize << " bytes" << std::endl;// 打印前几个泄漏的详细信息const size_t maxDetailsCount = 5;for (size_t i = 0; i < std::min(leaks.size(), maxDetailsCount); ++i) {const auto& leak = leaks[i];std::cout << " - Address: " << leak.address<< ", Size: " << leak.size << " bytes"<< ", Type: " << (leak.isArray ? "array" : "non-array") << std::endl;}if (leaks.size() > maxDetailsCount) {std::cout << " ... and " << (leaks.size() - maxDetailsCount) << " more" << std::endl;}}}
};// 重载全局new/delete运算符
void* operator new(std::size_t size, const char* file, int line) {void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();MemoryTracker::recordAllocation(ptr, size, file, line, false);return ptr;
}void* operator new[](std::size_t size, const char* file, int line) {void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();MemoryTracker::recordAllocation(ptr, size, file, line, true);return ptr;
}// 标准版本委托给上面的版本
void* operator new(std::size_t size) {return operator new(size, "Unknown", 0);
}void* operator new[](std::size_t size) {return operator new[](size, "Unknown", 0);
}void operator delete(void* ptr) noexcept {if (ptr) {MemoryTracker::recordDeallocation(ptr, false);std::free(ptr);}
}void operator delete[](void* ptr) noexcept {if (ptr) {MemoryTracker::recordDeallocation(ptr, true);std::free(ptr);}
}// 带位置参数的删除版本
void operator delete(void* ptr, const char*, int) noexcept {operator delete(ptr);
}void operator delete[](void* ptr, const char*, int) noexcept {operator delete[](ptr);
}// 宏定义简化使用
#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEWclass AutoCleanup {
public:~AutoCleanup() {MemoryTracker::printStatistics();MemoryTracker::reportLeaks();}
};void memoryLeakTest() {// 分配一些内存int* p1 = new int(42);int* p2 = new int[10];char* p3 = new char[100];// 模拟内存泄漏 - 不释放p2delete p1;delete[] p3;// 故意错误使用delete// int* p4 = new int[5];// delete p4; // 应该使用delete[]
}int main() {AutoCleanup cleanup; // 程序结束时自动报告泄漏memoryLeakTest();return 0;
}
这个内存跟踪器可以帮助我们找出内存泄漏和不正确的内存操作,是一个功能强大的调试工具。使用宏将new
替换为带有文件名和行号的版本,可以精确定位内存泄漏的位置。
小结
在本文(上篇)中,我们详细介绍了C++中自定义内存管理的基础知识、核心技术以及简单实现。我们讨论了:
- 重载全局和类特定的
new
/delete
运算符 - 使用placement new在预分配内存上构造对象
- 实现固定大小对象的内存池
- 构建简单的通用内存池
- 创建内存分析和调试工具
这些技术为我们提供了更灵活、高效的内存管理方式,特别是在性能关键的应用程序中。
在下篇文章中,我们将继续探讨更高级的内存管理技术,包括:
- 更复杂的内存池实现
- 针对STL容器的自定义分配器
- 自定义内存管理在游戏开发、嵌入式系统和高性能计算中的应用
- 多线程环境下的内存管理
- 自定义内存管理的最佳实践和性能分析
这是我C++学习之旅系列的第二十一篇技术文章。查看完整系列目录了解更多内容。
相关文章:
C++学习:六个月从基础到就业——内存管理:自定义内存管理(上篇)
C学习:六个月从基础到就业——内存管理:自定义内存管理(上篇) 本文是我C学习之旅系列的第二十一篇技术文章,也是第二阶段"C进阶特性"的第六篇,主要介绍C中的自定义内存管理技术(上篇&…...
大模型基础
1、提示词 典型构成: - **角色**:给 AI 定义一个最匹配任务的角色,比如:「你是一位软件工程师」「你是一位小学数学老师」 - **指示**:对任务进行描述 - **上下文**:给出与任务相关的其它背景信息&#x…...
TDengine 存储引擎设计
简介 TDengine 的核心竞争力在于其卓越的写入和查询性能。相较于传统的通用型数据库,TDengine 在诞生之初便专注于深入挖掘时序数据场景的独特性。它充分利用了时序数据的时间有序性、连续性和高并发特点,自主研发了一套专为时序数据定制的写入及存储算…...
C++回溯算法详解
文章目录 引言第一题1.1 题目解析1.2 解题思路回溯解法队列解法 1.3 解题代码回溯解法队列解法 引言 回溯算法是一种通过深度优先搜索系统性地遍历问题解空间的算法。它的核心思想是"试错":逐步构建候选解,当发现当前选择无法得到有效解时&am…...
前端Javascript模块化 CommonJS与ES Module区别
一、模块化规范的演进历程 IIFE(立即执行函数)阶段 早期通过立即执行函数实现模块化,利用函数作用域隔离变量,解决全局命名冲突问题。例如通过(function(){})()包裹代码,形成独立作用域。 CommonJS(Node.js)阶段 CommonJS规范以同步加载为核心,通过require和module.exp…...
问题 | RAIM + LSTM 你怎么看???
github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 RAIM LSTM import numpy as np import tensorflow as tf from tensorflow.keras.layers import LSTM, Dense# RAIM-LSTM 融合模型 class RAIM_LSTM(tf.keras.Model):d…...
进程与线程:03 用户级线程
多进程与操作系统基础 上一个内容我们讲了多进程图像,强调多进程图像是操作系统最核心的图像。我们还通过Windows任务管理器,实际观察了操作系统里的进程。 进程是操作系统的核心内容,管理好多个进程,就能管理好操作系统和CPU。…...
四种阻抗匹配的方式
一、串联端接方式 即靠近输出端的位置串联一个电阻。 要达到匹配效果,串联电阻和驱动端输出阻抗的总和应等于传输线的特征Z0 二、并联端接方式 并联端接又被称为终端匹配。 要达到阻抗匹配的要求,端接电阻应该和传输线的特征阻抗Z0相等。 三、AC并联端…...
WebRTC通信技术EasyRTC音视频实时通话安全巡检搭建低延迟、高可靠的智能巡检新体系
一、方案背景 在现代安防和工业领域,安全巡检是确保设施正常运行和保障人员安全的关键环节。传统的巡检方式往往依赖人工,效率低下且容易出现遗漏。随着技术的发展,实时通信技术EasyRTC为安全巡检提供了更加高效和智能化的解决方案。 二、方…...
使用json_repair修复大模型的json输出错误
json_repair 有些 LLM 在返回格式正确的 JSON 数据时会有些问题,有时会漏掉括号,有时会在数据中添加一些单词。不至于这种错误每次都要丢弃,再次生成太浪费时间了,因此能修复错误时还是要尽量修复。这就是 json_repair 的主要目的…...
聊透多线程编程-线程互斥与同步-12. C# Monitor类实现线程互斥
目录 一、什么是临界区? 二、Monitor类的用途 三、Monitor的基本用法 四、Monitor的工作原理 五、使用示例1-保护共享变量 解释: 六、使用示例2-线程间信号传递 解释: 七、注意事项 八、总结 在多线程编程中,线程之间的…...
鸿蒙系统的 “成长烦恼“:生态突围与技术迭代的双重挑战
一、应用生态:从 "有没有" 到 "好不好" 的漫长爬坡 作为一款诞生于中美科技博弈背景下的国产操作系统,鸿蒙(HarmonyOS)自 2019 年发布以来,已在设备装机量上取得突破 —— 截至 2023 年底…...
ESP8266_ESP32 Smartconfig一键配网功能
目录 SmartConfig一键配网基本原理设备绑定流程 ESP8266/ESP32 SmartConfig配网AT指令配置方式Arduino程序配置方式 总结 SmartConfig一键配网 SmartConfigTM 是由 TI 开发的配网技术,用于将新的 Wi-Fi 设备连接到 Wi-Fi 网络。它使用移动应用程序将无线网凭据从智…...
图解Agent2Agent(A2A)
🧠 向所有学习者致敬! “学习不是装满一桶水,而是点燃一把火。” —— 叶芝 我的博客主页: https://lizheng.blog.csdn.net 🌐 欢迎点击加入AI人工智能社区! 🚀 让我们一起努力,共创AI未来! 🚀 嘿,朋友们!今天咱们来聊聊 Agentic 应用背后的两大神器:A2A 和 …...
Kotlin基础(①)
open 关键字:打破 Kotlin 的“默认封闭”规则 // 基类必须加 open 才能被继承 open class Animal {// 方法也要加 open 才能被子类重写open fun makeSound() {println("Some sound")} }class Dog : Animal() {override fun makeSound() {println("W…...
Android Kotlin+Compose首个应用
本教程将创建一个简单的基于 Kotlin 语言的 APP,并使用 Compose 来管理 UI。 创建一个基于 Kotlin 的Android 应用 打开 Android Studio,选择New Project来创建一个应用,然后在Phone and Tablet选项卡,选择 Empty Activity&…...
《AI大模型应知应会100篇》第30篇:大模型进行数据分析的方法与局限:从实战到边界探索
大模型进行数据分析的方法与局限:从实战到边界探索 摘要 在金融分析师用自然语言询问季度财报趋势,电商平台通过对话生成用户画像的今天,大模型正在重塑数据分析的协作模式。本文通过实战代码与行业案例,揭示大模型如何成为数据…...
基于SSM+Vue的社群交流市场服务平台【提供源码+论文1.5W字+答辩PPT+项目部署】
作者简介:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容:🌟Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…...
Python Cookbook-6.7 有命名子项的元组
任务 Python 元组可以很方便地被用来将信息分组,但是访问每个子项都需要使用数字索引,所以这种用法有点不便。你希望能够创建一种可以通过名字属性访问的元组。 解决方案 工厂函数是生成符合要求的元组的子类的最简单方法: #若在2.4中可使用operator…...
软件功能测试和非功能测试有什么区别和联系?
软件测试是保障软件质量的核心环节,而软件功能测试和非功能测试作为测试领域的两大重要组成部分,承担着不同但又相互关联的职责。 软件功能测试指的是通过验证软件系统的各项功能是否按照需求规格说明书来正确实现,确保软件的功能和业务流程…...
Java Lambda表达式指南
一、Lambda表达式基础 1. 什么是Lambda表达式? 匿名函数:没有名称的函数函数式编程:可作为参数传递的代码块简洁语法:替代匿名内部类的更紧凑写法 2. 基本语法 (parameters) -> expression 或 (parameters) -> { statem…...
K8s使用LIRA插件更新安全组交互流程
在Kubernetes集群中,当使用Lira作为CNI(容器网络接口)插件,并且需要更新ConfigMap中的安全组()securityGroups字段)时,实际上你是在配置与Pod网络相关的高级选项。Lira作为一种支持P…...
利用TCP+多进程技术实现私聊信息
服务器: import socket from multiprocessing import Process from threading import Threaduser_dic {}def send_recv(client_conn, client_addr):while 1:# 接收客户端发送的消息res client_conn.recv(1024).decode("utf-8")print("客户端发送…...
【图问答】DeepSeek-VL 论文阅读笔记
《DeepSeek-VL: Towards Real-World Vision-Language Understanding》 1. 摘要/引言 基于图片问答(Visual Question Answering,VQA)的任务 2. 模型结构 和 三段式训练 1)使用 SigLIP 和 SAM 作为混合的vision encoder…...
深度学习预训练和微调
目录 1. 预训练(Pre-training)是什么? 2. 微调(Fine-tuning)是什么? 3. 预训练和微调的对象 4. 特征提取如何实现? 预训练阶段: 微调阶段: 5. 这样做的作用和意义 …...
面经-浏览器/网络/HTML/CSS
目录 1. http缓存机制 缓存机制 流程概述 2. 常见的http状态码 1xx(信息性状态码) 3xx(重定向状态码) 4xx(客户端错误状态码) 5xx(服务器错误状态码) 3. http和https的区别…...
轻松实现文件批量命名的实用工具
软件介绍 今天要给大家介绍一款超实用的批量文件重命名小工具,它完全可以称得上是同类产品的绝佳替代品。 软件特性 这小工具叫 MiniRenamer,身材十分苗条,大小还不到 300KB 呢。解压完后,不用任何复杂操作,直接就能…...
基于Redis实现高并发抢券系统的数据同步方案详解
在高并发抢券系统中,我们通常会将用户的抢券结果优先写入 Redis,以保证系统响应速度和并发处理能力。但数据的最终一致性要求我们必须将这些结果最终同步到 MySQL 的持久化库中。本文将详细介绍一种基于线程池 Redis Hash 扫描的异步数据同步方案&#…...
【Pandas】pandas DataFrame sub
Pandas2.2 DataFrame Binary operator functions 方法描述DataFrame.add(other)用于执行 DataFrame 与另一个对象(如 DataFrame、Series 或标量)的逐元素加法操作DataFrame.add(other[, axis, level, fill_value])用于执行 DataFrame 与另一个对象&…...
4.21总结
正式开始设计和实现前端页面 1.目标效果 2.今日实现内容 在前端编写了相应的store,api,utils文件,以便后续的组件复用 2.编写了相应的css文件...
VLA论文精读(十四)PointVLA: Injecting the 3D World into Vision-Language-Action Models
这篇论文瞄准的是2025年在arxiv上发布的一篇VLA领域论文。这篇文章最大的创新点在于将3D点云信息作为补充条件送入模型,而不是DP3一样只用纯3D数据从头训练模型,按照作者的说法这样可以在保留模型原有2D解释能力的同时添加了其3D能力,并且可以…...
BEVDet4D: Exploit Temporal Cues in Multi-camera 3D Object Detection
背景 对于现有的BEVDet方法,它对于速度的预测误差要高于基于点云的方法,对于像速度这种与时间有关的属性,仅靠单帧数据很难预测好。因此本文提出了BEVDet4D,旨在获取时间维度上的丰富信息。它是在BEVDet的基础上进行拓展,保留了之前帧的BEV特征,并将其进行空间对齐后与当…...
Java学习路线--自用--带链接
1.Java基础 黑马:黑马程序员Java零基础视频教程_下部 2.MySQL 尚硅谷:MySQL数据库入门到大牛,mysql安装到优化,百科全书级,全网天花板 3.Redis 黑马:黑马程序员Redis入门到实战教程,深度透…...
【锂电池容量特征提取】NASA数据集锂电池容量特征提取(Matlab完整源码)
目录 效果一览程序获取程序内容代码分享研究内容基于NASA数据集的锂电池容量特征提取方法研究摘要关键词 1. 引言1.1 研究背景1.2 研究意义1.3 研究目的 2. 文献综述2.1 锂电池容量特征提取相关理论基础2.2 国内外研究现状 3. NASA数据集介绍3.1 数据集来源与构成3.2 数据采集方…...
vue2使用markdown-it解析markdown文本
1.安装markdown-it npm instal markdown-it 2. 页面中引用 import MarkdownIt from markdown-it ...const mdRender MarkdownIt(); ...data {return {md: new MarkdownIt(),} } 3. html <p v-html"md.render(conetnt)" ></p>...
云服务器怎么选择防御最合适
用户问的是怎么选择云服务器的防御最合适。这个问题看起来是关于云安全方面的,尤其是如何配置防御措施来保护云服务器免受攻击。首先,我需要理解用户的需求可能是什么。他们可能是一个企业或者个人用户,正在考虑上云,但担心安全问…...
ubuntu20.04安装安装x11vnc服务基于gdm3或lightdm这两种主流的显示管理器。
前言:在服务端安装vnc服务,可以方便的远程操作服务器,而不用非要插上显示器才行。所以在服务器上安装vnc是很重要的。在ubuntu20中,默认的显示管理器已经变为gdm3,它可以带来与 GNOME 无缝衔接的体验,强调功…...
汽车动力转向器落锤冲击试验台
汽车动力转向器落锤冲击试验台依据标准:QC/T29096-1992《汽车转向器总成台架试验方法》;以工控机为控制核心,采用步进电机举升机构,高精度的光电编码器为位置反馈元件。能够自动完成落锤的起吊、精确的定位、释放、冲击过程的测量…...
Mybatis延迟加载、懒加载、二级缓存
DAY22.2 Java核心基础 Mybatis 延迟加载、懒加载 提高程序运行效率的技术 延迟加载,也叫惰性加载或者懒加载 延迟加载如何提升程序的运行效率? 持久层操作有一个原则:Java 程序和数据库交互频率越低越好 Java 程序每次和数据库进行交互…...
Linux网络编程 多进程UDP聊天室:共享内存与多进程间通信实战解析
知识点1【项目功能介绍】 今天我们写一个 UDP ,多进程与不同进程间通信的综合练习 我这里说一下 这个项目的功能: 1、群发(有设备个数的限制):发送数据,其他所有客户端都要受到数据 2、其他客户端 都 可…...
网络结构及安全科普
文章目录 终端联网网络硬件基础网络协议示例:用户访问网页 OSI七层模型网络攻击(Hack)网络攻击的主要类别(一)按攻击目标分类(二)按攻击技术分类 网络安全防御 典型攻击案例相关名词介绍网络连接…...
CAD文件如何导入BigemapPro
问题描述 在使用 BigemapPro 加载 CAD 文件的过程中,会出现两种不同的情况:部分文件能够被软件自动识别投影并顺利加载;而另一部分文件则无法自动识别投影,需要手动干预才能准确加载到影像上。下面为您详细介绍这两种情况的具体操…...
Spring-AOP分析
Spring分析-AOP 1.案例引入 在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我们了解到了IOC容器的创建过程,在文末也提到了AOP相关,但是没有作细致分析,这篇文章就结合示例&am…...
opencv 对图片的操作
对图片的操作 1.图片镜像旋转(cv2.flip())2 图像的矫正 1.图片镜像旋转(cv2.flip()) 图像的旋转是围绕一个特定点进行的,而图像的镜像旋转则是围绕坐标轴进行的。图像的镜像旋转分为水平翻转、垂直翻转、水平垂直翻转…...
Python第一周作业
Python第一周作业 文章目录 Python第一周作业 如何在命令行中创建一个名为venv的虚拟环境?请写出具体命令编写一段代码,判断变量x是否为偶数,如果是则返回"Even",否则返回"Odd"编写代码,使用分支结…...
jinjia2将后端传至前端的字典变量转换为JS变量
后端 country_dict {AE: .amazon.ae, AU: .amazon.com.au} 前端 const country_list JSON.parse({{ country_list | tojson | safe }});...
[渗透测试]渗透测试靶场docker搭建 — —全集
[渗透测试]渗透测试靶场docker搭建 — —全集 对于初学者来说,仅仅了解漏洞原理是不够的,还需要进行实操。对于公网上的服务我们肯定不能轻易验证某些漏洞,否则可能触犯法律。这是就需要用到靶场。 本文主要给大家介绍几种常见漏洞对应的靶场…...
二分查找、分块查找、冒泡排序、选择排序、插入排序、快速排序
二分查找/折半查找 前提条件:数组中的数据必须是有序的 核心逻辑:每次排除一半的查找范围 优点:提高查找效率 代码 public static int binarySearch(int[] arr, int num) {int start 0;int end arr.length - 1;while (start < end) {…...
【AI】SpringAI 第三弹:接入通用大模型平台
1.添加依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId> </dependency> 2.设置 yml 配置文件 在 application.yml 中添加 DeepSeek 的配置信息: spr…...
C++常用函数合集
万能头文件:#include<bits/stdc.h> 1. 输入输出流(I/O)函数 1.1cin 用于从标准输入流读取数据。 1.2cout 用于向标准输出流写入数据。 // 输入输出流(I/O)函数 #include <iostream> using namespace…...