一些C++中经常会接触的概念,包括中间件,数据结构封装等模块
了解原理,实现基本功能,实际运用的时候还需要根据具体需求进行扩展和优化
一般肯定直接用STL封装好的
- 内存池 Memory Pool
- 共享指针 Shared Pointer
- 函数封装 Function Encapsulation
- 双向链表容器 List
- 双端队列容器 Deque
- BST和AVL实现map
- 无序映射表 unordered_map
- 单例模式 Singleton
内存池 Memory Pool
内存池是一种预先分配一大块内存,然后按需分配小块内存的技术
内存池可以减少频繁的内存分配和释放操作从而提高性能
用一个栈实现:
classDiagram
class MemoryPool {
char* _pool
size_t _objectsize
size_t _totalsize
stack~void*~ _freelist
void* allocate()
void deallocate(void* ptr)
}
MemoryPool --* "1" char : 管理
MemoryPool --* "1" stack : 包含
class FreeListStack {
<>
void*类型
push(void* ptr)
pop() void*
top() void*
empty() bool
}
MemoryPool --> FreeListStack : 使用
class MemoryPool{
public:
MemoryPool(size_t objectsize, size_t totalsize);
~MemoryPool();
// 用户层api
void* allocate();
void deallocate(void* ptr);
private:
size_t _objectsize; // 单个元素大小
size_t _totalsize; // 预留的个数
char* _pool;
std::stack<void*> _freelist;
};
#include "MemoryPool.h"
MemoryPool::MemoryPool(size_t objectsize, size_t totalsize)
: _objectsize(objectsize), _totalsize(totalsize)
{
_pool = (char*)malloc(totalsize * objectsize);
if (_pool == nullptr){
throw std::bad_alloc();
}
// 初始化freelist
for (size_t i = 0; i < totalsize; ++i){
_freelist.push(_pool+ i * objectsize);
}
}
MemoryPool::~MemoryPool()
{
free(_pool);
}
void * MemoryPool::allocate()
{
if (_freelist.empty()){
throw std::bad_alloc();
// return nullptr;
}
void* p = _freelist.top();
_freelist.pop();
return p;
}
void MemoryPool::deallocate(void * ptr)
{
_freelist.push(ptr);
return;
}
flowchart TD
A[开始] --> B{根据类型创建内存池}
B -->|分配| C[调用 allocate]
B -->|释放| D[调用 deallocate]
C --> E{_freelist为空?}
E -->|是| F[抛出 bad_alloc]
E -->|否| G[从栈顶取指针]
G --> H[返回指针]
D --> I[指针压入栈]
I --> J[返回]
F --> K[结束]
H --> K
J --> K
#include "../inc/test.h"
#include "../inc/MemoryPool.h"
#include <cstring>
class Student
{
public:
char *_name;
int _age;
public:
Student(const char *name, int age) : _age(age) {
_name = new char[strlen(name) + 1];
strcpy(_name, name);
}
~Student() {
delete[] _name;
}
};
int main()
{
try
{
MemoryPool pool(sizeof(Student), 3);
void *mem1 = pool.allocate();
void *mem2 = pool.allocate();
void *mem3 = pool.allocate();
// 定位new表达式,不分配新的内存,直接在提供的内存地址上构造对象
Student *stu1 = new (mem1) Student("Tom", 20);
Student *stu2 = new (mem2) Student("Sam", 22);
Student *stu3 = new (mem3) Student("Tim", 24);
std::cout << "stu1:" << stu1->_name << " " << stu1->_age << std::endl;
std::cout << "stu2:" << stu2->_name << " " << stu2->_age << std::endl;
std::cout << "stu2:" << stu3->_name << " " << stu3->_age << std::endl;
// 显式调用析构
stu1->~Student();
stu2->~Student();
stu3->~Student();
// 池子回收
pool.deallocate(mem1);
pool.deallocate(mem2);
pool.deallocate(mem3);
}
catch (const std::bad_alloc &e)
{
std::cerr << "Memory allocation error: " << e.what() << std::endl;
return 1;
}
return 0;
}
如果是多线程的情况,需要在用户应用层进行线程安全的一系列操作
shared_ptr(共享智能指针)是C++标准库提供的一种智能指针,用于自动管理动态分配的内存资源。它采用引用计数机制,记录有多少个shared_ptr指向同一个对象。当最后一个shared_ptr被销毁或重置时,其所管理的对象会被自动删除,从而有效防止内存泄漏。shared_ptr支持拷贝和赋值操作,允许多个指针共享同一资源。使用shared_ptr能简化资源生命周期管理,是现代C++中避免原始指针风险的重要工具。
这里用一个struct块来模拟引用计数,实际上也可以直接用一个int*来替代。
classDiagram
class MySharedPtr~T~ {
T* ptr
controlblock* ctrl_blk
void Release()
int use_count() const
T* get() const
void reset(T* p)
}
class controlblock {
int count_ref
controlblock()
}
MySharedPtr --o controlblock : 拥有
MySharedPtr --> T : 管理
#ifndef SHARED_PTR_H
#define SHARED_PTR_H
#include <atomic>
struct controlblock
{
std::atomic<int> count_ref;
controlblock() : count_ref(1) {}
};
template <typename T>
class MySharedPtr
{
private:
T *ptr;
controlblock *ctrl_blk;
/**
* @brief 内部管理计数,当计数降到0时释放内存
*/
void Release()
{
if (ctrl_blk)
{
--ctrl_blk->count_ref;
if (ctrl_blk->count_ref == 0)
{
delete ptr;
delete ctrl_blk;
ptr = nullptr;
ctrl_blk = nullptr;
}
}
}
public:
/**
* @brief 带参数构造
* @param p 指向需要管理的对象的指针
* @return MySharedPtr<T>
* @note 用explicit防止隐式转换
*/
explicit MySharedPtr(T *p = nullptr)
{
{
if (p != nullptr)
{
ptr = p;
ctrl_blk = new controlblock();
}
else
{
ptr = nullptr;
ctrl_blk = nullptr;
}
}
}
/**
* @brief 默认构造
* @param p 指向需要管理的对象的指针
* @return MySharedPtr<T>
*/
MySharedPtr() : ptr(nullptr), ctrl_blk(nullptr) {}
/**
* @brief 析构
*/
~MySharedPtr()
{
if (ptr)
{
Release();
}
}
/**
* @brief 拷贝构造
* @param s 拷贝对象
* @return MySharedPtr<T>
*/
MySharedPtr(const MySharedPtr &s) : ptr(s.ptr), ctrl_blk(s.ctrl_blk)
{
if (ctrl_blk)
ctrl_blk->count_ref++;
}
/**
* @brief 拷贝赋值
* @param s 拷贝对象
* @return MySharedPtr<T>
*/
MySharedPtr &operator=(const MySharedPtr &s)
{
if (&s == this)
return *this;
// 自己引用计数减少
Release();
ptr = s.ptr;
ctrl_blk = s.ctrl_blk;
// 拷贝的引用计数增加
if (ctrl_blk)
ctrl_blk->count_ref++;
}
/**
* @brief 移动构造
* @param s 移动对象
* @return MySharedPtr<T>
*/
MySharedPtr(MySharedPtr &&s) : ptr(s.ptr), ctrl_blk(s.ctrl_blk)
{
s.ptr = nullptr;
s.ctrl_blk = nullptr;
}
/**
* @brief 移动赋值
* @param s 移动对象
* @return MySharedPtr<T>
*/
MySharedPtr &operator=(MySharedPtr &&s)
{
if (&s == this)
return *this;
ptr = s.ptr;
ctrl_blk = s.ctrl_blk;
s.ptr = nullptr;
s.ctrl_blk = nullptr;
}
public:
/**
* @brief ->重载,把类当指针用,方便链式调用
* @return T*
*/
T *operator->() const { return ptr; }
/**
* @brief *重载,取出引用对象
* @return T&
*/
T &operator*() const { return *ptr; }
/**
* @brief 返回引用计数
* @return 引用计数
*/
int use_count() const { return ctrl_blk->count_ref; }
/**
* @brief 返回裸指针
* @return 引用计数
*/
T *get() const { return ptr; }
/**
* @brief 重定向
*/
void reset(T * p){
Release();
ptr = p;
if (p){
ctrl_blk = new controlblock();
}else{
ctrl_blk = nullptr;
}
}
};
#endif
函数封装 Function Encapsulation
四种函数封装方式
- 函数指针
- 仿函数(函数对象)
- std::function
- lambda表达式
- 性能敏感场景:优先考虑函数指针或模板仿函数
- 回调系统:使用
std::function统一接口 - 局部算法:lambda表达式最合适
- 兼容C代码:必须使用函数指针
#ifndef FUNCTION_BIND_H
#define FUNCTION_BIND_H
#include <iostream>
#include <functional>
// 1. 函数指针 - 最基础的函数封装方式
// 优点:简单、高效、零开销
// 缺点:无法捕获上下文、无法处理带状态的函数对象
int (*funcptr)(int, int); // 声明函数指针类型
int add(int a, int b)
{
return a + b;
}
/*
使用示例:
int main() {
funcptr = add; // 指向add函数
int result = funcptr(1, 2); // 调用函数
std::cout << result << std::endl; // 输出: 3
}
*/
// 2. 仿函数(函数对象) - 类/结构体重载operator()
// 优点:可以携带状态、支持模板编程
// 缺点:需要定义完整的类、代码相对冗长
struct adder
{
int to_add;
adder(int value) : to_add(value) {} // 构造函数初始化状态
int operator()(int value) // 重载函数调用运算符
{
return to_add + value;
}
};
/*
使用示例:
int main() {
adder add_five(5); // 创建带状态的函数对象
int result = add_five(10); // 调用operator()
std::cout << result << std::endl; // 输出: 15
}
*/
// 3. std::function - C++11引入的通用函数包装器
// 优点:类型安全、统一接口、可包装任何可调用对象
// 缺点:有一定的运行时开销(类型擦除)
std::function<int(int, int)> func1 = add; // 包装函数指针
std::function<int(int)> func2 = adder(5); // 包装函数对象
std::function<int(int, int)> func3 = // 包装lambda表达式
[](int a, int b) { return a * b; };
// 4. Lambda表达式 - C++11引入的匿名函数
// 优点:语法简洁、可捕获上下文、支持多种捕获模式
// 缺点:捕获复杂时可能降低可读性
auto lambda1 = [](int a, int b) { return a + b; }; // 无捕获
int x = 10;
auto lambda2 = [x](int y) { return x + y; }; // 值捕获
auto lambda3 = [&x](int y) { x += y; return x; }; // 引用捕获
auto lambda4 = [=](int y) { return x + y; }; // 隐式值捕获全部
auto lambda5 = [&](int y) { x += y; return x; }; // 隐式引用捕获全部
#endif
双向链表容器 List
有节点定义,迭代器类和容器类
#ifndef MYLIST_H
#define MYLIST_H
#include <iostream>
#include <cassert>
template <typename T>
struct ListNode
{
T data;
ListNode *pre;
ListNode *next;
ListNode(const T &value) : data(value), pre(nullptr), next(nullptr) {}
};
template <typename T>
class Myiterator
{
public:
using self_type = Myiterator<T>;
using value_type = T;
using reference = T &;
using pointer = T *;
Myiterator(ListNode<T> *ptr = nullptr) : node_ptr(ptr) {}
reference operator*() const { return node_ptr->data; }
pointer operator->() const { return &(node_ptr->data); }
// ++it
self_type &operator++()
{
if (node_ptr != nullptr)
{
node_ptr = node_ptr->next;
}
return *this;
}
// it++
self_type operator++(int)
{
self_type tmp = *this;
if (node_ptr != nullptr)
{
node_ptr = node_ptr->next;
}
return tmp;
}
self_type &operator--()
{
if (node_ptr != nullptr)
{
node_ptr = node_ptr->pre;
}
return *this;
}
self_type operator--(int)
{
self_type tmp = *this;
--(*this);
return tmp;
}
bool operator==(const self_type &other) const
{
return (other.node_ptr == node_ptr);
}
bool operator!=(const self_type &other) const
{
return (other.node_ptr != node_ptr);
}
private:
/* data */
ListNode<T> *node_ptr;
friend class MyList<T>;
};
template <typename T>
class MyList
{
public:
using iterator = Myiterator<T>;
private:
ListNode<T> *head;
ListNode<T> *tail;
public:
MyList()
{
head = new ListNode<T>();
tail = new ListNode<T>();
head->next = tail;
tail->pre = head;
}
~MyList()
{
clear();
delete head;
delete tail;
}
MyList(const MyList &other) = delete;
MyList &operator=(const MyList &other) = delete;
public:
iterator insert(iterator pos, const T &value)
{
ListNode<T> *node = pos.node_ptr;
ListNode<T> *newnode = new ListNode<T>(value);
ListNode<T> *temp_pre = node->pre;
newnode->next = node;
newnode->pre = temp_pre;
temp_pre->next = newnode;
node->pre = newnode;
return iterator(newnode);
}
iterator erase(iterator pos)
{
ListNode<T> *node = pos.node_ptr;
// 不能删除头尾节点
if (node == head || node == tail)
{
return iterator(tail);
}
node->pre->next = node->next;
node->next->pre = node->pre;
ListNode<T> *nextnode = node->next;
delete node;
return iterator(nextnode);
}
void remove(const T& target){
for (iterator it = begin(); it != end();){
if (*it == target) it = erase(it);
else it++;
}
}
void clear()
{
ListNode<T> *cur = head->next;
while (cur != tail)
{
ListNode<T> *next = cur->next;
delete cur;
cur = next;
}
head->next = tail;
tail->pre = head;
}
bool isempty() const
{
return (head->next == tail);
}
public:
iterator begin()
{
return iterator(head->next);
}
iterator end()
{
return iterator(tail);
}
public:
void push_back(const T &value)
{
insert(end(), value);
}
void push_front(const T &value)
{
insert(begin(), value);
}
void pop_front()
{
if (!isempty())
erase(begin());
}
void pop_back()
{
if (!isempty())
{
iterator tmp = end();
--tmp;
erase(tmp);
}
}
public:
T &front()
{
assert(!isempty() && "Cannot access front of an empty list");
return head->next->data;
}
T &back()
{
assert(!isempty() && "Cannot access back of an empty list");
return tail->pre->data;
}
public:
size_t size() const {
size_t count = 0;
for (iterator it = begin(); it != end(); it++){
count++;
}
return count;
}
};
#endif
双端队列容器 Deque
deque 通常由一系列固定大小的数组块组成,这些块通过一个中央数组进行管理
整个结构使得在两端扩展的时候不需要再重新分配整个容器的内存
简化版:环形缓冲区deque
支持随机访问元素
支持在两端频繁插入和删除元素
#ifndef MYDEQUE_H
#define MYDEQUE_H
#include <iostream>
template <typename T>
class MyDeque
{
private:
T *buffer; // 环形缓冲区
size_t capacity; // 缓冲区容量
size_t count; // 当前元素数量
size_t front_idx; // 前端索引
size_t back_idx; // 下一个插入的位置(即最后一个元素的下一个位置)
public:
MyDeque(size_t initial_capacity = 10) : capacity(initial_capacity),
count(0), front_idx(0), back_idx(0)
{
// 调用T的无参构造
buffer = new T[capacity]();
}
~MyDeque()
{
delete[] buffer;
}
bool isempty() const
{
return count == 0;
}
size_t size() const
{
return count;
}
public:
void resize(size_t new_capacity)
{
T *new_buffer = new T[new_capacity]();
for (size_t i = 0; i < count; i++)
{
new_buffer[i] = buffer[(front_idx + i) % capacity];
}
delete[] buffer;
buffer = new_buffer;
front_idx = 0;
back_idx = count;
capacity = new_capacity;
}
void push_front(const T &value)
{
if (count == capacity)
{
resize(capacity * 2);
}
// 更新front_idx与插入值, 环形逻辑
front_idx = (front_idx - 1 + capacity) % capacity;
buffer[front_idx] = value;
count++;
}
void push_back(const T &value)
{
if (count == capacity)
{
resize(capacity * 2);
}
// 先赋值给当前位置
buffer[back_idx] = value;
// 再移动 back_idx 到下一个位置
back_idx = (back_idx + 1) % capacity;
count++;
}
void pop_front()
{
if (isempty())
{
throw std::out_of_range("Deque is empty. ");
}
front_idx = (front_idx + 1) % capacity;
count--;
}
void pop_back()
{
if (isempty())
{
throw std::out_of_range("Deque is empty. ");
}
back_idx = back_idx == 0 ? capacity - 1 : back_idx - 1;
count--;
}
const T &front() const
{
if (isempty())
{
throw std::out_of_range("Deque is empty. ");
}
return buffer[front_idx];
}
const T &back() const
{
if (isempty())
{
throw std::out_of_range("Deque is empty. ");
}
int last_idx = back_idx == 0 ? capacity - 1 : back_idx - 1;
return buffer[last_idx];
}
// 迭代器
class Myiterator
{
public:
using self_type = MyDeque<T>;
using value_type = T;
using reference = T &;
using pointer = T *;
private:
MyDeque<T> *_deque;
size_t pos;
public:
Myiterator(self_type *ptr = nullptr, size_t position) : _deque(ptr), pos(position) {}
reference operator*() const {
return _deque->buffer[(_deque->front_idx + pos) % _deque->capacity];
}
pointer operator->() const {
return &(_deque->buffer[(_deque->front_idx + pos) % _deque->capacity]);
}
// 前置++
Myiterator &operator++() {
pos++;
return *this;
}
// 后置++
Myiterator operator++(int) {
Myiterator tmp = *this;
pos++;
return tmp;
}
bool operator==(const Myiterator &other) const {
return _deque == other._deque && pos == other.pos;
}
bool operator!=(const Myiterator &other) const {
return !(*this == other);
}
};
MyDeque begin(){
return Myiterator(this, 0);
}
};
#endif
BST和AVL实现map
stl中的map底层是用红黑树实现的,红黑树的插入删除操作相对复杂,这里用普通的二叉搜索树和自平衡二叉搜索树实现一个map。map 是一种键值对数据结构,在许多算法和系统设计中都有广泛的应用。通过封装不同的树结构,map 提供了高效的元素查找、插入和删除操作。
1. 基于二叉搜索树 (BST) 的实现
二叉搜索树(BST)是一种特殊的二叉树,其中每个节点的值都大于左子树的所有节点,并且小于右子树的所有节点。这样的性质使得二叉搜索树能够支持高效的查找、插入和删除操作。在最理想的情况下,BST 能够以对数时间复杂度 O(log n) 进行这些操作。然而,若树不平衡,最坏的情况会退化为链表,导致操作时间复杂度为 O(n)。
#ifndef MYMAP_H
#define MYMAP_H
/*
BST 实现map (不同于std::map,std::map是基于平衡二叉搜索树,
比如红黑树实现的)
这里使用普通的二叉搜索树,非自动平衡的二叉搜索树
*/
#include <iostream>
#include <utility>
#include <exception>
#include <stack>
template <typename Key, typename T>
struct TreeNode
{
std::pair<Key, T> data;
TreeNode *left;
TreeNode *right;
TreeNode *parent;
TreeNode(const Key &key, const T &value, TreeNode *parentnode = nullptr)
: data(std::make_pair(key, value)), left(nullptr), right(nullptr), parent(parentnode) {}
};
template <typename Key, typename T>
class Mymap
{
private:
// 只保存根节点
TreeNode<Key, T> *root;
public:
/**
* @brief 默认构造函数,初始化空的映射
*/
Mymap() : root(nullptr) {}
// 析构时按照递归的方式回收资源
// helper function
void release(TreeNode<Key, T> *node)
{
if (node == nullptr)
return;
if (node->right)
release(node->right);
if (node->left)
release(node->left);
delete node;
}
/**
* @brief 析构函数,释放所有节点内存
*/
~Mymap()
{
release(root);
}
Mymap(const Mymap &other) = delete;
Mymap &operator=(const Mymap &other) = delete;
public:
/**
* @brief 插入键值对到映射中
* @param key 要插入的键
* @param value 要插入的值
* @note 如果键已存在,则更新对应的值
*/
void insert(const Key &key, const T &value)
{
if (root == nullptr)
{
root = new TreeNode<Key, T>(key, value);
return;
}
TreeNode<Key, T> *current = root;
TreeNode<Key, T> *parent = nullptr;
while (current != nullptr)
{
parent = current;
if (key < current->data.first)
{
current = current->left;
}
else if (key > current->data.first)
{
current = current->right;
}
else
{
current->data.second = value;
return;
}
}
if (key < parent->data.first)
parent->left = new TreeNode<Key, T>(key, value, parent);
else
parent->right = new TreeNode<Key, T>(key, value, parent);
}
public:
/**
* @brief 找到某一节点的左子树中的最小节点
* @param node 起始节点(假设不为空)
* @return 左子树中的最小节点
*/
TreeNode<Key, T> *mininumNode(TreeNode<Key, T> *node) const
{
while (node->left != nullptr)
{
node = node->left;
}
return node;
}
/**
* @brief 找到节点的后续节点(中序遍历中的下一个节点)
* @param node 起始节点(假设不为空)
* @return 后续节点,如果不存在则返回nullptr
*/
TreeNode<Key, T> *successorNode(TreeNode<Key, T> *node) const
{
// 右子树不为空
if (node->right != nullptr)
{
return mininumNode(node->right);
}
// 右子树为空
TreeNode<Key, T> *p = node->parent;
while (p != nullptr && node == p->right)
{
node = p;
p = p->parent;
}
return p;
}
/**
* @brief 删除指定键的节点
* @param key 要删除的键
* @note 如果键不存在,则不执行任何操作
*/
void erase(const Key &key)
{
TreeNode<Key, T> *node = find(key);
if (node == nullptr)
return;
// 情况1:删除的节点没有子节点
if (node->left == nullptr && node->right == nullptr)
{
if (node->parent == nullptr)
{
// 删除的是根节点
delete node;
root = nullptr;
}
else
{
if (node == node->parent->left)
node->parent->left = nullptr;
else
node->parent->right = nullptr;
delete node;
}
}
// 情况2:删除的节点有一个子节点
else if (node->left == nullptr || node->right == nullptr)
{
TreeNode<Key, T> *child = (node->left != nullptr) ? node->left : node->right;
if (node->parent == nullptr)
{
// 删除的是根节点
root = child;
child->parent = nullptr;
delete node;
}
else
{
if (node == node->parent->left)
node->parent->left = child;
else
node->parent->right = child;
child->parent = node->parent;
delete node;
}
}
// 情况3:删除的节点有两个子节点
else
{
TreeNode<Key, T> *successor = successorNode(node);
// 用后继节点的值替换当前节点
node->data = successor->data;
// 现在问题转化为删除successor节点
// successor最多只有一个右子节点(因为它是右子树的最小值,没有左子节点)
// 重新设置node指向successor,然后按照情况1或2删除
TreeNode<Key, T> *child = successor->right;
TreeNode<Key, T> *parent = successor->parent;
// 更新父节点指向
if (successor == parent->left)
parent->left = child;
else
parent->right = child;
// 更新子节点的父指针
if (child != nullptr)
child->parent = parent;
// 删除successor
delete successor;
}
}
public:
/**
* @brief 查找指定键的节点
* @param key 要查找的键
* @return 找到的节点指针,如果不存在则返回nullptr
*/
TreeNode<Key, T> *find(const Key &key) const
{
TreeNode<Key, T> *currentnode = root;
while (currentnode != nullptr)
{
if (key < currentnode->data.first)
currentnode = currentnode->left;
else if (key > currentnode->data.first)
currentnode = currentnode->right;
else
return currentnode;
}
return nullptr;
}
};
#endif
2. 基于 AVL 树的实现
为了克服普通二叉搜索树在最坏情况下的退化问题,我们可以使用 AVL 树,它是一种自平衡的二叉搜索树。AVL 树通过保持每个节点的平衡因子(左右子树的高度差不超过 1)来确保树的平衡性。这种平衡策略使得 AVL 树在任何时候都保持较为理想的树形结构,从而保证查找、插入和删除操作都能在 O(log n) 的时间复杂度内完成。
#ifndef MYAVLTREE_H
#define MYAVLTREE_H
#include <iostream>
#include <string>
#include <exception>
#include <algorithm>
#include <functional>
#include <vector>
#include <utility>
/**
* @brief AVL 树节点结构体
* @tparam Key 键类型
* @tparam Value 值类型
*/
template <typename Key, typename Value>
struct TreeNode
{
std::pair<Key, Value> data; ///< 存储键值对数据
TreeNode *left; ///< 左子节点指针
TreeNode *right; ///< 右子节点指针
int height; ///< 节点高度
/**
* @brief 构造函数
* @param k 键
* @param v 值
*/
TreeNode(const Key &k, const Value &v)
: data(std::make_pair(k, v)), height(1), left(nullptr), right(nullptr) {}
};
/**
* @brief 自平衡二叉搜索树(AVL树)实现
* @tparam Key 键类型,必须支持比较操作
* @tparam Value 值类型
*/
template <typename Key, typename Value>
class MyAVLTree
{
public:
using node = TreeNode<Key, Value>; ///< 节点类型别名
private:
node *root; ///< 树的根节点
/**
* @brief 获取节点高度
* @param n 节点指针
* @return 节点高度,空节点返回0
*/
int getHeight(node *n)
{
if (n == nullptr)
return 0;
return n->height;
}
/**
* @brief 计算节点的平衡因子
* @param n 节点指针
* @return 平衡因子 = 左子树高度 - 右子树高度
*/
int getbalance(node *n)
{
if (n == nullptr)
return 0;
else
return getHeight(n->left) - getHeight(n->right);
}
/**
* @brief 对节点进行右旋操作(LL型不平衡)
* @param objectnode 需要旋转的节点
* @return 旋转后新的子树根节点
*/
node *rightrotate(node *objectnode)
{
node *x = objectnode->left;
node *origin = x->right;
x->right = objectnode;
objectnode->left = origin;
objectnode->height = std::max(getHeight(objectnode->left), getHeight(objectnode->right)) + 1;
x->height = std::max(getHeight(x->left), getHeight(x->right)) + 1;
return x;
}
/**
* @brief 对节点进行左旋操作(RR型不平衡)
* @param objectnode 需要旋转的节点
* @return 旋转后新的子树根节点
*/
node *leftrotate(node *objectnode)
{
node *x = objectnode->right;
node *origin = x->left;
x->left = objectnode;
objectnode->right = origin;
x->height = std::max(getHeight(x->right), getHeight(x->left)) + 1;
objectnode->height = std::max(getHeight(objectnode->right), getHeight(objectnode->left)) + 1;
return x;
}
/**
* @brief 查找子树中的最小键值节点
* @param cur 子树根节点
* @return 最小键值节点指针
*/
node *getminvaluenode(node *cur)
{
node *current = cur;
while (current->left != nullptr)
{
current = current->left;
}
return current;
}
/**
* @brief 递归删除所有节点(用于析构函数)
* @param n 要删除的子树根节点
*/
void destroy(node *n)
{
if (n == nullptr)
return;
destroy(n->left);
destroy(n->right);
delete n;
}
public:
/**
* @brief 默认构造函数
* @post 创建空的AVL树
*/
MyAVLTree() : root(nullptr) {}
/**
* @brief 析构函数
* @post 释放所有节点内存
*/
~MyAVLTree()
{
destroy(root);
root = nullptr;
}
/**
* @brief 在树中插入键值对(公开接口)
* @param key 要插入的键
* @param value 要插入的值
* @note 如果键已存在,则更新对应的值
*/
void insert(const Key &key, const Value &value)
{
root = insert(root, key, value);
}
/**
* @brief 从树中删除指定键的节点(公开接口)
* @param key 要删除的键
* @note 如果键不存在,则不进行任何操作
*/
void remove(const Key &key)
{
root = deletenode(root, key);
}
private:
/**
* @brief 递归插入键值对到子树中(内部实现)
* @param node 子树根节点
* @param key 要插入的键
* @param value 要插入的值
* @return 插入后新的子树根节点
*/
node *insert(node *node, const Key &key, const Value &value)
{
if (node == nullptr)
{
return new TreeNode<Key, Value>(key, value);
}
// 递归查找插入位置
if (key < node->data.first)
{
node->left = insert(node->left, key, value);
}
else if (key > node->data.first)
{
node->right = insert(node->right, key, value);
}
else
{
// 键已存在,更新值
node->data.second = value;
return node;
}
// 更新节点高度
node->height = 1 + std::max(getHeight(node->left), getHeight(node->right));
// 检查平衡并旋转
int balance = getbalance(node);
// 左左情况(LL)
if (balance > 1 && key < node->left->data.first)
{
return rightrotate(node);
}
// 右右情况(RR)
if (balance < -1 && key > node->right->data.first)
{
return leftrotate(node);
}
// 左右情况(LR)
if (balance > 1 && key > node->left->data.first)
{
node->left = leftrotate(node->left);
return rightrotate(node);
}
// 右左情况(RL)
if (balance < -1 && key < node->right->data.first)
{
node->right = rightrotate(node->right);
return leftrotate(node);
}
return node;
}
/**
* @brief 递归删除子树中的节点(内部实现)
* @param root 子树根节点
* @param key 要删除的键
* @return 删除后新的子树根节点
*/
node *deletenode(node *root, const Key &key)
{
if (root == nullptr)
return root;
// 递归查找要删除的节点
if (key < root->data.first)
{
root->left = deletenode(root->left, key);
}
else if (key > root->data.first)
{
root->right = deletenode(root->right, key);
}
else
{
// 找到要删除的节点
// 情况1:有一个子节点或没有子节点
if ((root->left == nullptr) || (root->right == nullptr))
{
auto *temp = root->left ? root->left : root->right;
if (temp == nullptr) // 无子节点
{
temp = root;
root = nullptr;
}
else // 有一个子节点
{
*root = *temp; // 值传递
}
delete temp;
}
// 情况2:有两个子节点
else
{
auto *temp = getminvaluenode(root->right);
root->data.first = temp->data.first;
root->data.second = temp->data.second;
root->right = deletenode(root->right, temp->data.first);
}
}
if (root == nullptr)
return root;
// 更新高度
root->height = 1 + std::max(getHeight(root->left), getHeight(root->right));
// 检查平衡并旋转
int balance = getbalance(root);
// 左左情况(LL)
if (balance > 1 && getbalance(root->left) >= 0)
return rightrotate(root);
// 右右情况(RR)
if (balance < -1 && getbalance(root->right) <= 0)
return leftrotate(root);
// 左右情况(LR)
if (balance > 1 && getbalance(root->left) < 0)
{
root->left = leftrotate(root->left);
return rightrotate(root);
}
// 右左情况(RL)
if (balance < -1 && getbalance(root->right) > 0)
{
root->right = rightrotate(root->right);
return leftrotate(root);
}
return root;
}
};
#endif
无序映射表 unordered_map
哈希表是现代编程中最高效的数据结构之一,其核心在于通过哈希函数实现键到存储位置的直接映射。这里实现的开链法哈希表采用中心数组加链表的结构:哈希函数将键转换为数组索引,冲突元素通过链表连接,在保证空间效率的同时维持了平均O(1)的查询性能。
负载因子监控与自动扩容机制是保持高性能的关键。当元素密度超过阈值时,系统执行rehash操作——创建更大的桶数组并重新分配所有元素,这一过程虽然代价高昂,但确保了哈希表长期运行的高效性。
迭代器的实现展示了哈希表遍历的复杂性:它不仅要遍历链表,还要在桶间智能跳转。
#ifndef MYHASH_H
#define MYHASH_H
#include <iostream>
#include <utility>
#include <vector>
#include <functional>
#include <list>
#include <iterator>
#include <stdexcept>
/**
* @brief 哈希链表节点
* @tparam Key 键类型
* @tparam Value 值类型
*/
template <typename Key, typename Value>
struct HashNode
{
std::pair<Key, Value> data;
HashNode *next;
/**
* @brief 构造一个哈希节点
* @param key 节点键
* @param value 节点值
*/
HashNode(const Key &key, const Value &value) : data(std::make_pair(key, value)), next(nullptr) {}
};
/**
* @brief 简单哈希表(开链解决冲突)
* @tparam Key 键类型
* @tparam Value 值类型
* @tparam Hash 哈希函数类型(默认为 std::hash<Key>)
*
* 功能:插入、查找、删除、清空、迭代等。自动按照负载因子进行 rehash(扩容)。
*/
template <typename Key, typename Value, typename Hash = std::hash<Key>>
class MyHashMap
{
public:
class Iterator;
using key_type = Key;
using mapped_type = Value;
using value_type = std::pair<const Key, Value>;
using size_type = std::size_t;
/**
* @brief 构造函数
* @param initial_capacity 初始桶数量
* @param max_load_factor 最大负载因子(超过则触发 rehash)
*/
MyHashMap(size_type initial_capacity = 16, double max_load_factor = 0.75) : bucket_count_(initial_capacity), element_count_(0), max_load_factor_(max_load_factor),
hash_func_(Hash())
{
// 用vector的resize来重新扩容
buckets_.resize(bucket_count_);
}
/**
* @brief 析构函数,释放所有节点
*/
~MyHashMap()
{
clear();
}
/**
* @brief 清空哈希表,释放所有节点
*/
void clear()
{
for (size_type i = 0; i < bucket_count_; ++i)
{
HashNode<Key, Value> *current_node = buckets_[i];
while (current_node != nullptr)
{
HashNode<Key, Value> *temp = current_node;
current_node = current_node->next;
delete temp;
}
buckets_[i] = nullptr;
}
element_count_ = 0;
}
MyHashMap(const MyHashMap &other) = delete;
MyHashMap &operator=(const MyHashMap &other) = delete;
/**
* @brief 插入或更新键值对
* @param key 键
* @param value 值
*
* 若键已存在则更新其值,否则插入新节点;插入后若负载因子超过阈值则 rehash。
*/
void insert(const Key &key, const Value &value)
{
// 进行哈希映射
size_type hash_value = hash_func_(key);
size_type index = hash_value % bucket_count_;
HashNode<Key, Value> *node = buckets_[index];
while (node != nullptr)
{
if (node->data.first == key)
{
node->data.second = value;
return;
}
node = node->next;
}
HashNode<Key, Value> *newnode = new HashNode<Key, Value>(key, value);
newnode->next = buckets_[index];
buckets_[index] = newnode;
++element_count_;
// 检查负载因子
// 负载因子计算方式: element_count_ / bucket_count_
// 可以理解为 每个桶平均有多少元素
auto load_factor = static_cast<double>(element_count_) / bucket_count_;
if (load_factor > max_load_factor_)
{
rehash();
}
}
/**
* @brief 根据键查找值的指针
* @param key 目标键
* @return 指向值的指针;找不到返回 nullptr
*/
Value *find(const Key &key)
{
auto hash_value = hash_func_(key);
auto index = hash_value % bucket_count_;
HashNode<Key, Value> *node = buckets_[index];
while (node != nullptr)
{
if (node->data.first == key)
{
return &(node->data.second);
}
node = node->next;
}
return nullptr;
}
/**
* @brief 删除指定键(若存在)
* @param key 要删除的键
* @return 删除成功返回 true,否则 false
*/
bool erase(const Key &key)
{
auto hash_value = hash_func_(key);
auto index = hash_value % bucket_count_;
auto node = buckets_[index];
HashNode<Key, Value> *prev = nullptr;
while (node != nullptr)
{
if (node->data.first == key)
{
if (prev == nullptr)
{
buckets_[index] = node->next;
}
else
{
prev->next = node->next;
}
delete node;
--element_count_;
return true;
}
prev = node;
node = node->next;
}
return false;
}
size_type size() const
{
return element_count_;
}
bool empty() const
{
return element_count_ == 0;
}
Iterator begin()
{
for (size_type i = 0; i < bucket_count_; ++i)
{
if (buckets_[i] != nullptr)
{
return Iterator(this, i, buckets_[i]);
}
}
return end();
}
Iterator end()
{
return Iterator(this, bucket_count_, nullptr);
}
class Iterator
{
public:
using iterator_category = std::forward_iterator_tag;
using value_type = std::pair<Key, Value>;
using difference_type = std::ptrdiff_t;
using pointer = value_type *;
using reference = value_type &;
/**
* @brief 构造一个迭代器
* @param map 指向所属哈希表
* @param bucket_index_ 当前桶索引
* @param node 当前节点指针(可为 nullptr 表示 end)
*/
Iterator(MyHashMap *map, size_t bucket_index_, HashNode<Key, Value> *node) : map_(map), bucket_index_(bucket_index_), current_node_(node) {}
/**
* @brief 解除引用,返回当前键值对(引用)
* @throws std::out_of_range 若迭代器已越界(nullptr)
*/
reference operator*() const
{
if (current_node_ == nullptr)
{
throw std::out_of_range("Iterator out of range");
}
return current_node_->data;
}
/**
* @brief 指针访问当前元素
* @throws std::out_of_range 若迭代器已越界(nullptr)
*/
pointer operator->() const
{
if (current_node_ == nullptr)
{
throw std::out_of_range("Iterator out of range");
}
return &(current_node_->data);
}
Iterator &operator++()
{
advance();
return *this;
}
Iterator operator++(int)
{
Iterator temp = *this;
advance();
return temp;
}
/**
* @brief 相等比较
*/
bool operator==(const Iterator &other) const
{
return map_ == other.map_ && bucket_index_ == other.bucket_index_ && current_node_ == other.current_node_;
}
/**
* @brief 不等比较
*/
bool operator!=(const Iterator &other) const
{
return !(*this == other);
}
private:
MyHashMap *map_;
size_type bucket_index_;
HashNode<Key, Value> *current_node_;
// 迭代器前进的函数
void advance()
{
// 先尝试在当前桶的链表中前进
if (current_node_ != nullptr)
{
current_node_ = current_node_->next;
}
// 到达当前桶的末尾,继续寻找下一个非空桶
while (current_node_ == nullptr)
{
++bucket_index_;
if (bucket_index_ >= map_->bucket_count_)
{
// 已经超过最后一个桶,设置结束状态
current_node_ = nullptr;
break;
}
current_node_ = map_->buckets_[bucket_index_];
}
}
}; // Iterator 类结束(注意分号)
private:
// 中央数组
std::vector<HashNode<Key, Value> *> buckets_;
// 桶的数量
size_type bucket_count_;
// 总元素数量
size_type element_count_;
// 负载因子
double max_load_factor_;
// 哈希函数
Hash hash_func_;
// 重新哈希,扩容并重新分配元素
void rehash()
{
size_type new_bucket_count = bucket_count_ * 2;
std::vector<HashNode<Key, Value> *> new_buckets(new_bucket_count, nullptr);
for (size_type i = 0; i < bucket_count_; ++i)
{
HashNode<Key, Value> *node = buckets_[i];
while (node != nullptr)
{
HashNode<Key, Value> *next_node = node->next;
size_type new_index = hash_func_(node->data.first) % new_bucket_count;
node->next = new_buckets[new_index];
// 插入到新桶的头部 头插法
new_buckets[new_index] = node;
node = next_node;
}
buckets_[i] = nullptr;
}
buckets_ = std::move(new_buckets);
bucket_count_ = new_bucket_count;
}
};
#endif
单例模式 Singleton
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。该模式在需要控制资源访问(如数据库连接、日志对象、配置管理器等)的场景中尤为常用。在 C++ 中实现线程安全的单例需要考虑构造时机、多线程竞争以及资源释放等问题,不同的实现方式各有特点。
以下代码展示了三种典型的单例模式实现:基于双重检查锁定的懒汉式、饿汉式静态初始化,以及使用 std::call_once 的线程安全懒汉式。代码中包含了详细的注释,可供参考。
#ifndef MYSINGLETON_H
#define MYSINGLETON_H
#include <iostream>
#include <mutex>
#include <memory>
/**
* @file Mysingleton.hpp
* @brief 提供几种单例模式的示例实现(懒汉、饿汉、以及基于 call_once 的安全懒汉)。
*
* 该头文件演示了三种不同的单例实现方式,并在注释中说明了线程安全性和销毁语义:
* - MysingletonLazy: 使用双重检查锁定 (DCL) + std::mutex 保护的懒汉式(使用 shared_ptr 管理实例)。
* - MysingletonHun: 饿汉式,假设在翻译单元中初始化静态实例(须在 .cpp 中定义并初始化 instance)。
* - MysingletonSafe: 推荐的线程安全懒汉式,使用 std::call_once + std::once_flag,配合自定义删除器。
*
* @note 头文件仅包含类定义和静态成员声明;若需要全局初始化(饿汉式),请在对应的 .cpp 文件中提供静态成员的定义。
*/
/**
* @class MysingletonLazy
* @brief 懒汉式单例示例(采用 shared_ptr + 双重检查锁定)。
*
* 特性:
* - 使用 std::shared_ptr 管理实例所有权,方便在多处持有引用时自动释放。
* - 使用 std::mutex 与双重检查锁定以减少锁开销(需注意在某些旧平台的内存模型问题)。
* - 构造函数私有,拷贝与赋值被删除以防止复制。
*
* @thread_safety GetInstance() 在多数现代实现中是线程安全的(此处使用 mutex 保护)。
*/
class MysingletonLazy
{
private:
/// 单例的共享指针实例(只在类内声明,需在实现文件或静态初始化处定义)。
static std::shared_ptr<MysingletonLazy> instance;
/// 保护实例创建的互斥量。
static std::mutex mtx; // 互斥锁用于保护实例的创建过程,确保线程安全
private:
/// 私有默认构造函数,防止外部直接创建。
MysingletonLazy() {};
MysingletonLazy(const MysingletonLazy &) = delete; ///< 禁止拷贝
MysingletonLazy &operator=(const MysingletonLazy &) = delete; ///< 禁止赋值
public:
/**
* @brief 析构函数
*
* 仅用于示例打印,实际释放由 shared_ptr 管理。
*/
~MysingletonLazy()
{
std::cout << "MysingletonLazy destructed " << std::endl;
};
/**
* @brief 获取单例实例的共享指针。
*
* 使用双重检查锁定(Double-Checked Locking)以在多线程下安全创建实例并减少锁开销。
*
* @return std::shared_ptr<MysingletonLazy> 指向单例对象的共享指针。
* @note 如果你使用 C++11 及以上,推荐使用局部静态变量或 std::call_once 的实现。
*/
static std::shared_ptr<MysingletonLazy> GetInstance()
{
// 双重检查锁定(Double-Checked Locking)模式,确保线程安全的同时避免不必要的锁开销
if (instance == nullptr)
{
std::lock_guard<std::mutex> lock(mtx); // 使用锁保护实例的创建过程,确保线程安全
if (instance == nullptr)
{
instance = std::shared_ptr<MysingletonLazy>(new MysingletonLazy());
}
}
return instance;
}
};
/**
* @class MysingletonHun
* @brief 饿汉式单例示例(启动时创建实例)。
*
* 特性:
* - 通过在程序启动时初始化静态实例来保证线程安全(依赖于静态初始化顺序)。
* - 需要在某个 .cpp 文件中定义并初始化静态成员 instance,例如:
* @code
* std::shared_ptr<MysingletonHun> MysingletonHun::instance = std::shared_ptr<MysingletonHun>(new MysingletonHun());
* @endcode
*
* @note 饿汉式在单例构造开销小且必须在程序启动时就准备好的场景较为合适;否则会浪费资源。
*/
class MysingletonHun
{
private:
/// 由 shared_ptr 管理的静态单例实例(需在翻译单元中定义)。
static std::shared_ptr<MysingletonHun> instance;
private:
MysingletonHun() {}; ///< 私有构造
MysingletonHun(const MysingletonHun &) = delete; ///< 禁止拷贝
MysingletonHun &operator=(const MysingletonHun &) = delete; ///< 禁止赋值
public:
/**
* @brief 析构函数(示例打印)
*
* 实际销毁时机由 shared_ptr 的引用计数决定,若在翻译单元中为全局 shared_ptr 初始化,则程序退出时会执行销毁。
*/
~MysingletonHun()
{
std::cout << "mysingletonHun destructed " << std::endl;
};
/**
* @brief 获取单例实例。
* @return std::shared_ptr<MysingletonHun> 单例共享指针(若未初始化则可能为 nullptr)。
* @warning 确保在某处为 MysingletonHun::instance 提供定义和初始化,否则返回 nullptr。
*/
static std::shared_ptr<MysingletonHun> GetInstance()
{
return instance;
}
};
/**
* @brief 自定义删除器示例类,用于演示如何在 shared_ptr 中使用自定义删除行为。
*
* operator() 会在删除 MysingletonSafe 实例时被调用。
*/
class safedeleter
{
public:
/**
* @brief 删除操作符,当 shared_ptr 释放最后一个引用时调用。
* @param sf 要删除的 MysingletonSafe 指针。
*/
void operator()(MysingletonSafe *sf)
{
std::cout << "safe deleter called " << std::endl;
delete sf;
}
};
/**
* @class MysingletonSafe
* @brief 推荐的线程安全懒汉式单例(使用 std::call_once)。
*
* 特性:
* - 使用 std::call_once 与 std::once_flag 保证仅初始化一次(线程安全)。
* - 使用自定义删除器 safedeleter 与 shared_ptr 配合,展示如何控制销毁行为。
* - 构造与拷贝语义被限制,GetInstance 返回 shared_ptr 管理的实例。
*
* @note 这是一个懒加载(首次调用时创建)且线程安全的单例实现。
*/
class MysingletonSafe
{
public:
/**
* @brief 获取单例实例(线程安全,使用 std::call_once 实现)。
* @return std::shared_ptr<MysingletonSafe> 指向单例的共享指针。
*/
static std::shared_ptr<MysingletonSafe> GetInstance(){
static std::once_flag flag;
std::call_once(flag,[]{instance = std::shared_ptr<MysingletonSafe>(new MysingletonSafe(), safedeleter());});
return instance;
}
private:
/// 单例实例(由 GetInstance 使用 std::call_once 初始化)。
static std::shared_ptr<MysingletonSafe> instance;
// 这里加不加class都行
friend safedeleter; ///< 允许 safedeleter 访问私有析构函数
private:
MysingletonSafe(); ///< 私有构造(需要在实现文件中定义)
/**
* @brief 私有析构函数(被 safedeleter 调用以控制销毁过程)。
*
* 通过将析构函数设为私有并将 safedeleter 设为友元,可以确保只有自定义删除器能删除该实例。
*/
~MysingletonSafe()
{
std::cout << "safe delete called by friend class " << std::endl;
}
MysingletonSafe(const MysingletonSafe &other) = delete; ///< 禁止拷贝
MysingletonSafe &operator=(const MysingletonSafe &other) = delete; ///< 禁止赋值
};
#endif
这里提供模板单例,可快速复用,提高效率。
#include <memory>
#include <mutex>
#include <iostream>
using namespace std;
template <typename T>
class Singleton {
protected:
Singleton() = default;
Singleton(const Singleton<T>&) = delete;
Singleton& operator=(const Singleton<T>& st) = delete;
static std::shared_ptr<T> _instance;
public:
static std::shared_ptr<T> GetInstance() {
static std::once_flag s_flag;
std::call_once(s_flag, [&]() {
_instance = shared_ptr<T>(new T);
});
return _instance;
}
void PrintAddress() {
std::cout << _instance.get() << endl;
}
~Singleton() {
std::cout << "this is singleton destruct" << std::endl;
}
};
template <typename T>
std::shared_ptr<T> Singleton<T>::_instance = nullptr;
使用时,public继承并在子类构造和析构里实现自己的逻辑
注意,关于静态成员变量的定义位置,需要根据类的类型来区分:对于非模板类,静态成员变量必须在一个单独的cpp文件中定义和初始化,这是为了避免多重定义链接错误,遵循单一定义规则;
而对于模板类,静态成员变量则应该在头文件中定义,因为模板需要在使用时实例化,编译器必须看到完整的定义才能为不同的模板参数生成相应的代码,如果放在cpp文件中,其他包含头文件的编译单元将无法找到该静态成员的定义而导致链接错误。这种区别是C++模板编程与非模板编程的重要不同之处。
不过,C++17引入了inline变量,可以让非模板类的静态成员也在头文件中定义