• C++
  • C++ 动态内存分配学习笔记教程

  • @ 2025-8-10 15:04:01

C++ 动态内存分配学习笔记教程

1. 动态内存分配的基本概念

动态内存分配是指在程序运行时根据需要灵活地分配和释放内存的机制。与静态内存分配(如全局变量、局部变量)相比,它具有以下特点:

  • 灵活性:内存大小可在运行时确定,而非编译时固定。
  • 持久性:动态分配的内存不会随函数调用结束而自动释放,需手动管理。
  • 按需分配:仅在需要时分配内存,提高内存使用效率。

动态内存分配主要用于处理大小不确定的数据(如用户输入的字符串、动态增长的数组等),是C++中实现复杂数据结构(如链表、树、图)的基础。

2. 动态内存分配的核心操作符

C++提供了两个核心操作符用于动态内存管理:new(分配内存)和delete(释放内存)。

2.1 new 操作符:分配内存

new的作用是向系统申请一块指定类型的内存,并返回该内存的地址。

基本语法

// 分配单个元素的内存
数据类型* 指针变量 = new 数据类型;

// 分配数组的内存
数据类型* 指针变量 = new 数据类型[数组大小];

示例:分配单个元素

#include <iostream>

int main() {
    // 动态分配一个int类型的内存,初始值不确定
    int* numPtr = new int;
    
    // 为分配的内存赋值
    *numPtr = 100;
    std::cout << "值: " << *numPtr << std::endl; // 输出:100
    std::cout << "地址: " << numPtr << std::endl; // 输出:内存地址(如0x7f8a9b0)
    
    // 释放内存(必须做!)
    delete numPtr;
    numPtr = nullptr; // 避免野指针(指向已释放内存的指针)
    
    return 0;
}

示例:分配数组

#include <iostream>

int main() {
    int n;
    std::cout << "请输入数组大小: ";
    std::cin >> n;
    
    // 动态分配大小为n的int数组
    int* arr = new int[n];
    
    // 为数组赋值
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }
    
    // 打印数组
    std::cout << "数组元素: ";
    for (int i = 0; i < n; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    
    // 释放数组内存(注意使用[])
    delete[] arr;
    arr = nullptr; // 避免野指针
    
    return 0;
}

2.2 delete 操作符:释放内存

delete用于将动态分配的内存归还给系统,避免内存泄漏(已分配的内存不再使用却未释放)。

注意事项

  • 释放单个元素:delete 指针;
  • 释放数组:delete[] 指针;(必须带[],否则可能导致部分内存未释放)
  • 同一块内存不能释放两次(会导致程序崩溃)
  • 释放后需将指针置为nullptr(避免成为野指针)

3. 动态内存分配的初始化

new操作符支持在分配内存时直接初始化,语法如下:

3.1 单个元素的初始化

// C++98风格:值初始化
int* p1 = new int(5); // 初始化为5

// C++11及以上:列表初始化
int* p2 = new int{10}; // 初始化为10
std::string* strPtr = new std::string{"Hello"}; // 字符串初始化为"Hello"

3.2 数组的初始化

#include <iostream>

int main() {
    // C++11及以上支持数组列表初始化
    int* arr1 = new int[3]{1, 2, 3}; // 初始化3个元素
    int* arr2 = new int[5]{}; // 所有元素初始化为0(值初始化)
    
    std::cout << "arr1: ";
    for (int i = 0; i < 3; i++) {
        std::cout << arr1[i] << " "; // 输出:1 2 3
    }
    
    std::cout << "\narr2: ";
    for (int i = 0; i < 5; i++) {
        std::cout << arr2[i] << " "; // 输出:0 0 0 0 0
    }
    
    delete[] arr1;
    delete[] arr2;
    return 0;
}

4. 动态内存分配的常见问题

4.1 内存泄漏

内存泄漏指动态分配的内存不再使用却未被释放,导致系统内存逐渐耗尽。

示例:内存泄漏

#include <iostream>

void leakMemory() {
    int* data = new int[1000]; // 分配内存
    // 未使用delete[]释放内存,函数结束后指针消失,内存无法回收
}

int main() {
    // 多次调用导致大量内存泄漏
    for (int i = 0; i < 100000; i++) {
        leakMemory();
    }
    return 0;
}

避免方法

  • 每个new对应一个delete,每个new[]对应一个delete[]
  • 优先使用智能指针(见第7节)而非裸指针
  • 养成函数出口前释放内存的习惯

4.2 野指针

野指针指指向已释放内存或非法内存地址的指针,访问野指针会导致程序崩溃或数据错误。

示例:野指针问题

#include <iostream>

int main() {
    int* ptr = new int(10);
    delete ptr; // 释放内存
    // ptr现在是野指针!
    
    *ptr = 20; // 错误:访问已释放的内存,行为未定义
    std::cout << *ptr << std::endl; // 可能崩溃或输出垃圾值
    
    return 0;
}

避免方法

  • 释放内存后立即将指针置为nullptr
  • 不使用未初始化的指针
  • 避免指针指向栈内存(函数返回后栈内存自动释放)

4.3 重复释放

对同一块内存释放两次会导致程序崩溃。

示例:重复释放

int* ptr = new int;
delete ptr;
delete ptr; // 错误:重复释放,程序崩溃

5. 动态内存与结构体/类

动态内存分配常与结构体或类结合使用,用于创建动态对象。

示例:动态结构体对象

#include <iostream>
#include <string>

struct Student {
    std::string name;
    int age;
    
    void display() {
        std::cout << "姓名: " << name << ", 年龄: " << age << std::endl;
    }
};

