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