====== 第九章 动态内存管理 ======
C++提供了强大的动态内存管理能力,允许程序在运行时分配和释放内存。这种灵活性对于处理大小可变的数据结构、实现复杂算法至关重要。本章将全面介绍C++动态内存管理的机制、最佳实践以及常见问题。
===== 9.1 为什么需要动态内存 =====
==== 9.1.1 静态内存的局限 ====
在程序编译时确定的内存分配称为静态内存分配,它有以下限制:
- **大小固定**:数组大小必须在编译时确定
- **生命周期**:变量的生命周期由作用域决定
- **灵活性差**:无法根据运行时需求调整内存大小
// 静态内存分配的问题
void problemExample() {
int arr[1000]; // 如果只需要10个元素,浪费内存
// 如果需要10000个元素,会溢出
// 无法根据用户输入决定数组大小
int n;
cin >> n;
// int arr2[n]; // 在标准C++中,这是不允许的(VLA是GCC扩展)
}
==== 9.1.2 动态内存的优势 ====
- 内存大小在运行时确定
- 内存的生命周期由程序员控制
- 可以实现复杂的数据结构(链表、树、图等)
- 提高内存使用效率
===== 9.2 new 和 delete 运算符 =====
==== 9.2.1 基本用法 ====
**分配单个对象**
#include
using namespace std;
int main() {
// 动态分配一个int
int *p = new int; // 分配内存,未初始化
int *p2 = new int(10); // 分配并初始化为10
cout << "*p2 = " << *p2 << endl; // 10
// 使用内存
*p = 100;
cout << "*p = " << *p << endl;
// 释放内存
delete p;
delete p2;
// 释放后置空指针(好习惯)
p = nullptr;
p2 = nullptr;
return 0;
}
**分配数组**
#include
using namespace std;
int main() {
int n;
cout << "输入数组大小: ";
cin >> n;
// 动态分配数组
int *arr = new int[n];
// 初始化数组
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
// 使用数组
cout << "数组元素: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 释放数组(注意使用delete[])
delete[] arr;
arr = nullptr;
return 0;
}
==== 9.2.2 new/delete 的注意事项 ====
#include
using namespace std;
void newDeleteTips() {
// 1. new和delete必须配对使用
int *p1 = new int(10);
delete p1; // 正确
// delete p1; // 错误!重复释放
// 2. new[]和delete[]必须配对
int *arr = new int[10];
// delete arr; // 错误!应该用delete[]
delete[] arr; // 正确
// 3. 不能释放非动态分配的内存
int x = 10;
int *p2 = &x;
// delete p2; // 运行时错误!
// 4. 内存分配可能失败
try {
// 在C++11之前,new失败会抛出bad_alloc异常
int *big = new int[1000000000];
delete[] big;
} catch (bad_alloc &e) {
cout << "内存分配失败: " << e.what() << endl;
}
// C++11之后可以使用nothrow版本
int *p3 = new (nothrow) int[1000000000];
if (p3 == nullptr) {
cout << "内存分配失败" << endl;
}
}
int main() {
newDeleteTips();
return 0;
}
===== 9.3 动态内存与类 =====
==== 9.3.1 类的动态成员 ====
当类包含动态分配的内存时,需要特别注意构造函数、析构函数和赋值运算符。
#include
#include
using namespace std;
class String {
private:
char *data;
int length;
public:
// 默认构造函数
String() {
data = new char[1];
data[0] = '\0';
length = 0;
cout << "默认构造函数" << endl;
}
// 带参数的构造函数
String(const char *str) {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
cout << "带参数构造函数: " << str << endl;
}
// 拷贝构造函数(深拷贝)
String(const String &other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "拷贝构造函数" << endl;
}
// 赋值运算符(深拷贝)
String &operator=(const String &other) {
cout << "赋值运算符" << endl;
if (this != &other) { // 检查自赋值
// 先释放原有内存
delete[] data;
// 分配新内存并复制
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
}
return *this;
}
// 析构函数
~String() {
cout << "析构函数: " << (data ? data : "null") << endl;
delete[] data;
}
// 显示字符串
void display() const {
cout << data << endl;
}
int getLength() const {
return length;
}
};
int main() {
String s1("Hello");
String s2 = s1; // 调用拷贝构造函数
String s3;
s3 = s1; // 调用赋值运算符
s1.display();
s2.display();
s3.display();
return 0;
}
==== 9.3.2 三/五法则 ====
如果一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,通常三者都需要自定义。C++11之后,移动构造函数和移动赋值运算符也需要考虑。
#include
using namespace std;
class Resource {
private:
int *data;
int size;
public:
// 构造函数
Resource(int s = 10) : size(s) {
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = i;
}
cout << "构造函数" << endl;
}
// 析构函数
~Resource() {
delete[] data;
cout << "析构函数" << endl;
}
// 拷贝构造函数(Rule of Three/Five)
Resource(const Resource &other) : size(other.size) {
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
cout << "拷贝构造函数" << endl;
}
// 拷贝赋值运算符
Resource &operator=(const Resource &other) {
cout << "拷贝赋值运算符" << endl;
if (this != &other) {
// 复制并交换惯用法
int *newData = new int[other.size];
for (int i = 0; i < other.size; i++) {
newData[i] = other.data[i];
}
delete[] data;
data = newData;
size = other.size;
}
return *this;
}
// C++11: 移动构造函数
Resource(Resource &&other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
cout << "移动构造函数" << endl;
}
// C++11: 移动赋值运算符
Resource &operator=(Resource &&other) noexcept {
cout << "移动赋值运算符" << endl;
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
Resource r1(5);
r1.print();
Resource r2 = r1; // 拷贝构造
Resource r3;
r3 = r1; // 拷贝赋值
Resource r4 = std::move(r1); // 移动构造
return 0;
}
===== 9.4 常见的动态内存问题 =====
==== 9.4.1 内存泄漏 ====
内存泄漏是指程序动态分配的内存没有被释放,导致可用内存逐渐减少。
#include
using namespace std;
// 内存泄漏示例
void memoryLeak() {
int *p = new int[1000];
// 使用p...
// 忘记delete[] p;
}
// 更隐蔽的内存泄漏
void hiddenLeak() {
int *p = new int[100];
if (someCondition()) {
return; // 提前返回,导致内存泄漏
}
delete[] p;
}
// 循环中的内存泄漏
void loopLeak() {
for (int i = 0; i < 1000000; i++) {
int *p = new int;
// 使用p...
// 忘记delete p;
}
}
// 正确的做法
void noLeak() {
int *p = new int[1000];
try {
// 使用p...
} catch (...) {
delete[] p; // 异常时也要释放
throw;
}
delete[] p;
}
// 使用RAII避免内存泄漏(推荐)
void raiiApproach() {
// 使用智能指针,自动管理内存
// unique_ptr p(new int[1000]);
// 或使用vector
// vector vec(1000);
}
==== 9.4.2 重复释放 ====
void doubleFree() {
int *p = new int(10);
delete p;
// delete p; // 错误!重复释放,未定义行为
}
void anotherDoubleFree() {
int *p1 = new int(10);
int *p2 = p1;
delete p1;
// delete p2; // 错误!p2和p1指向同一块内存
}
==== 9.4.3 使用已释放的内存(悬挂指针) ====
void danglingPointer() {
int *p = new int(10);
delete p;
// p现在是悬挂指针
// *p = 20; // 错误!使用已释放的内存
p = nullptr; // 释放后置空是好习惯
}
int *returnDangling() {
int *p = new int(10);
delete p;
return p; // 返回悬挂指针!
}
===== 9.5 动态内存的最佳实践 =====
==== 9.5.1 RAII原则 ====
RAII(Resource Acquisition Is Initialization)是C++中管理资源的核心原则。
#include
using namespace std;
// RAII类示例
class FileHandler {
private:
FILE *file;
public:
FileHandler(const char *filename, const char *mode) {
file = fopen(filename, mode);
if (!file) {
throw runtime_error("Failed to open file");
}
}
~FileHandler() {
if (file) {
fclose(file);
}
}
// 禁止拷贝
FileHandler(const FileHandler &) = delete;
FileHandler &operator=(const FileHandler &) = delete;
// 允许移动
FileHandler(FileHandler &&other) noexcept : file(other.file) {
other.file = nullptr;
}
void write(const char *data) {
if (file) {
fprintf(file, "%s", data);
}
}
};
void useFile() {
FileHandler file("test.txt", "w");
file.write("Hello, RAII!");
// 文件会自动关闭,即使发生异常
}
==== 9.5.2 使用智能指针 ====
现代C++推荐使用智能指针管理动态内存。
#include
#include
using namespace std;
void smartPointerDemo() {
// unique_ptr: 独占所有权
unique_ptr up1(new int(10));
cout << *up1 << endl;
// 更好的创建方式(C++14)
auto up2 = make_unique(20);
// 转移所有权
unique_ptr up3 = move(up1);
// up1现在为nullptr
// 数组版本
auto arr = make_unique(10);
for (int i = 0; i < 10; i++) {
arr[i] = i;
}
}
void sharedPointerDemo() {
// shared_ptr: 共享所有权,引用计数
shared_ptr sp1 = make_shared(100);
cout << "引用计数: " << sp1.use_count() << endl; // 1
{
shared_ptr sp2 = sp1;
cout << "引用计数: " << sp1.use_count() << endl; // 2
}
cout << "引用计数: " << sp1.use_count() << endl; // 1
// 当引用计数为0时,内存自动释放
}
class Node;
using NodePtr = shared_ptr;
class Node {
public:
int data;
// weak_ptr用于打破循环引用
weak_ptr parent;
NodePtr child;
Node(int d) : data(d) {
cout << "Node " << d << " created" << endl;
}
~Node() {
cout << "Node " << data << " destroyed" << endl;
}
};
void weakPointerDemo() {
NodePtr parent = make_shared(1);
NodePtr child = make_shared(2);
parent->child = child;
child->parent = parent; // weak_ptr不会增加引用计数
}
===== 9.6 内存池与自定义分配器 =====
==== 9.6.1 内存池的概念 ====
内存池预先分配一大块内存,然后从中分配小块内存,减少频繁的系统调用。
#include
#include
using namespace std;
class MemoryPool {
private:
struct Block {
char data[64]; // 固定大小的块
};
vector pool;
vector available;
public:
MemoryPool(size_t size = 100) {
for (size_t i = 0; i < size; i++) {
Block *block = new Block();
pool.push_back(block);
available.push_back(block);
}
}
~MemoryPool() {
for (auto block : pool) {
delete block;
}
}
void *allocate() {
if (available.empty()) {
throw runtime_error("Pool exhausted");
}
Block *block = available.back();
available.pop_back();
return block;
}
void deallocate(void *ptr) {
available.push_back(static_cast(ptr));
}
bool isFull() const {
return available.empty();
}
};
int main() {
MemoryPool pool(10);
vector allocated;
for (int i = 0; i < 5; i++) {
allocated.push_back(pool.allocate());
}
for (auto ptr : allocated) {
pool.deallocate(ptr);
}
return 0;
}
===== 9.7 综合示例 =====
==== 实现一个动态数组类(完整版) ====
#include
#include
using namespace std;
template
class DynamicArray {
private:
T *data;
int size;
int capacity;
void resize(int newCapacity) {
T *newData = new T[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
DynamicArray(int initCapacity = 4)
: size(0), capacity(initCapacity) {
data = new T[capacity];
}
// 拷贝构造函数
DynamicArray(const DynamicArray &other)
: size(other.size), capacity(other.capacity) {
data = new T[capacity];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
// 赋值运算符
DynamicArray &operator=(const DynamicArray &other) {
if (this != &other) {
T *newData = new T[other.capacity];
for (int i = 0; i < other.size; i++) {
newData[i] = other.data[i];
}
delete[] data;
data = newData;
size = other.size;
capacity = other.capacity;
}
return *this;
}
// 移动构造函数
DynamicArray(DynamicArray &&other) noexcept
: data(other.data), size(other.size), capacity(other.capacity) {
other.data = nullptr;
other.size = 0;
other.capacity = 0;
}
// 移动赋值运算符
DynamicArray &operator=(DynamicArray &&other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
capacity = other.capacity;
other.data = nullptr;
other.size = 0;
other.capacity = 0;
}
return *this;
}
~DynamicArray() {
delete[] data;
}
void push_back(const T &value) {
if (size >= capacity) {
resize(capacity * 2);
}
data[size++] = value;
}
void pop_back() {
if (size > 0) {
size--;
}
}
T &operator[](int index) {
if (index < 0 || index >= size) {
throw out_of_range("Index out of range");
}
return data[index];
}
const T &operator[](int index) const {
if (index < 0 || index >= size) {
throw out_of_range("Index out of range");
}
return data[index];
}
int getSize() const { return size; }
int getCapacity() const { return capacity; }
bool empty() const { return size == 0; }
void clear() { size = 0; }
T *begin() { return data; }
T *end() { return data + size; }
void print() const {
cout << "[";
for (int i = 0; i < size; i++) {
cout << data[i];
if (i < size - 1) cout << ", ";
}
cout << "]" << endl;
}
};
int main() {
DynamicArray arr;
for (int i = 1; i <= 10; i++) {
arr.push_back(i);
}
cout << "数组内容: ";
arr.print();
cout << "大小: " << arr.getSize() << endl;
cout << "容量: " << arr.getCapacity() << endl;
arr[5] = 100;
cout << "修改后: ";
arr.print();
DynamicArray arr2 = arr;
cout << "拷贝后: ";
arr2.print();
return 0;
}
===== 9.8 习题 =====
==== 基础题 ====
1. **new/delete基础**
编写程序,使用new动态分配一个int、一个double和一个包含10个元素的int数组,然后使用delete/delete[]释放它们。
2. **动态字符串**
实现一个函数,接收一个C风格字符串,返回其副本(使用动态内存分配)。调用者负责释放返回的内存。
char *strCopy(const char *source);
3. **二维动态数组**
编写程序,动态分配一个m×n的二维数组,初始化并打印,然后正确释放内存。
==== 进阶题 ====
4. **简单矩阵类**
实现一个Matrix类,支持:
- 动态分配二维矩阵
- 矩阵加法
- 矩阵乘法
- 正确的内存管理(深拷贝)
5. **实现String类**
完善本章中的String类,添加以下功能:
- 字符串连接(+运算符)
- 字符串比较
- 获取子串
- 查找字符/子串
6. **对象池**
实现一个简单的对象池类,用于管理某一类型对象的分配和回收,避免频繁的new/delete操作。
==== 综合题 ====
7. **内存泄漏检测**
编写一个简单的内存分配跟踪系统,能够:
- 重载new/delete运算符
- 跟踪所有分配和释放操作
- 程序结束时报告未释放的内存
8. **自定义向量类**
基于本章的DynamicArray模板类,实现一个完整的Vector类,功能类似于std::vector:
- push_back/pop_back
- insert/erase
- reserve/capacity
- begin/end迭代器
- emplace_back
9. **链表内存管理**
实现一个双向链表类,使用自定义内存池管理节点内存,而不是直接使用new/delete。
==== 思考题 ====
10. **深入分析**
- 解释new和malloc的区别
- 为什么delete[]需要知道数组大小?(提示:数组长度通常存储在内存中)
- 分析placement new的用途和使用场景
- 讨论在哪些情况下应该使用动态内存,哪些情况下应该使用栈内存
===== 9.9 小结 =====
本章深入学习了C++的动态内存管理:
- **new/delete** 是C++动态内存管理的基础运算符
- **数组分配** 需要使用new[]和delete[]配对
- **类的内存管理** 需要遵循三/五法则,正确实现构造函数、析构函数和赋值运算符
- **内存问题** 包括内存泄漏、重复释放和悬挂指针,需要格外注意
- **RAII原则** 是管理资源的最佳实践
- **智能指针**(unique_ptr、shared_ptr、weak_ptr)是现代C++管理动态内存的推荐方式
掌握动态内存管理是成为优秀C++程序员的关键,正确的内存管理习惯可以避免大量难以调试的错误。