背景:
对于计算机程序设计而言,变量和对象在内存中的分配都是编译器在编译程序时安排好的,这带来了极大的不便,如数组必须大开小用,(大开小用就是现在定义一个数组容量是100,实际使用了10,造成了极大的内存浪费),因此为了解决这个问题。我们引入了动态内存分配来解决了这个问题。
一、动态内存的用法
因为new返回的就是一个指针类型,所以我们必须用同类型一个指针来承接(有一种特殊情况就是在继承的时候可以用父类指针指向子类对象)。
new和delete运算符是用于动态分配和撤销内存的运算符。
1.开辟单变量地址空间
使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址,如果申请不成功,则返回零值。
new运算符返回的是一个指向所分配类型变量(对象)的**指针**。对所创建的变量或对象,都是**通过该指针**来间接操作的,所以动态创建的对象本身没有标识符名。
一般使用格式:
格式1:指针变量名=new 类型标识符;
格式2:指针变量名=new 类型标识符(初始值);
格式3:指针变量名=new 类型标识符 [内存单元个数];
说明:格式1写法仅仅申请空间,但不为其赋值初始化。
格式2在内存分配成功后,同时将一初值存放到该内存单元中,要是只写括号但不写具体值的话就默认用0对该对象初始化;
而格式3可同时分配若干个内存单元,相当于形成一个动态数组。
例如:
1)new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址。int *a = new int 即为将一个int类型的地址赋值给整型指针a
2)int *a = new int(5) 作用同上,但是同时将整数空间赋值为5
2.开辟一维数组空间
对于数组进行动态分配的格式为:
指针变量名=new 类型名[下标表达式];
delete [ ] 指向该数组的指针变量名;
两式中的方括号是非常重要的,两者必须配对使用,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。
delete []的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。
请注意“下标表达式”不必是常量表达式,即它的值不必在编译时确定,可以在运行时确定。
一维:int t; cin >> t; int* a = new int[t];//这个一维数组就是在运行时确定的空间
3.开辟多维数组的写法及用法。
左半部分:类型名 (*指针名)[数组第二维长度][数组第三维长度].......
右半部分:new 类型名[数组第一维长度][数组第二维长度][数组第三维长度]...................
有两个注意点:
1.左半部分的小括号不要忘了
2.左边中括号里的维度要比右边少一个第一维度。
int(* p)[3] = new int[2][3];//二维
int(*p1)[2][3] = new int[1][2][3];//三维
4、删除delete用法
1. 删除单变量地址空间
int *a = new int;
delete a; //释放单个int的空间
2. 删除数组空间,
int *a = new int[5];
delete []a; //释放int数组空间,注意,不管是几维数组,最后delete后面写一个[]就可以。
二:动态内存分配和我们自定义类相遇的时候的一些注意点。
既然是自定义类,那就不可避免地要涉及到构造函数的问题。
如果自定义类中含有用户定义的默认构造函数,则new T和new T()加不加括号效果是一样的。
但如果用户没有定义默认拷贝构造函数,这时候就会有区别,使用new T时会调用系统的隐含的构造函数,而new T()不仅会调用系统的隐含的构造函数,还会为基本数据类型和指针类型全部用0赋值,这一个过程是递归的,什么是递归?就是当一个类中含有另一个自定义类型的时候会连那个自定义类也全部用0初始化。
//先给出一个类,下面方便叙述
class point {
public:
int m_a;
int m_b;
point(){};
point() {
this->m_a = m_a;
this->m_b = m_b;
}
void print(int m_a, int m_b) {
cout << m_a << " " << m_b << endl;
}
};
1.创建单独类的对象
//调用无参构造函数
point *p=new point;
//调用有参构造函数
point *p=new point(1,3)
2.创建自定义类数组
point* p = new point[10];
point* p = new point[10]();
用法:
point* p = new point[10];
p[1].m_a = 1;
p[1].m_b = 2;
p[1].print();
delete []p;
三、使用注意事项
1. new 和delete都是内建的操作符,语言本身所固定了,无法重新定制,想要定制new和delete的行为,徒劳无功的行为。
2. 动态分配失败,则返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。所以使用时尽量先判断一下(if(指针变量名!=NULL)
3. 指针删除与堆空间释放。删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身(指针p本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放),释放堆空间后,p成了空指针。
4. 内存泄漏(memory leak)和重复释放。new与delete 是配对使用的, delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会**重复释放**堆内存空间。(千万小心,别把系统整瘫痪)。
5. 动态分配的变量或对象的生命期。我们也称堆空间为自由空间(free store),但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放,往往会出错。
6. 要访问new所开辟的结构体空间,无法直接通过变量名进行,只能通过赋值的指针进行访问。
用new和delete可以动态开辟和撤销地址空间。在编程序时,若用完一个变量(一般是暂时存储的数据),下次需要再用,但却又想省去重新初始化的功夫,可以在每次开始使用时开辟一个空间,在用完后撤销它。