了解动态内存在 C++ 中是如何工作的是成为一名合格的 C++ 程序员必不可少的。C++ 程序中的内存分为两个部分:
栈:在函数内部声明的所有变量都将占用栈内存。
堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
delete 与 delete[] 区别:
针对简单类型 使用 new 分配后的不管是数组还是非数组形式内存空间用两种方式均可 如:
int *a = new int[10];
delete a;
delete [] a;
此种情况中的释放效果相同,原因在于:分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理,在析构时,系统并不会调用析构函数, 它直接通过指针可以获取实际分配的内存空间,哪怕是一个数组内存空间(在分配过程中 系统会记录分配内存的大小等信息,此信息保存在结构体_CrtMemBlockHeader中, 具体情况可参看VC安装目录下CRT\SRC\DBGDEL.cpp)
2、针对类Class,两种方式体现出具体差异
当你通过下列方式分配一个类对象数组:
class A
{
private:
char *m_cBuffer;
int m_nLen;
public:
A(){ m_cBuffer = new char[m_nLen]; }
~A() { delete [] m_cBuffer; }
};
A *a = new A[10];
// 仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不能释放 从而造成内存泄漏
delete a;
// 调用使用类对象的析构函数释放用户自己分配内存空间并且 释放了a指针指向的全部内存空间
delete [] a;
所以总结下就是,如果ptr代表一个用new申请的内存返回的内存空间地址,即所谓的指针,那么:
delete ptr -- 代表用来释放内存,且只用来释放ptr指向的内存。
delete[] rg -- 用来释放rg指向的内存,!!还逐一调用数组中每个对象的 destructor!!
对于像 int/char/long/int*/struct 等等简单数据类型,由于对象没有 destructor,所以用 delete 和 delete [] 是一样的!但是如果是C++ 对象数组就不同了!
利用动态内存, 我们也可以做出链表, 可以不断增长的数组:
#include <iostream>
#include <cstdio>
using namespace std;
struct node
{
//链表的节点
int data;//数据
int num;//节点编号
struct node *next;//指向下一个节点
};
int main()
{
struct node *head/*头节点*/, *p, *q;
head=NULL;
p=NULL;
q=new node;
q->next=NULL;
q->num=1;
int a=-1;
cout<<"请输入第1个数字:";
cin>>a;
q->data=a;
head=q;
while (a!=0)
{
p=q;
q=new node;
q->next=NULL;
p->next=q;
q->num=p->num+1;
cout<<"请输入第"<<q->num<<"个数字:";
cin>>a;
q->data=a;
}
//前面都是输入,这以下都是输出
q=head;
p=NULL;
while (q->data!=0)
{
printf("%d %d\n",q->num,q->data);
q=q->next;
}
//释放内存
q=head;
p=q;
while(q->next!=NULL)
{
p=q->next;
delete []q;
q = p;
}
return 0;
}
->: 用指针访问结构体内的变量。
在链表中插入、删除节点也很简单, 先给next赋下一个节点地址,再加数据即可。
new 和 malloc 内部的实现方式有什么区别?
new 的功能是在堆区新建一个对象,并返回该对象的指针。
所谓的【新建对象】的意思就是,将调用该类的构造函数,因为如果不构造的话,就不能称之为一个对象。
而 malloc 只是机械的分配一块内存,如果用 mallco 在堆区创建一个对象的话,是不会调用构造函数的。
严格说来用 malloc 不能算是新建了一个对象,只能说是分配了一块与该类对象匹配的内存而已,然后强行把它解释为【这是一个对象】,按这个逻辑来,也不存在构造函数什么事。
同样的,用 delete 去释放一个堆区的对象,会调用该对象的析构函数。
用 free 去释放一个堆区的对象,不会调用该对象的析构函数。
做个简单的实验即可明了:
#include <iostream>
#include <malloc.h>
class TEST
{
private:
int num1;
int num2;
public:
TEST()
{
num1 = 10;
num2 = 20;
}
void Print()
{
std::cout << num1 << " " << num2 << std::endl;
}
};
int main(void)
{
// 用malloc()函数在堆区分配一块内存空间,然后用强制类型转换将该块内存空间
// 解释为是一个TEST类对象,这不会调用TEST的默认构造函数
TEST * pObj1 = (TEST *)malloc(sizeof(TEST));
pObj1->Print();
// 用new在堆区创建一个TEST类的对象,这会调用TEST类的默认构造函数
TEST * pObj2 = new TEST;
pObj2->Print();
return 0;
}
/*
运行结果:
-----------------------------
-842150451 -842150451 |
10 20 |
请按任意键继续. . . |
-----------------------------
我们可以看到pObj1所指的对象中,字段num1与num2都是垃圾值
而pObj2所指的对象中,字段num1与num2显然是经过了构造后的值
*/
第二个笔记,思路是好的,但是错误的。这种写法最后会是一个 data 为 0 的结点,但遍历和析构时用 data 为 0 判断结束而导致缺少对 data 为 0 结点的操作,改正如下
#include<iostream>
using namespace std;
struct node
{
//链表的节点
int data;//数据
int num;//节点编号
struct node *next;//指向下一个节点
};
int main()
{
struct node *head/*头节点*/, *p, *q;
head = NULL;
p = NULL;
q = new node;
q->next = NULL;
q->num = 1;
int a = -1;
cout << "请输入第1个数字:";
cin >> a;
q->data = a;
head = q;
while (a != 0)
{
p = q;
q = new node;
q->next = NULL;
p->next = q;
q->num = p->num + 1;
cout << "请输入第" << q->num << "个数字:";
cin >> a;
q->data = a;
}
//前面都是输入,这以下都是输出
q = head;
p = NULL;
while (1)
{
printf("%d %d\n", q->num, q->data);
if (q->data == 0)
break;
q = q->next;
}
//释放内存
q = head;
p = q;
while (1)
{
p = q->next;
delete[]q;
q = p;
if (!q)
break;
}
return 0;
}