动态内存分配技术可以保证程序在运行过程中按照实际需要申请适量的内存,使用结束后可以释放,这种在程序运行过程中申请和释放的存储单元称为堆对象,申请和删除过程一般称为建立和删除
建立堆对象
语法形式
new 数据类型 (初始化参数列表);
运行该语句申请分配用于存放指定类型数据的内存空间,并依据初始化列表中给出的值进行初始化。如果申请成功,new运算便返回一个指向新分配内存首地址的类型的指针,可以通过这种指针对堆对象进行访问;若申请失败,抛出异常。
删除堆对象
语法形式
delete 指针名;
功能:释放指针p指向的内存(不删除指针)必须是new操作的返回值。
用new分配的内存,必须用delete释放,否则导致分配的内存无法收回,使程序占据的内存越来越大,叫做内存泄漏。
例 动态创建对象
//6_16.cpp
#include <iostream>
using namespace std;
class Point {
public:
Point() : x(0), y(0) {
cout<<"Default Constructor called."<<endl;
}
Point(int x, int y) : x(x), y(y) {
cout<< "Constructor called."<<endl;
}
~Point() { cout<<"Destructor called."<<endl; }
int getX() const { return x; }
int getY() const { return y; }
void move(int newX, int newY) {
x = newX;
y = newY;
}
private:
int x, y;
};
int main() {
cout << "Step one: " << endl;
Point *ptr1 = new Point; //动态创建对象,没有给出参数列表,因此调用默认构造函数
delete ptr1; //删除对象,自动调用析构函数
cout << "Step two: " << endl;
ptr1 = new Point(1,2); //动态创建对象,并给出参数列表,因此调用有形参的构造函数
delete ptr1; //删除对象,自动调用析购函数
return 0;
}
动态数组
建立一维动态数组的语法形式
new 类型名 [数组长度];
释放一维动态数组的语法形式
delete [] 指针名;
例:动态创建对象数组(点类构造与上例相同,故省略)
int main() {
Point *ptr = new Point[2]; //创建对象数组
ptr[0].move(5, 10); //通过指针访问数组元素的成员
ptr[1].move(15, 20); //通过指针访问数组元素的成员
cout << "Deleting..." << endl;
delete[] ptr; //删除整个对象数组
return 0;
}
多态创建多维数组的语法形式
new 类型名 T [数组第一维长度][数组第二维长度]。。。。;
例
char (* fp)[3];
fp=new char[2][3];
内存申请成功,new运算返回一个指向新分配内存首地址的指针(指向行的指针)。
释放过程与一维数组的释放操作相同。
例:动态创建多维数组
//6_19.cpp
#include <iostream>
using namespace std;
int main() {
float (*cp)[9][8] = new float[8][9][8];
for (int i = 0; i < 8; i++)
for (int j = 0; j < 9; j++)
for (int k = 0; k < 8; k++)
//以指针形式数组元素
*(*(*(cp + i) + j) + k) = static_cast<float>(i * 100 + j * 10 + k);
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 9; j++) {
for (int k = 0; k < 8; k++)
//将指针cp作为数组名使用,通过数组名和下标访问数组元素
cout << cp[i][j][k] << " ";
cout << endl;
}
cout << endl;
}
delete[] cp;
return 0;
}
循环遍历数组的时候也是从第一个方括号的数字开始,一维嵌套下一维。程序中的指针形式和数组名下标形式是等价的,之前在用指针表示数组元素时说过。
使用动态内存分配操作实现了数组的动态创建,使数组元素的个数可以根据运行时的需要创建,但是建立和删除数组的过程使程序繁琐,有更好的方法是将数组的建立和删除过程封装起来,形成一个动态数组类。
动态数组封装成类
通过类的成员函数访问数组元素时,可以在每次访问之前检查一下下标是否越界。
例 动态数组类
#include <iostream>
#include <cassert>
using namespace std;
class Point { //类的声明同例6-16 … };
//动态数组类
class ArrayOfPoints {
public:
ArrayOfPoints(int size) : size(size) {
points = new Point[size];
}
~ArrayOfPoints() {
cout << "Deleting..." << endl;
delete[] points;
}
//获得下标为index的数组元素
Point &element(int index) {
assert(index >= 0 && index < size); //如果数组下标不会越界,程序中止
return points[index];
}
private:
Point *points; //指向动态数组首地址
int size; //数组大小
};
int main() {
int count;
cout << "Please enter the count of points: ";
cin >> count;
ArrayOfPoints points(count); //创建对象数组
points.element(0).move(5, 10); //通过访问数组元素的成员
points.element(1).move(15, 20); //通过类访问数组元素的成员
return 0;
}
为了避免建立和删除数组的繁琐,本例将数组的建立和删除封装起来形成一个类。在主函数开始处创建了对象数组,用户输入的count值传到动态数组类的构造函数中,运行函数体中的动态内存分配,points指针变量名获得了对象数组的首地址。主函数中的element成员函数则是得到返回指针地址的作用,同时用assert还可以检查下标越界。得到对象数组两个元素后,就与原来的例题相同了。
值得注意的一点是 element成员函数返回值类型是引用类型的,因为此处返回的是数组名和下标,相当于将原来的值复制一份返回,但是我们需要修改原来的值,所以必须要返回引用 是“左值"可以被赋值修改的。
智能指针
- 显式管理内存在是能上有优势,但容易出错。
- C++11提供智能指针的数据类型,对垃圾回收技术提供了一些支持,实现一定程度的内存管理
C++11 的智能指针
- unique_ptr :不允许多个指针共享资源,可以用标准库中的move函数转移指针
- shared_ptr :多个指针共享资源
- weak_ptr :可复制shared_ptr,但其构造或者释放对资源不产生影响