一、什么是动态内存分配?
在C和C++中,程序运行时可以根据需要分配内存空间。这就叫做动态内存分配 。它与静态内存分配不同,静态内存分配是编译时就决定好了内存的大小,而动态内存分配则是程序在运行时根据情况来分配内存。
比如你在编写程序时,不知道需要多少个整数,或者数组的大小在运行时才知道,这时候就可以使用动态内存分配。
二、为什么要用动态内存?
想象一下,程序在运行时可能要处理很多数据,数据的大小不确定。**如果在编译时就决定数组的大小,那就有可能内存浪费或者不够用。**动态内存分配的优势就是你可以在运行时根据需要调整内存的大小,节省资源。
动态内存分配的目的是在运行时按需分配内存空间。C++ 提供了两个操作符来管理动态内存:
new
:用于在堆上分配内存。delete
:用于释放new
分配的内存。
三、new
和 new[]
:动态分配内存
C++提供了两个关键字来做动态内存分配:new
和 new[]
。
new
用来分配一个对象的内存,指向这个对象。new[]
用来分配一段连续的内存空间,指向数组的第一个元素。
1.new
语法:
cpp
int* p = new int; // 分配一个 int 大小的内存
- new :在堆上分配了一个
int
类型的内存,大小是sizeof(int)
,并返回这块内存的地址。 - p:是一个指针,它存储的是动态分配内存的地址。
2.new[ ]语法:
cpp
int* arr = new int[5]; // 分配一个包含 5 个 int 元素的数组
-
new int[5]
:这里new
操作符动态地在堆上分配了一块足够容纳 5 个int
类型数据的内存区域。每个int
通常占用 4 字节(视平台而定),所以总共分配了 5 * 4 字节的内存。 -
返回值 :
new int[5]
返回的是这块内存的首地址 ,即第一个int
元素的地址。 -
这里的操作依然是在堆上分配内存,但与单个变量不同,分配的是一个数组 ,包含 5 个
int
类型的元素,arr
是指向这个数组的指针。通过arr[0]
,arr[1]
...来访问数组的元素。
四、 delete
和 delete[]
:释放动态内存
动态分配的内存使用完之后,必须手动释放,否则会造成内存泄漏 。C++提供了 delete
和 delete[]
来释放内存。
delete
用来释放单个对象的内存。delete[]
用来释放数组的内存。
1.delete
语法:
cpp
delete p; // 释放通过 new 分配的内存
delete
释放的是 单个对象 的内存。
2.delete[ ]
语法:
cpp
delete[] arr; // 释放通过 new[] 分配的数组内存
delete[]
用来释放通过 new[]
分配的数组 的内存。如果用 new[]
分配了一个数组,就必须用 delete[]
来释放这个数组。
五、内存泄漏和如何避免
内存泄漏是指忘记释放 分配的内存,导致内存不断积累,最终耗尽系统内存。内存泄漏的一个常见情况是分配了内存后,没有调用 delete
或 delete[]
释放它。
1.示例:
cpp
int* p = new int; // 分配了内存
// 忘记调用 delete 释放内存
2.如何避免:
- 每次使用
new
或new[]
分配内存后,都要在不再使用内存时,使用delete
或delete[]
释放它。
六、nullptr
:空指针
在C++中,nullptr
用来表示指针不指向任何有效的内存。初始化指针 时,如果不指向任何地址,可以将它设置为 nullptr
,这样可以避免指针指向垃圾数据。
示例:
cpp
int* p = nullptr; // 指针 p 不指向任何地方
当指针不再需要时,可以将它设置为 nullptr
,并且要尽量避免让指针指向已经释放的内存,这样就避免了悬空指针的问题。
七、内存分配失败时的处理
new
和 new[]
在分配内存失败时会抛出bad_alloc
异常。如果你不想让程序因内存分配失败而崩溃,可以使用 new(nothrow)
,它不会抛出异常,而是返回 nullptr
。
示例:
cpp
int* p = new(std::nothrow) int[1000]; // 如果内存不足,p 为 nullptr
if (!p) {
std::cout << "Memory allocation failed!" << std::endl;
}
八、new
和 delete
与类构造函数/析构函数
当使用 new
分配内存时,如果是分配类对象的内存,会调用类的构造函数。如果使用 delete
释放内存,会调用类的析构函数。
cpp
class MyClass {
public:
MyClass() { std::cout << "MyClass 构造函数" << std::endl; }
~MyClass() { std::cout << "MyClass 析构函数" << std::endl; }
};
MyClass* ptr = new MyClass; // 调用构造函数
delete ptr; // 调用析构函数并释放内存
九、综合代码示例:
1.代码:
cpp
#include <iostream>
#include <new> // 用于 nothrow 和 bad_alloc
using namespace std;
// 定义一个简单的类,演示构造函数和析构函数的调用
class MyClass {
public:
MyClass() {
cout << "MyClass 构造函数" << endl;
}
~MyClass() {
cout << "MyClass 析构函数" << endl;
}
};
int main() {
// 1. 使用 new 分配内存
int* p = new(nothrow) int; // 在堆上分配一个 int 类型的内存
if (!p) {
cout << "内存分配失败!" << endl;
return 1; // 如果内存分配失败,退出程序
}
*p = 10; // 给 p 指向的内存赋值
cout << "p 指向的值: " << *p << endl;
// 2. 使用 new[] 分配内存
int* arr = new(nothrow) int[5]; // 在堆上分配一个包含 5 个元素的 int 数组
if (!arr) {
cout << "数组内存分配失败!" << endl;
delete p; // 释放之前分配的单个内存
return 1;
}
// 给数组赋值并打印
for (int i = 0; i < 5; ++i) {
arr[i] = (i + 1) * 10; // 给数组元素赋值
cout << "arr[" << i << "] = " << arr[i] << endl;
}
// 3. 使用 delete 释放单个对象内存
delete p; // 释放之前分配的单个 int 类型的内存
p = nullptr; // 将指针设为 nullptr,避免悬空指针
// 4. 使用 delete[] 释放数组内存
delete[] arr; // 释放之前分配的数组内存
arr = nullptr; // 将数组指针设为 nullptr
// 5. 使用 new 来分配类对象的内存
MyClass* myObj = new(nothrow) MyClass(); // 分配 MyClass 类型的对象
if (!myObj) {
cout << "类对象内存分配失败!" << endl;
return 1;
}
// 6. 使用 delete 释放类对象的内存
delete myObj; // 会调用 MyClass 的析构函数
return 0;
}
2.输出:
cpp
MyClass 构造函数
p 指向的值: 10
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
MyClass 析构函数
3.代码讲解:
1.new
和 new[]
:
new std::nothrow int;
:分配一个int
类型的单个对象,并在内存分配失败时不会抛出异常,而是返回nullptr
。new std::nothrow int[5];
:分配一个包含 5 个int
元素的数组,并且同样使用std::nothrow
来避免抛出异常。
2.内存分配失败的处理:
- 使用
if (!p)
和if (!arr)
来检查内存分配是否成功。如果失败,就输出错误信息并退出程序。
3.释放内存:
delete p;
释放由new
分配的单个对象内存。delete[] arr;
释放由new[]
分配的数组内存。
4.使用 nullptr
:
- 在释放内存后,将指针设置为
nullptr
,避免指针悬空的问题。
5.构造函数与析构函数:
new MyClass()
:当new
分配一个类对象的内存时,会调用该类的构造函数。delete myObj;
:当调用delete
时,会自动调用类的析构函数来释放资源。
十、总结:
new
:动态分配单个对象的内存。new[]
:动态分配一个数组的内存。delete
:释放由new
分配的内存。delete[]
:释放由new[]
分配的数组内存。nullptr
:表示指针不指向任何有效内存。- 每次动态分配内存后,都要记得释放,以避免内存泄漏。
十一、小贴士:
- 不要忘记释放动态分配的内存。
- 使用
new[]
分配数组时,一定要用delete[]
来释放。 - 使用
nullptr
可以帮助管理指针,避免错误使用。