什么叫动态分配?
形象来说,动态分配就像是在一个大型购物广场中,你根据需要随时租用或归还一个店铺。程序运行时,如果需要更多空间来存储数据,就会向操作系统 “租用” 内存空间;用完后,为了避免浪费资源,需要 “归还” 这些空间。这个过程不是自动的,需要你手动管理。
动态分配的优点
- 灵活性:需要的时候用,不需要的时候还;
动态分配最核心的地方就是它可以在不需要的时候归还。比如,在某个函数中定义了一个数组
a[5]
,当这个数组满了之后我们需要对其扩容,if (数组满) a扩容到10
,如果这个数组是动态分配的,我们就可以手动的释放之前的a[5]
;如果这个数组是静态分配的,这个空间无法释放,会一直占用,直到程序运行结束。 - 不会占用栈和数据段的空间,为内存减轻负荷;
- 举例:动态定义的顺序表 \(\text{vs}\) 静态定义的顺序表、动态实现的链表 \(\text{vs}\) 静态实现的链表;
链表需要进行频繁的插入和删除,如果使用的静态分配,那么这些插入删除的空间在程序运行时将无法释放,只有使用动态分配才有办法在程序运行时释放这些空间,因为静态分配不提供释放空间的方法。
动态分配的语法解释
动态分配的变量
int a = 10; // 静态分配一个整型
int *b = new int(10); // 动态分配一个整型
delete b;
这两条语句的功能类似,都得到了一个值为 \(10\) 的变量,只不过变量 a
存储在栈内存中,b
存储在堆内存中;在程序运行时,a
会一直占用这个空间,无法主动释放,b
可以主动释放。
为什么要主动释放?如果我们静态实现了一个链表,每次插入的时候需要分配一个新结点,由于是静态分配的,这个新结点将存在栈内存中;每次删除的时候,虽然链表在逻辑上将其删除了,但是它还是占用着栈空间的;在多次的插入和删除后,将会导致栈溢出,从而发生段错误;使用动态分配就不会这样了,它不仅可以手动释放空间,还不会占用栈内存。
- 使用
new
关键字进行动态分配时,它总是返回一个指针,指向新分配的内存地址,因为new
的本质就是申请一份空间; - 有借有还,申请了空间,必须手动释放;
- 访问的时候和指针一样,如果访问内容需要使用
*a
;
动态分配的数组
int staticArray[5] = {1, 2, 3, 4, 5}; // 静态分配一个整型数组
int *dynamicArray = new int[5]; // 动态分配一个整型数组
delete[] dynamicArray; // 释放动态分配的整型数组
- 动态分配数组在
new
的时候使用的是[元素个数]
,动态分配变量在new
的时候使用的是(元素值)
; - 不能直接利用列表赋值,通常用
for
循环遍历赋值; - 访问时与数组类似,又两种访问方式,第一种是利用指针,比如
*(p+1)
,第二种直接使用数组下标p[1]
。感觉和数组相同;
动态分配的结构体
struct Person {
string name;
int age;
};
Person alice = {"Alice", 20}; // 静态分配并初始化一个结构体
Person *Mike = new Person{"Mike", 30}; // 动态分配并初始化一个结构体
delete Mike;
参考
- 零壹考研