在介绍动态内存分配之前,我们先来讨论一下动态内存与静态内存的区别:
1. 静态内存
静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会自动释放所占用的内存空间。变量的分配与释放,都无须程序员自行考虑。
2. 动态内存
用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配。
3. 区别
a) 静态内存分配在编译时完成,不占用CPU资源; 动态内存分配在运行时,分配与释放都占用CPU资源。
b) 静态内存在栈(stack)上分配; 动态内存在堆(heap)上分配。
c) 动态内存分配需要指针和引用类型支持,静态不需要。
d) 静态内存分配是按计划分配,由编译器负责; 动态内存分配是按需分配,由程序员负责。
在C++中对象可以静态分配—即编译器在处理程序源代码时分配,也可以动态分配—即程序执行时调用运行时,调用库函数来分配。这两种内存分配方法的主要区别是效率与灵活性之间的平衡准则不同。出于静态内存分配是在程序执行之前进行的因而效率比较高。但是,它缺少灵活性,它要求在程序执行之前就知道所需内存的类型和数量。例如,利用静态分配的字符串数组,我们无法很容易地处理和存储任意的文本文件,此时需要的内存取决于用户输入,这样的情况下,程序需要动态分配内存。一般来说,存储未知数目的元素需要动态内存分配的灵活性。
C ++中将运算符new和delete合成在一起,实现动态内存分配。
1.动态内存分配的特点
(1)通过new关键字进行动态内存申请
(2)动态内存分配是基于数据类型进行的
(3)使用关键字delete释放申请的内存
2.动态内存分配的语法
<1> 基本类型变量的申请:
Type *point = new Type;
......
delete point;
动态分配了用于存放Type类型数据的内存空间,将首地址赋值给了指针point。
<2> 数组类型的申请:
Type *point = new Type[N];
......
delete[] point;
动态分配了用于分配Type类型的元素的块(数组),其中N是表示这些元素的量的整数值,N要用[ ]括起来。
Example:
int *point;
point = new int(10);//情况1
......
delete point;
point = new int[10];//情况2
......
delete[] point;
情况1,系统为int类型的1个元素分配空间,且将这个元素的初值赋为10,并返回该元素的地址,将其地址分配给指针point。此时,point指向一个内存空间,该空间存放int类型初始值为10的元素。
情况2,系统为int类型的10个元素分配空间,并返回指向该序列第一个元素的地址,将其地址分配给指针point。此时,point指向一个有效的内存块,其中包含10个int类型元素的空间。
对于情况2,point是一个指针,因此,point指向的第一个元素可以使用表达式point [0]或表达式* point (两者都是等价的)来访问。可以使用point [1]或*(point + 1)访问第二个元素,依此类推......
Notes:我们程序请求的动态内存由系统从内存堆中分配。 但是,计算机内存是一种有限的资源,它可能会耗尽。 因此,无法保证所有使用operator new分配内存的请求都将由系统授予。
<3> 动态内存分配实例:
#include<iostream>
using namespace std;
int main()
{
int *point;
point = new int(10);
*point += 10;
cout << "point= " << point << endl;
cout << "*point= " << *point << endl;
delete point;
point = new int[10];
for (int i = 0; i < 10; i++)
{
point[i] = i+1;
cout << "point[" << i+1 << "] = " << point[i] << endl;
}
for (int i = 0; i < 10; i++)
{
*(point + i) += 10;
cout << "point[" << i+1 << "] = " << point[i] << endl;
}
delete[] point;
return 0;
}
编译运行结果为:
3. new关键字与malloc函数的区别
new关键字是C++的一部分 | malloc是由C库提供的函数 |
new以具体类型为单位进行内存分配 | malloc以字节为单位进行内存分配 |
new在申请单个类型变量时可进行初始化 | malloc不具备内存初始化的特性 |
4. new关键字的初始化:
在前面提到过int类型的初始化,下面列举几个其他常用类型的初始化:
int *ip = new int(10);
float *fp = new float(8.0f);
char *cp = new char('c');
测试:
#include<iostream>
using namespace std;
int main()
{
int *ip = new int(10);
float *fp = new float(88.88f);
char *cp = new char('c');
cout << "*ip = " << *ip << endl;
cout << "*fp = " << *fp << endl;
cout << "*cp = " << *cp << endl;
delete ip;
delete fp;
delete cp;
return 0;
}
编译运行结果为:
注意:
int* point = new int(10); | 表示动态分配一个int ,初始化为 10 |
int* point = new int[10]; | 表示动态分配一个数组,数组大小为10 |
用new分配的内存,必须用delete加以释放,否则会导致动态分配的内存无法回收,使得程序所占据的内存会越来越大,这叫做“内存泄漏”。
5.C++动态分配内存异常机制
在内存分配时,如果我们分配一个很大的内存空间,会出现栈满,程序产生异常,程序崩溃。所以我们需要引入异常机制。
C++提供了两种标准机制来检查分配是否成功。
<1> 处理异常。
使用此方法,在分配失败时抛出std::bad_alloc类型的异常。 例外是这些教程后面解释的强大的C++特性。 但是现在,你应该知道如果抛出此异常并且未由特定处理程序处理,则程序执行将终止。
此异常方法是new默认使用的方法,并且是在声明中使用的方法,like:
point = new int [10]; // if allocation fails, an exception is thrown
如果你想检查 new 是否成功,应该捕捉异常:
try {
int* p = new int[SIZE];
// 其它代码
} catch ( const bad_alloc& e )
{
return -1;
}
<2> nothrow
普通new一个异常的类型std::bad_alloc。这个是标准适应性态。在早期C++的舞台上,这个性态和现在的非常不同;new将返回0来指出一个失败,和malloc()非常相似。
在内存不足时,new (std::nothrow)并不抛出异常,而是将指针置NULL。
在一定的环境下,返回一个NULL指针来表示一个失败依然是一个不错的选择。C++标准委员会意识到这个问题,所以他们决定定义一个特别的new操作符版本,这个版本返回0表示失败。
当使用它时会发生的情况是,当内存分配失败时,而不是抛出new (std::nothrow)异常或终止程序,new返回的指针是空指针,程序继续正常执行 。
可以使用名为nothrow的特殊对象(在header <new>中声明)作为new的参数来指定此方法:
point = new (nothrow) int [10];
在这种情况下,如果此内存块的分配失败,则可以通过检查point是否为空指针来检测异常
int *point;
point = new(nothrow) int[10];
if (point == nullptr)
{
cout << "分配内存时出错!" << endl;
}
实例说明:
#include<iostream>
#include<new>
using namespace std;
int main()
{
int i;
int *point;
cout << "创建数组长度为:";
cin >> i;
point = new(nothrow) int[i];
if (point == nullptr)
{
cout << "内存分配出现错误,无法分配内存!" << endl;
}
else
{
for (int j = 0; j < i; j++)
{
cout << "请输入数组元素" << j + 1 << ": ";
cin >> point[j];
}
cout << "你所输入的元素为:";
for (int j = 0; j < i; j++)
{
cout << point[j] << " ";
}
cout<<endl;
}
delete[] point;
return 0;
}
编译运行结果:
分配失败是非常普通的,它们通常在植入性和不支持异常的可移动的器件中发生更频繁。因此,应用程序开发者在这个环境中使用nothrow new来替代普通的new是非常安全的。
参考资料:https://blog.csdn.net/qq_40416052/article/details/82493916