31.堆上分配内存

31.内存分配-堆上分配内存


31.1 知识点

知识回顾 堆是什么

堆(Heap)是程序运行时用于动态分配内存的区域。
它存储的是动态分配的内存数据,堆内存由程序员手动管理,需要主动分配并在使用后释放。
堆上的内存生命周期不固定,由程序员决定何时分配和释放。

如何在堆上分配内存

在堆上分配内存,通常使用 new 关键字。
new 会在堆上为变量或数组分配内存,并返回该内存的地址。

单个对象分配

语法:

变量类型* 指针名 = new 变量类型(初始值(可选));

作用:
new 的作用是分配内存(类似开房,分配内存房间、门牌号和具体的内存容量)。

注意:

  • 指针变量本质上还是在栈上,只是其中存储的地址是堆上的地址。
  • 指针变量本身由系统管理,但需要手动管理它指向的地址。

示例代码:

// 在堆上分配内存并初始化为10
int* p = new int(10);  
cout << "p 存储的地址: " << p << endl;  // 输出指针 p 存储的地址(堆上的地址)
cout << "p 指向的值: " << *p << endl;  // 输出指针 p 指向的值,即 10

// 修改堆内存中存储的值
*p = 99;  
cout << "修改后,p 指向的值: " << *p << endl;  // 输出修改后的值,即 99

// 为其他类型分配堆内存
char* c = new char('A');  
bool* b = new bool(true);

分配数组

语法:

数组类型* 指针名 = new 数组类型[容量] {初始值(可选)};

作用:
new 的作用是分配一块连续的内存空间,用于存储数组的元素。

示例代码:

// 在堆上分配一个大小为4的整数数组,并初始化为 {1, 2, 3, 4}
int* arr = new int[4] {1, 2, 3, 4};  
cout << "arr[0]: " << arr[0] << endl;  // 输出数组第一个元素
cout << "arr[1]: " << arr[1] << endl;  // 输出数组第二个元素

// 为二维数组分配内存
int(*arr2)[3] = new int[2][3]{1, 2, 3, 4, 5, 6};  
cout << "arr2[0][0]: " << arr2[0][0] << endl;  // 输出二维数组的元素
cout << "arr2[1][1]: " << arr2[1][1] << endl;  // 输出二维数组的元素

如何在堆上释放内存

使用 delete 关键字即可在堆上释放内存。

单个对象的释放

语法:

delete 指针变量;

作用:
delete 的作用是释放通过 new 分配的动态内存。
注意:

  • delete 并不销毁指针变量本身,而是销毁指针变量指向的堆内存。

示例代码:

// 释放指针 p 指向的内存
delete p;  
p = nullptr;  // 防止悬挂指针

delete c;  
c = nullptr;

delete b;  
b = nullptr;

数组的释放

语法:

delete[] 数组变量;

注意:

  • 使用 new[] 分配数组时,必须使用 delete[] 释放内存。

示例代码:

// 释放动态分配的数组
delete[] arr;  
arr = nullptr;  // 防止悬挂指针

delete[] arr2;  
arr2 = nullptr;

31.2 知识点代码

Lesson31_内存分配_堆上分配内存.cpp