int main() {
    // 动态分配单个结构体对象
    Student* stu = new Student{"张三", 18};
    stu->display(); // 输出:姓名: 张三, 年龄: 18
    
    // 动态分配结构体数组
    Student* stuArr = new Student[2]{
        {"李四", 19},
        {"王五", 20}
    };
    for (int i = 0; i < 2; i++) {
        stuArr[i].display();
    }
    
    // 释放内存
    delete stu;
    delete[] stuArr;
    stu = nullptr;
    stuArr = nullptr;
    
    return 0;
}

6. 动态内存与函数

动态内存可以在函数间传递,但需注意内存所有权的管理(谁负责释放)。

示例:函数返回动态分配的内存

#include <iostream>

// 函数返回动态分配的数组
int* createArray(int size) {
    int* arr = new int[size];
    for (int i = 0; i < size; i++) {
        arr[i] = i;
    }
    return arr; // 返回指针,调用者需负责释放
}

int main() {
    int* myArr = createArray(5);
    
    for (int i = 0; i < 5; i++) {
        std::cout << myArr[i] << " "; // 输出:0 1 2 3 4
    }
    
    delete[] myArr; // 必须释放!
    return 0;
}

最佳实践

  • 明确函数返回的动态内存由调用者释放
  • 避免在函数中释放其他函数分配的内存(除非明确约定)
  • 复杂场景下使用智能指针传递所有权

7. 智能指针:自动管理动态内存

C++11引入了智能指针(smart pointers),它能自动释放所指向的内存,大幅减少内存管理错误。常用的智能指针有:

  • std::unique_ptr:独占所有权,同一时间只能有一个指针指向内存
  • std::shared_ptr:共享所有权,多个指针可指向同一内存,引用计数为0时自动释放

7.1 std::unique_ptr

#include <iostream>
#include <memory> // 智能指针头文件

int main() {
    // 创建unique_ptr,管理动态分配的int
    std::unique_ptr<int> uPtr(new int(5));
    std::cout << *uPtr << std::endl; // 输出:5
    
    // 或使用更安全的make_unique(C++14及以上)
    auto uPtr2 = std::make_unique<int>(10);
    std::cout << *uPtr2 << std::endl; // 输出:10
    
    // 管理动态数组(需指定数组类型)
    auto arrPtr = std::make_unique<int[]>(3);
    arrPtr[0] = 1;
    arrPtr[1] = 2;
    arrPtr[2] = 3;
    
    // 无需手动释放!离开作用域时自动调用delete/delete[]
    return 0;
}

7.2 std::shared_ptr

#include <iostream>
#include <memory>

int main() {
    // 创建shared_ptr
    auto sPtr = std::make_shared<std::string>("Hello");
    std::cout << "引用计数: " << sPtr.use_count() << std::endl; // 输出:1
    
    // 复制指针,引用计数增加
    auto sPtr2 = sPtr;
    std::cout << "引用计数: " << sPtr.use_count() << std::endl; // 输出:2
    
    // 重置指针,引用计数减少
    sPtr2.reset();
    std::cout << "引用计数: " << sPtr.use_count() << std::endl; // 输出:1
    
    // 最后一个指针离开作用域时自动释放内存
    return 0;
}

智能指针的优势

  • 自动释放内存,避免内存泄漏
  • 明确所有权,减少野指针和重复释放问题
  • 支持动态数组(unique_ptr<int[]>

8. 动态内存分配的实际应用案例

案例1:动态字符串处理

#include <iostream>
#include <cstring> // 用于strlen、strcpy

int main() {
    // 读取用户输入的字符串
    std::cout << "请输入字符串: ";
    char buffer[1024]; // 临时缓冲区
    std::cin.getline(buffer, 1024);
    
    // 根据输入长度动态分配内存
    int length = std::strlen(buffer);
    char* str = new char[length + 1]; // +1 用于存储结束符'\0'
    
    // 复制字符串
    std::strcpy(str, buffer);
    std::cout << "你输入的是: " << str << std::endl;
    
    delete[] str; // 释放内存
    return 0;
}

案例2:动态链表实现

#include <iostream>
#include <memory>

// 链表节点结构体
template <typename T>
struct Node {
    T data;
    std::unique_ptr<Node<T>> next; // 智能指针管理下一个节点
    
    Node(T val) : data(val), next(nullptr) {}
};

// 链表类
template <typename T>
class LinkedList {
private:
    std::unique_ptr<Node<T>> head; // 头节点
public:
    // 添加元素到头部
    void push(T val) {
        auto newNode = std::make_unique<Node<T>>(val);
        newNode->next = std::move(head); // 转移head的所有权
        head = std::move(newNode);
    }
    
    // 打印链表
    void print() {
        Node<T>* current = head.get(); // 获取裸指针
        while (current != nullptr) {
            std::cout << current->data << " -> ";
            current = current->next.get();
        }
        std::cout << "nullptr" << std::endl;
    }
};

int main() {
    LinkedList<int> list;
    list.push(3);
    list.push(2);
    list.push(1);
    list.print(); // 输出:1 -> 2 -> 3 -> nullptr
    
    // 无需手动释放链表节点,智能指针自动处理
    return 0;
}

总结

动态内存分配是C++中处理灵活数据需求的核心技术,本教程主要涵盖:

  1. 基础操作new/delete分配释放单个元素,new[]/delete[]处理数组。
  2. 初始化:支持值初始化和列表初始化,提高代码安全性。
  3. 常见问题:内存泄漏、野指针、重复释放的成因与避免方法。
  4. 高级管理:智能指针(unique_ptr/shared_ptr)自动管理内存,减少人为错误。
  5. 实际应用:结合结构体、函数和数据结构(如链表)的实践案例。

掌握动态内存分配需要大量实践,建议优先使用智能指针和标准库容器(如std::vector),减少手动管理内存的场景,提高代码可靠性。

0 条评论

目前还没有评论...