post on 28 Apr 2025 about 18725words require 63min
CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
堆(Heap)
new
/malloc
(C++)或由运行时自动分配(Python)来获取;释放时需 delete
/free
或由垃圾回收负责。堆空间大但分配、释放开销较大,可能产生内存碎片。栈:一种线性受限结构,只允许在栈顶进行插入(Push)和删除(Pop),遵循“后进先出”(LIFO)原则。常用操作包括 push
(进栈)、pop
(出栈)、top
(取栈顶)等,这些操作时间复杂度通常为 O(1)。栈可用数组(顺序栈)或链表(链式栈)实现,适用于函数调用、递归计算、撤销操作等场景。
1
2
3
4
5
6
7
8
9
10
11
int st[N];
// 这里使用 st[0] (即 *st) 代表栈中元素数量,同时也是栈顶下标
// 压栈 :
st[++*st] = var1;
// 取栈顶 :
int u = st[*st];
// 弹栈 :注意越界问题, *st == 0 时不能继续弹出
if (*st) --*st;
// 清空栈
*st = 0;
C++ 中的 STL 也提供了一个容器 std::stack
,使用前需要引入 stack
头文件。
STL 中的 stack
容器提供了一众成员函数以供调用,其中较为常用的有:
元素访问
st.top()
返回栈顶修改
st.push()
插入传入的参数到栈顶st.pop()
弹出栈顶容量
st.empty()
返回是否为空st.size()
返回元素数量此外,std::stack
还提供了一些运算符。较为常用的是使用赋值运算符 =
为 stack
赋值,示例:
1
2
3
4
5
6
7
8
9
10
11
12
// 新建两个栈 st1 和 st2
std::stack<int> st1, st2;
// 为 st1 装入 1
st1.push(1);
// 将 st1 赋值给 st2
st2 = st1;
// 输出 st2 的栈顶元素
cout << st2.top() << endl;
// 输出: 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
st = [5, 1, 4]
# 使用 append() 向栈顶添加元素
st.append(2)
st.append(3)
# >>> st
# [5, 1, 4, 2, 3]
# 使用 pop 取出栈顶元素
st.pop()
# >>> st
# [5, 1, 4, 2]
# 使用 clear 清空栈
st.clear()
堆(优先队列): 一种树形结构,即满足堆序性的完全二叉树。每个节点的值都不大于(或不小于)其父节点的值,根节点是最大值(大顶堆)或最小值(小顶堆)。常见操作包括:上浮(shift_up)、下沉(shift_down)、插入(push)和弹出(pop)堆顶元素,以及查询堆顶(top)。堆常用作优先队列,在任务调度、Dijkstra 最短路径、Top-K 计算等场景中非常常见。
堆是一棵树,其每个节点都有一个键值,且每个节点的键值都大于等于/小于等于其父亲的键值。
每个节点的键值都大于等于其父亲键值的堆叫做小根堆,否则叫做大根堆。STL 中的 priority_queue 其实就是一个大根堆。
(小根)堆主要支持的操作有:插入一个数、查询最小值、删除最小值、合并两个堆、减小一个元素的值。
一些功能强大的堆(可并堆)还能(高效地)支持 merge 等操作。
一些功能更强大的堆还支持可持久化,也就是对任意历史版本进行查询或者操作,产生新的版本。
priority_queue
):1
2
3
4
5
std::priority_queue<int> pq;
pq.push(5);
pq.push(3);
int top = pq.top(); // 5 (最大值)
pq.pop();
heapq
最小堆):1
2
3
4
5
import heapq
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 5)
top = heapq.heappop(heap) # 3 (最小值)
堆排序是一种高效的、基于比较的排序算法。它利用了堆这种数据结构的特性。基本思想是:
排序 (Sort):
堆排序的平均时间复杂度和最坏时间复杂度都是 O(nlogn)。
在 C++ 中,可以利用标准库 <algorithm>
中提供的堆操作函数来方便地实现堆排序。
std::make_heap(first, last)
: 将指定范围 [first, last)
内的元素重新排列,使其成为一个大顶堆。std::sort_heap(first, last)
: 将一个已经建好的堆 [first, last)
进行排序。它会重复地将堆顶元素(最大值)移动到序列的末尾,并重新调整剩余部分为堆。C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <vector>
#include <algorithm> // 包含 make_heap, sort_heap
void print_vector(const std::vector<int>& vec) {
for (int val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> v = {3, 5, 1, 8, 4, 7, 2};
std::cout << "Original vector: ";
print_vector(v);
// 1. 将 vector 转换成大顶堆
std::make_heap(v.begin(), v.end());
std::cout << "After make_heap (top is max): " << v.front() << std::endl;
// 2. 对堆进行排序 (结果为升序)
std::sort_heap(v.begin(), v.end());
std::cout << "Sorted vector: ";
print_vector(v); // 输出: 1 2 3 4 5 7 8
return 0;
}
Python 的 heapq
模块本身不直接提供一个完整的 heapsort
函数,但我们可以很容易地利用其 heappush
和 heappop
来实现。因为 heapq
是最小堆,所以 heappop
总是弹出最小值,天然适合用于升序排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import heapq
def heapsort_asc(iterable):
"""
使用 heapq 实现升序排序
"""
h = []
# 将所有元素压入堆中
for value in iterable:
heapq.heappush(h, value)
# 依次弹出堆中最小的元素,构成有序列表
return [heapq.heappop(h) for _ in range(len(h))]
data = [3, 5, 1, 8, 4, 7, 2]
print(f"Original list: {data}")
sorted_data = heapsort_asc(data)
print(f"Sorted list (ascending): {sorted_data}") # 输出: [1, 2, 3, 4, 5, 7, 8]
# 原地堆排序 (In-place Heap Sort)
# 这更接近于堆排序的经典实现
def heapsort_inplace(arr):
n = len(arr)
# 1. 构建大顶堆 (从最后一个非叶子节点开始)
# 注意:heapq 是最小堆,所以这里通过对负数操作来模拟大顶堆
# 或者我们手动实现大顶堆的 sift_down
# 为了简单,我们还是用 heapq 来理解,但传统实现更高效
h = []
for x in arr:
heapq.heappush(h, x)
arr[:] = [heapq.heappop(h) for _ in range(n)]
return arr
data_inplace = [3, 5, 1, 8, 4, 7, 2]
heapsort_inplace(data_inplace)
print(f"In-place sorted list: {data_inplace}")
栈和堆(优先队列)各有特点:
在程序运行时,内存通常分为代码区、数据区、堆区和栈区。其中:
new
/malloc
等申请内存,由程序员 delete
/free
释放(在 Python/Java 等语言由垃圾回收自动释放)。堆的可用空间远大于栈,存放动态对象。堆内存碎片化的风险更高:频繁的分配和释放可能将大块连续内存切割成许多小碎片,降低利用率。分配方式:栈的分配和回收速度极快,操作由 CPU 指令自动完成;堆的分配开销较大,一般需要额外的内存管理算法(如自由链表或分代收集),在 C++ 中需要程序员手动释放。Python 中所有对象都分配在堆上,解释器通过引用计数和垃圾回收来管理。
访问效率:由于栈内存连续、分配固定,因此访问和分配速度更高。堆内存由多个块组成,需额外指针管理,因而略慢于栈访问。此外,栈是线程私有的(线程安全),而堆是所有线程共享的(需注意并发安全)。
1
2
3
4
5
6
void func() {
int a = 10; // 分配在栈上
int *p = new int(20); // 分配在堆上
// ...
delete p; // 手动释放堆内存
} // 函数返回时,a 的栈空间自动释放,若忘了 delete,则 p 指向的内存泄露
1
2
3
4
def func():
a = 10 # 10 是整数对象,存储在堆中;a 是栈帧内的局部引用
b = [1, 2, 3] # 列表对象在堆上分配
# 变量 a, b 是存放在函数调用栈帧中的引用,当函数结束,这些引用消失
当程序调用一个函数时,当前上下文(包括当前函数的局部变量、返回地址、CPU 寄存器等)会被“压栈(push)”到栈上;函数执行完成后,栈顶信息被“弹栈(pop)”,程序自动返回调用点并恢复先前状态。这种机制正是栈的典型应用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
// 一个示例函数,用于演示栈帧形成
int factorial(int n) {
if (n <= 1) {
return 1;
}
// 调用 factorial(n-1) 前,会把当前的 n、返回地址等信息压入栈
return n * factorial(n - 1);
}
int main() {
int x = 5;
std::cout << "factorial(" << x << ") = " << factorial(x) << std::endl;
return 0;
}
factorial
时,当前函数的局部变量(如 n
)和返回地址会压入栈;函数结束时,栈帧被弹出,返回到上一级调用点。1
2
3
4
5
6
7
8
9
10
11
12
def factorial(n):
if n <= 1:
return 1
# 递归调用时,Python 会将当前函数帧压入调用栈
return n * factorial(n - 1)
if
name
== "
__main__
":
x = 5
print(f"factorial({x}) =", factorial(x))
new
/malloc
(C++)或创建对象(Python)。这些内存由程序员负责释放(或由垃圾回收器回收)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
void stackExample() {
int a = 10; // 分配在栈上
int b[100]; // 数组也分配在栈上
std::cout << "a = " << a << std::endl;
std::cout << "b[0] = " << b[0] << std::endl;
} // 函数结束时,a 和 b 的栈空间自动释放
void heapExample() {
int *p = new int(20); // 在堆上分配一个 int
int *arr = new int[100];// 在堆上分配一个大小为 100 的数组
std::cout << "*p = " << *p << std::endl;
std::cout << "arr[0] = " << arr[0] << std::endl;
delete p; // 释放堆内存
delete[] arr; // 释放数组
}
int main() {
stackExample();
heapExample();
return 0;
}
stackExample
:变量 a
和数组 b
分配在栈上,由系统自动分配与回收。heapExample
:使用 new
在堆上分配内存,需要手动调用 delete
/delete[]
来释放,否则会发生内存泄漏。1
2
3
4
5
6
7
8
9
10
11
def memory_example():
a = 10 # 整数对象 10 存储在堆上,a 是栈帧中的一个引用
lst = [1, 2, 3] # 列表对象存储在堆上,lst 引用保存在栈帧
print(a, lst)
if
name
== "
__main__
":
memory_example()
# Python 通过引用计数和垃圾回收自动释放不再使用的对象
逆波兰表达式(后缀表达式)无需括号即可明确运算顺序,评估过程中需要一个栈来保存操作数、临时结果。
+ - * /
四则运算)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <stack>
#include <sstream>
#include <string>
#include <vector>
// 将字符串拆分为逆波兰表达式的 tokens
std::vector<std::string> tokenize(const std::string &expr) {
std::vector<std::string> tokens;
std::istringstream iss(expr);
std::string token;
while (iss >> token) {
tokens.push_back(token);
}
return tokens;
}
int evalRPN(const std::vector<std::string> &tokens) {
std::stack<int> st;
for (const auto &tk : tokens) {
if (tk == "+" || tk == "-" || tk == "
_" || tk == "/") {
int b = st.top(); st.pop();
int a = st.top(); st.pop();
int res = 0;
if (tk == "+") res = a + b;
else if (tk == "-") res = a - b;
_* else if (tk == "*
") res = a * b;
else if (tk == "/") res = a / b;
st.push(res);
} else {
st.push(std::stoi(tk));
}
}
return st.top();
}
int main() {
std::string expr = "3 4 + 2 * 7 /";
// 对应中缀: ((3 + 4) * 2) / 7 = 2
auto tokens = tokenize(expr);
std::cout << "Result: " << evalRPN(tokens) << std::endl;
return 0;
}
std::stack<int>
存放操作数。每遇运算符,弹出两个操作数,计算后将结果压回栈。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def eval_rpn(tokens):
stack = []
for tk in tokens:
if tk in {"+", "-", "
_", "/"}:
b = stack.pop()
a = stack.pop()
if tk == "+":
stack.append(a + b)
elif tk == "-":
stack.append(a - b)
_* elif tk == "*
":
stack.append(a * b)
else:
# 对于除法,需要注意 Python 的整除与 C++ 不同
stack.append(int(a / b)) # 向零取整
else:
stack.append(int(tk))
return stack.pop()
if
name
== "
__main__
":
expr = "3 4 + 2 * 7 /".split()
print("Result:", eval_rpn(expr)) # 2
stack
模拟栈,操作与 C++ 版本一致。许多应用需要实现“撤销”功能,此时可将用户操作或状态快照依次压入栈,用户点击“撤销”时,再次从栈顶弹出即可恢复到上一次状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class TextEditor:
def
__init__
(self):
self.text = ""
self.undo_stack = [] # 存放历史状态
def write(self, s):
# 在写新内容前,将当前状态压栈
self.undo_stack.append(self.text)
self.text += s
def undo(self):
if self.undo_stack:
self.text = self.undo_stack.pop()
else:
print("Nothing to undo")
def show(self):
print(f"Current Text: '{self.text}'")
if
name
== "
__main__
":
editor = TextEditor()
editor.write("Hello")
editor.show() # Hello
editor.write(", World!")
editor.show() # Hello, World!
editor.undo()
editor.show() # Hello
editor.undo()
editor.show() # (空字符串)
self.text
的旧值压入 undo_stack
。调用 undo()
时,将栈顶字符串弹出并恢复。浏览器维护一个“历史页面访问栈”:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class BrowserHistory:
def __init__(self, homepage: str):
self.back_stack = [] # 后退栈
self.forward_stack = [] # 前进栈
self.current = homepage # 当前页面
def visit(self, url: str):
self.back_stack.append(self.current)
self.current = url
self.forward_stack.clear() # 新访问清空前进历史
def back(self):
if self.back_stack:
self.forward_stack.append(self.current)
self.current = self.back_stack.pop()
else:
print("No pages to go back to")
def forward(self):
if self.forward_stack:
self.back_stack.append(self.current)
self.current = self.forward_stack.pop()
else:
print("No pages to go forward to")
def show(self):
print(f"Back: {self.back_stack}, Current: {self.current}, Forward: {self.forward_stack}")
if name == "__main__":
browser = BrowserHistory("homepage.com")
browser.show()
browser.visit("news.com")
browser.show()
browser.visit("sports.com")
browser.show()
browser.back()
browser.show()
browser.back()
browser.show()
browser.forward()
browser.show()
back_stack
和 forward_stack
) 维护历史访问记录,实现后退和前进功能。在编译器或解释器的语法分析阶段,需要检查表达式或语句是否合法。最常见的是括号匹配问题:扫描字符串时,遇到左括号((
、[
、{
)时压栈,遇到右括号时检查栈顶是否是对应的左括号,若不匹配则报错;最后栈为空则匹配成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <stack>
#include <string>
#include <unordered_map>
bool isValid(const std::string &s) {
std::stack<char> st;
std::unordered_map<char, char> pairs = {
{')', '('}, {']', '['}, {'}', '{'}
};
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
} else if (c == ')' || c == ']' || c == '}') {
if (st.empty() || st.top() != pairs[c]) {
return false;
}
st.pop();
}
// 忽略其他字符
}
return st.empty();
}
int main() {
std::string s1 = "([{}])";
std::string s2 = "([}{])";
std::cout << s1 << " is " << (isValid(s1) ? "valid\n" : "invalid\n");
std::cout << s2 << " is " << (isValid(s2) ? "valid\n" : "invalid\n");
return 0;
}
std::stack<char>
存储左括号,遇到右括号时检查对应关系。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def is_valid(s: str) -> bool:
stack = []
pairs = {')': '(', ']': '[', '}': '{'}
for c in s:
if c in '([{':
stack.append(c)
elif c in ')]}':
if not stack or stack[-1] != pairs[c]:
return False
stack.pop()
# 忽略其他字符
return not stack
if name == "__main__":
print(is_valid("([{}])")) # True
print(is_valid("([}{])")) # False
操作系统在进行线程切换或进程切换时,需要保存当前执行状态(寄存器上下文、程序计数器等)到线程/进程的栈帧中,待下次重新调度时再从栈中恢复。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct CPUContext {
uint64_t rip; // 指令指针(程序计数器)
uint64_t rsp; // 栈指针
uint64_t regs[16];// 其他通用寄存器
// ...
};
void context_switch(CPUContext *cur, CPUContext
_next) {
// 保存当前上下文到 cur
asm volatile (
"mov %%rsp, %0\n\t"
"mov %%rax, %1\n\t"
// … 其他寄存器
_* : "=m"(cur->rsp), "=m"(cur->regs[0]) /*
…
_/
:
:
);
// 加载下一个上下文
asm volatile (
"mov %0, %%rsp\n\t"
"mov %1, %%rax\n\t"
// … 其他寄存器
:
_* : "m"(next->rsp), "m"(next->regs[0]) /*
… */
);
// 跳转到下一个线程的指令地址
asm volatile ("jmp *%0" :: "m"(next->rip));
}
CPUContext
结构,模拟上下文切换。真实内核会更复杂,并在内核栈上完成这些操作。在实时系统(RTOS)中,每个任务通常分配一个固定大小的栈,用于保存用户态执行时的局部变量和调用帧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 假设 Cortex-M 架构,中断发生时硬件会自动压入 R0-R3、R12、LR、PC、xPSR
void SysTick_Handler(void) {
// 此时硬件已将通用寄存器和 xPSR 压入当前任务的栈中,使用 PSP/MSP 寄存器区分
// 处理中断逻辑
// ...
// 退出中断时,硬件自动从栈中弹回寄存器并恢复现场
}
// 任务创建时,手动构造该任务的初始栈帧
uint32_t *create_task_stack(void (*task_func)(void), uint32_t *stack_top) {
// 栈顶需预留硬件自动压栈的空间(8 寄存器)
*(--stack_top) = INITIAL_xPSR; // xPSR
*(--stack_top) = (uint32_t)task_func; // PC
*(--stack_top) = 0xFFFFFFFD; // LR (使用 PSP 指向任务栈)
// R12, R3, R2, R1, R0
for (int i = 0; i < 5; i++) {
*(--stack_top) = 0;
}
// 接下来是软件自动压入的寄存器(R4–R11)
for (int i = 0; i < 8; i++) {
*(--stack_top) = 0;
}
return stack_top; // 返回任务上下文初始化后的栈顶指针
}
task_func
开始执行。C 语言的内存模型分为 5 个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:
new
/malloc
分配内存时,分配器会在堆中寻找足够大的空闲块。当程序对堆申请大量连续内存(如 new
/malloc
)而栈调用层次过深(或线程栈空间不足)时:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <vector>
void recurse(int depth) {
// 每次调用消耗一定的栈空间
char buffer[1024]; // 1KB 的局部数组
if (depth > 0) {
recurse(depth - 1);
}
}
int main() {
// 不断分配堆内存
std::vector<int*> allocations;
try {
while (true) {
allocations.push_back(new int[10000]); // 每次分配 ~40KB
}
} catch (std::bad_alloc &e) {
std::cerr << "Heap exhausted: " << e.what() << std::endl;
}
// 同时进行深度递归
recurse(100000); // 这会导致栈溢出
return 0;
}
new int[10000]
(堆分配)与深度递归 recurse(100000)
(栈分配),就可能发生堆与栈冲突。实际运行时,程序要么先出现堆分配失败(抛出 bad_alloc
),要么先出现栈溢出 (Stack Overflow)。pvPortMalloc
/vPortFree
操作。1
2
3
4
5
6
7
8
9
10
11
12
13
// FreeRTOS 示例:创建一个任务,并为其指定栈大小
void vTaskFunction(void *pvParameters) {
int local_var = 42; // 存储在任务栈中
for (;;) {
// Task logic...
}
}
int main(void) {
// 在创建任务时,指定 256 字 作为任务栈大小
xTaskCreate(vTaskFunction, "Task1", 256, NULL, 1, NULL);
vTaskStartScheduler(); // 启动调度器,开始抢占式多任务
return 0;
}
函数调用与请求栈
缓存管理
std::priority_queue
或 Python 的 heapq
实现定时淘汰策略。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <queue>
#include <unordered_map>
#include <chrono>
#include <thread>
struct CacheItem {
int key;
std::string value;
std::chrono::steady_clock::time_point expire_time;
};
struct CompareExpire {
bool operator()(const CacheItem &a, const CacheItem &b) {
return a.expire_time > b.expire_time; // 过期时间早的优先级高
}
};
class LRUCache {
public:
LRUCache(size_t capacity): capacity_(capacity) {}
void put(int key, const std::string &value, int ttl_seconds) {
auto now = std::chrono::steady_clock::now();
CacheItem item{key, value, now + std::chrono::seconds(ttl_seconds)};
if (cache_map_.size() >= capacity_) {
// 移除过期或最久未使用元素
evict();
}
cache_map_[key] = item;
min_heap_.push(item);
}
std::string get(int key) {
auto it = cache_map_.find(key);
if (it == cache_map_.end()) return ""; // 未命中
// 更新过期时间或移动到最新位置可自行实现
return it->second.value;
}
private:
void evict() {
auto now = std::chrono::steady_clock::now();
while (!min_heap_.empty()) {
auto &top = min_heap_.top();
if (top.expire_time <= now) {
// 已过期,删除
cache_map_.erase(top.key);
min_heap_.pop();
} else {
// 如果堆顶未过期,但 cache_map_ 超过容量,可自行实现额外的 LRU 逻辑
break;
}
}
// 这里简化:如果仍然超出容量,可额外删除最老元素
}
size_t capacity_;
std::unordered_map<int, CacheItem> cache_map_;
std::priority_queue<CacheItem, std::vector<CacheItem>, CompareExpire> min_heap_;
};
int main() {
LRUCache cache(3);
cache.put(1, "A", 2);
cache.put(2, "B", 5);
cache.put(3, "C", 10);
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "Get key 1: " << cache.get(1) << std::endl; // 可能已过期,返回 ""
return 0;
}
std::priority_queue
(最小堆)按过期时间排序,当容量满时弹出最早过期项或其他淘汰策略。new/delete
会导致堆碎片和性能损耗。常用的做法是在启动时预先向堆申请一大块连续内存,将其切分为固定大小的“内存池块”,通过栈或链表管理空闲块。分配时从池中取出一个空闲块,释放时将其归还池中,而无需操作系统的堆管理。GameObject
为例)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <vector>
// 假设游戏对象
class GameObject {
public:
GameObject() : x(0), y(0) {}
void reset() { x = y = 0; }
void set_position(int px, int py) { x = px; y = py; }
void print() const { std::cout << "GameObject at (" << x << ", " << y << ")\n"; }
private:
int x, y;
};
class ObjectPool {
public:
ObjectPool(size_t poolSize) {
pool_.reserve(poolSize);
for (size_t i = 0; i < poolSize; ++i) {
pool_.push_back(new GameObject());
free_stack_.push(pool_.back());
}
}
~ObjectPool() {
for (auto obj : pool_) delete obj;
}
GameObject* acquire() {
if (free_stack_.empty()) return nullptr;
GameObject* obj = free_stack_.top();
free_stack_.pop();
return obj;
}
void release(GameObject* obj) {
obj->reset();
free_stack_.push(obj);
}
private:
std::vector<GameObject*> pool_;
std::stack<GameObject*> free_stack_;
};
int main() {
ObjectPool pool(5);
GameObject *obj1 = pool.acquire();
obj1->set_position(10, 20);
obj1->print(); // GameObject at (10, 20)
pool.release(obj1);
GameObject *obj2 = pool.acquire();
obj2->print(); // GameObject at (0, 0) (重置后)
return 0;
}
ObjectPool
在构造时一次性向堆申请若干 GameObject
,将它们全部存在 pool_
容器中,再把指针压入 free_stack_
(栈)。获取时从 free_stack_
弹栈;释放时将对象 reset()
并压回栈。避免了频繁的 new/delete
。线程栈
堆碎片与分配器
总体而言,栈与堆在数据结构和内存管理层面都是基础而关键的概念。数据结构层面,栈提供简单高效的 LIFO 存取,堆(优先队列)提供基于优先级的动态调度;内存管理层面,栈由系统自动分配释放,速度快但空间有限;堆则按需动态分配,由程序或运行时负责回收,灵活但需要注意碎片和内存泄漏。理解两者的差异及应用场景(如嵌入式的静态分配、后端的请求栈和缓存管理、游戏的内存池、操作系统的线程栈管理等)可以帮助程序员写出更健壮、高效的代码。
Related posts