#include <iostream>
using namespace std;
int main()
{
    std::cout << "堆上分配内存\n";

    #pragma region 知识回顾 堆是什么

    // 堆(Heap)是程序运行时用于动态分配内存的区域。
    // 它存储的是动态分配的内存数据。
    // 堆内存由程序员手动管理,我们需要主动分配并在使用后释放。
    // 堆上的内存生命周期不固定,由程序员决定何时分配和释放。

    #pragma endregion

    #pragma region 知识点一 如何在堆上分配内存

    // 在堆上分配内存,通常使用 `new` 关键字。
    // new 会在堆上为变量或数组分配内存,并返回该内存的地址。

    //1.单个对象分配
    //语法:变量类型* 指针名 = new 变量类型(初始值(可选))
    //作用:new 的作用就是“开房”(分配内存房间,门牌号,和具体的内存容量)
    //注意:
    //指针变量本质上还是在栈上,只是其中存储的地址是堆上的地址
    //指针变量本身还是由系统管理,我们需要手动管理的是他指向的地址

    // 这里的 `new` 动态地在堆上分配了一个内存空间,并初始化为10。
    int* p = new int(10);  // 在堆上分配内存并初始化为10
    cout << "p 存储的地址: " << p << endl;  // 输出指针 p 存储的地址(堆上的地址)
    cout << "p 指向的值: " << *p << endl;  // 输出指针 p 指向的值,即 10

    // 修改堆内存中存储的值
    *p = 99;  // 将 p 指向的内存中的值修改为 99
    cout << "修改后,p 指向的值: " << *p << endl;  // 输出修改后的值,即 99

    // 为其他类型分配堆内存
    char* c = new char('A');  // 在堆上分配内存并初始化为 'A'
    bool* b = new bool(true);  // 在堆上分配内存并初始化为 true


    //2.分配数组
    //语法:数组类型* 指针名 = new 数组类型[容量] {初始值(可选)};
    //作用:new的作用同样是“开房”(分配了n个连续的内存房间,具体是否初始化房间里的值,自己定)
    //这时 指针arr相当于就是数组名

    // 这里我们在堆上分配了一个大小为 4 的数组,并初始化为 {1, 2, 3, 4}。
    int* arr = new int[4] {1, 2, 3, 4};  // 在堆上分配一个整数数组
    cout << "arr[0]: " << arr[0] << endl;  //1 输出数组第一个元素
    cout << "arr[1]: " << arr[1] << endl;  //2 输出数组第二个元素

    // 为二维数组分配内存
    int(*arr2)[3] = new int[2][3]{ 1, 2, 3, 4, 5, 6 };  // 在堆上分配一个二维数组
    cout << "arr2[0][0]: " << arr2[0][0] << endl;  //1 输出二维数组的元素
    cout << "arr2[1][1]: " << arr2[1][1] << endl; //5 输出二维数组的元素

    #pragma endregion

    #pragma region 知识点二 如何在堆上释放内存

    //使用delete关键字 即可在堆上释放内存

    //语法:
    //delete 指针变量 —— 配对 new
    //delete[] 数组变量 —— 配对 new[]

    //作用:
    //delete的作用就是释放通过new在堆上分配的动态内存

    //注意:
    //delete的作用并不是销毁指针变量本身,而是销毁指针变量指向的堆内存

    // 1. 释放单个对象的内存
    delete p;  // 释放指针 p 指向的内存
    p = nullptr;  // 将指针 p 设置为空,防止悬挂指针

    delete c;  // 释放指针 c 指向的内存
    c = nullptr;

    delete b;  // 释放指针 b 指向的内存
    b = nullptr;

    // 2. 释放动态分配的数组
    // 注意:使用 `new[]` 分配数组时,必须使用 `delete[]` 来释放内存。
    delete[] arr;  // 释放数组 arr 占用的内存
    arr = nullptr;  // 将指针 arr 设置为空,防止悬挂指针

    delete[] arr2;  // 释放二维数组 arr2 占用的内存
    arr2 = nullptr;  // 将指针 arr2 设置为空,防止悬挂指针

    #pragma endregion
}

31.3 练习题

代码解析与实现

请问这句代码是在做什么?是否存在问题

int i = (int)(new int(100));
  • 解释

    • 这行代码存在问题。我们不能将 new 运算符分配的内存(一个指针)直接强制转换为 int 类型。
    • new int(100) 返回的是一个指向堆上整数的指针,而 i 是一个 int 类型的变量,无法存储指针。
  • 等价代码

int* ptr = new int(100); // 用指针来接收堆内存的地址
cout << ptr << endl; // 打印堆内存地址
int i = (int)ptr;//把指针变量强行转成int 指针8字节 int4字节 会有问题
cout << i << endl;//地址丢失 不准确的值

有一个在堆上的分配的数组,容量为5,其中存储了 {1, 2, 3, 4, 5}

现在希望将数组容量扩容到10,并且保留原数组中的内容,请实现该功能。

  • 说明

    • 静态数组不能满足动态扩容。
    • 静态数组(栈上的数组)无法直接赋值给另一个数组,因为数组名是指针,但它的内存地址是固定的。
  • 示例错误代码

int array[5] = {1, 2, 3, 4, 5};
int array2[10];
array = array2; // 错误!静态数组不能直接赋值
  • 正确实现代码
// 动态在堆上分配一个大小为 5 的数组,并初始化为 {1, 2, 3, 4, 5}
int* array = new int[5]{1, 2, 3, 4, 5};

// 创建一个新的数组,大小为 10
int* array2 = new int[10];  // 动态分配一个大小为 10 的数组

// 将原数组的内容搬到新的数组中
for (int i = 0; i < 5; i++) {
    array2[i] = array[i];  // 复制原数组的元素
}

// 扩展新的数组,添加新的值
array2[5] = 6;
array2[6] = 7;
array2[7] = 8;
array2[8] = 9;
array2[9] = 10;

// 用完后释放原数组的内存
delete[] array;  // 释放原数组的内存
array = array2;  // 让原指针指向新的数组

// 打印新数组中的所有元素
for (int i = 0; i < 10; i++) {
    cout << array[i] << endl;  // 输出数组中的每个元素
}

// 释放新数组的内存
delete[] array;  // 释放数组内存
array = nullptr;  // 将指针设置为 null,避免悬挂指针
array2 = nullptr; // 将指针设置为 null

请实现一个返回指针的函数,用于返回两个数相加的和。

  • 实现代码
