C++-------动态内存管理

发布于:2025-02-11 ⋅ 阅读:(58) ⋅ 点赞:(0)

堆 - 栈图

在 C++ 中,栈和堆是两块不同的内存区域,有着不同的特性:

  • 栈(Stack)
    • 是一块连续的内存区域,由编译器自动管理。函数调用时,局部变量、函数参数等都会被压入栈中,函数结束时,相应的数据会按顺序弹出栈,内存自动回收。例如:
void func() {
    int localVar = 10; // localVar 存储在栈上
}
- 栈内存分配和释放速度快,因为它的管理机制简单、规整,但大小通常有限制,取决于编译器和操作系统。
  • 堆(Heap)
    • 是一块相对较大、不连续的内存区域,用于动态分配内存。程序员使用 newmalloc 等操作符来申请堆内存,使用完毕后需手动用 deletefree 释放,否则容易造成内存泄漏。例如:
int* ptr = new int; // 在堆上分配内存
*ptr = 20;
delete ptr; 
- 堆内存的优点是灵活性高,可以按需分配较大的内存块,但分配和释放速度比栈慢,且管理难度较大。

下面是一个简单的堆 - 栈示意图:

内存区域 管理方式 分配速度 内存连续性 典型用途
编译器自动管理 连续 局部变量、函数参数
程序员手动管理 不连续 动态分配大内存块、对象生存期控制

拷贝对象

  • 浅拷贝
    • 简单地复制对象的值,对于指针成员,只是复制指针的值,而不是指针指向的数据。这意味着多个对象的指针成员会指向同一块内存。例如:
class MyClass {
public:
    int* data;
    MyClass() {
        data = new int(10);
    }
    MyClass(const MyClass& other) {
        data = other.data;
    }
};
- 浅拷贝的问题在于,如果一个对象释放了指针指向的内存,其他共享该指针的对象就会出现悬空指针。

在这里插入图片描述

  • 深拷贝
    • 不仅复制对象的值,还递归地复制指针成员指向的数据。这样每个对象都有独立的、完整的数据副本。例如:
class MyClass {
public:
    int* data;
    MyClass() {
        data = new int(10);
    }
    MyClass(const MyClass& other) {
        data = new int(*other.data);
    }
};
- 深拷贝确保了对象间的独立性,但代价是额外的内存和时间开销,因为要复制深层次的数据结构。
  • 赋值和拷贝构造函数
    • 拷贝构造函数:用于创建一个新对象,初始化为已有对象的副本,语法形式为 ClassName(const ClassName& other)。上面的 MyClass(const MyClass& other) 就是拷贝构造函数示例。
    • 赋值运算符:用于将一个已有对象的值赋给另一个已存在的对象,语法形式为 ClassName& operator=(const ClassName& other)。例如:
class MyClass {
public:
    int* data;
    MyClass& operator=(const MyClass& other) {
        if (this!= &other) {
            delete data;
            data = new int(*other.data);
        }
        return *this;
    }
};

关键字 const 的使用

  • 修饰变量const 修饰的变量值不能被修改,一旦初始化就固定了。例如:const int num = 10;num 在后续代码中无法重新赋值。
  • 修饰指针
    • const int* ptr:指针指向的内容是常量,不能通过该指针修改所指的值,但指针本身可以指向其他地方。
    • int* const ptr:指针本身是常量,不能指向其他地方,但可以修改指针指向的值。
    • const int* const ptr:指针和它指向的内容都是常量。
  • 修饰函数参数:表示函数不会修改传入的参数,这有助于代码可读性和编译器优化,同时防止意外修改。例如:void print(const std::string& str),函数内部不能修改 str
  • 修饰函数返回值:当返回值为对象或指针时,用 const 修饰可以防止调用者意外修改返回值,常用于返回类的成员变量。

charstack 类的效率

  • 空间效率
    • 如果采用动态数组实现 charstack 类,如之前代码示例,可能会存在一定的空间浪费。因为动态数组每次扩容时,通常是翻倍扩容,扩容后的部分在短时间内可能闲置。例如,初始容量为 10,扩容到 20 后,可能栈里只有 11 个元素,剩余 9 个空间未使用。
  • 时间效率
    • 入栈(push):平均情况下,入栈操作时间复杂度为 O ( 1 ) O(1) O(1),但在扩容时,需要复制旧数组元素到新数组,时间复杂度变为 O ( n ) O(n) O(n),其中 n n n 是当前栈中元素个数 。
    • 出栈(pop):时间复杂度为 O ( 1 ) O(1) O(1),只需调整栈顶指针即可完成出栈操作。
    • 判断空满:时间复杂度均为 O ( 1 ) O(1) O(1),只涉及简单的指针或索引比较。如果想进一步提升效率,可以考虑采用链表结构实现栈,避免数组扩容带来的额外开销,但链表实现会增加指针操作的复杂性。
      在这里插入图片描述

网站公告

今日签到

点亮在社区的每一天
去签到