int* add(int a, int b)
{
    // 栈上分配,语句块执行结束后会被系统自动释放
    // 如果在外部使用 c 对应的地址,会出现问题,因为它可能已被释放
    /*
    int c = a + b;
    return &c;
    */

    // 在堆上分配内存,外部需要手动管理该内存
    return new int(a + b);
}

int* c = add(5, 6);  // 调用 add 函数,返回值存储在指针 c 中
cout << c << endl;  // 输出指针 c 的地址
cout << *c << endl; // 输出指针 c 所指向的值,即 11
cout << *c << endl; // 可以再次输出11 之前返回是局部变量地址 会被释放掉 下次输入可能会不准确

// 注意:当返回堆上的内存时,我们需要手动释放它
// 这里必须在使用完后通过 delete 来释放内存,避免内存泄漏
delete c;   // 释放 c 指向的堆内存
c = nullptr; // 防止悬挂指针

31.4 练习题代码

Lesson31_练习题.cpp

#include <iostream>
using namespace std;
int* add(int a, int b);
int main()
{
    std::cout << "堆上分配内存 练习题\n";

    #pragma region 练习题一

    //请问这句代码是在做什么?是否存在问题
    //int i = (int)(new int(100));

    //解释:
    //这行代码存在问题。我们不能将 `new` 运算符分配的内存(一个指针)直接强制转换为 `int` 类型。
    //`new int(100)` 返回的是一个指向堆上整数的指针,而 `i` 应该是一个 `int` 类型的变量,不能存储指针。

    //这段代码等价于:
    int* ptr = new int(100); // 用指针来接收堆内存的地址
    cout << ptr << endl; // 打印堆内存地址
    int i = (int)ptr;//把指针变量强行转成int 指针8字节 int4字节 会有问题
    cout << i << endl;//地址丢失 不准确的值

    #pragma endregion

    #pragma region 练习题二

    /*有一个在堆上的分配的数组,容量为5,其中存储了{ 1,2,3,4,5 }
    现在希望将数组容量扩容到10,并且保留原数组中的内容,请实现该功能*/

    //之前学习的是静态数组 不能满足动态扩容
    //静态数组不能直接将一个静态数组(栈上的数组)赋值给另一个静态数组的原因,主要是因为数组名本质上是一个指针,代表了数组的首地址,但它是不可改变的。
    //具体来说,静态数组的内存大小在编译时已经确定,因此数组的内存位置是固定的,不能像指针一样直接修改。
    //int array[5] = { 1,2,3,4,5 };
    //int array2[10];
    //array = array2;// 错误!无法将一个数组赋值给另一个数组


    // 动态在堆上分配一个大小为 5 的数组,并初始化为 { 1, 2, 3, 4, 5 }
    int* array = new int[5] {1, 2, 3, 4, 5};

    // 创建一个新的数组,大小为 10
    int* array2 = new int[10];  // 动态分配一个大小为 10 的数组

    // 将原数组的内容搬到新的数组中
    for (int i = 0; i < 5; i++) {
        array2[i] = array[i];  // 复制原数组的元素
    }

    // 扩展新的数组,添加新的值
    array2[5] = 6;
    array2[6] = 7;
    array2[7] = 8;
    array2[8] = 9;
    array2[9] = 10;

    // 用完后释放原数组的内存
    delete[] array;  // 释放原数组的内存
    array = array2;  // 让原指针指向新的数组

    // 打印新数组中的所有元素
    for (int i = 0; i < 10; i++) {
        cout << array[i] << endl;  // 输出数组中的每个元素
    }

    // 释放新数组的内存
    delete[] array;  // 释放数组内存
    array = nullptr;  // 将指针设置为 null,避免悬挂指针
    array2 = nullptr; // 将指针设置为 null

    #pragma endregion

    #pragma region 练习题三

    //请实现一个返回指针的函数,用于返回两个数相加的和。

    int* c = add(5, 6);  // 调用 add 函数,返回值存储在指针 c 中
    cout << c << endl;  // 输出指针 c 的地址
    cout << *c << endl; // 输出指针 c 所指向的值,即 11
    cout << *c << endl; // 可以再次输出11 之前返回是局部变量地址 会被释放掉 下次输入可能会不准确

    // 注意:当返回堆上的内存时,我们需要手动释放它
    // 这里必须在使用完后通过 delete 来释放内存,避免内存泄漏
    delete c;   // 释放 c 指向的堆内存
    c = nullptr; // 防止悬挂指针

    #pragma endregion

}

#pragma region 练习题三

int* add(int a, int b)
{
    //栈上分配,语句块执行结束后 会被系统自动释放
    //那么如果在外部去使用c对应的地址,会出现问题 因为可能已经被释放了
    /*int c = a + b;
    return &c;*/

    //为了能在外部正常使用 我们可以在堆上分配,自己在外部去管理它即可
    return new int(a + b);
}

#pragma endregion


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