上一讲中鸡啄米讲的是对象指针,指针的相关内容就讲完了。今天鸡啄米给大家讲解下C++编程入门时必须要掌握的又一个重点内容--动态内存分配。
动态内存分配最常见的就是用来分配一个某类型的数组。我们可以使用数组来处理大量的数据,但实际上很多情况下我们并不知道此数组会有多少个元素,所以在定义数组时到底定义多大的数组就要仔细考虑下了。比如,我们要对输入的若干个数进行分析,得出所有的正数存到一个数组里以备他用,这个正数数组应该定义多大呢?如果大了可能会造成内存的浪费,如果小了可能会出现数组越界的情况。这种情况下比较理想的是判断全部数据里有多少个正数就定义多大的数组,既不浪费内存也不会出现数组越界的问题,这就需要用到动态内存分配了。
动态内存分配指的就是在程序运行过程中根据实际情况动态分配适量的内存,使用完以后再释放。动态内存分配时申请和释放的内存单元叫做堆对象。申请和释放的过程一般叫做建立和删除。
一.C++动态内存分配运算符:new和delete
C++中new运算符用来动态分配内存,也可以称为动态创建堆对象。使用new运算符动态分配内存的语法形式为:
new 类型名(初值列表);
此语句用于申请分配一个内存空间,此内存空间存放由“类型名”表示的类型的数据,并用初值列表中列出的值初始化。该语句会返回一个由“类型名”表示的类型的指针,如果分配失败则返回NULL。
我们动态建立一个普通变量时,初始化就是为其赋值。例如:
int *p; p = new int(10);
上面的语句动态分配了用来存放一个整数数据的内存空间,同时把初始值10存入该内存空间中,也就是p指向的整形数据是10,最后把分配内存的首地址赋值给p。
如果我们动态建立的是一个类的对象,那么会根据初值列表调用该类合适的构造函数。
delete运算符用来删除用new动态建立的堆对象,即释放new动态分配的堆内存。使用delete运算符的语法形式为:
delete 指针名;
如果被删除的是普通变量,则会直接释放动态分配的内存。如果被删除的是对象,则该对象的析构函数被调用。这里要注意一下,用new动态分配的内存只能用delete释放一次,如果释放第二次会出现错误。
鸡啄米写一个动态创建对象的例子:
#include <iostream> using namespace std; class CStudent { public: CStudent() { cout<<"Default constructor called."<<endl; } // 无参数的构造函数 CStudent(int nAge) { m_nAge = nAge; cout<<"Constructor called."<<endl; } // 带参数的构造函数 ~CStudent() { cout<<"Destructor called."<<endl; } // 析构函数 int GetAge() { return m_nAge; } // 内联函数,返回m_nAge private: int m_nAge; // 私有数据 }; int main() { CStudent *ptr = new CStudent; // 动态创建对象,没有给出初值列表,所以调用无参数的构造函数 delete ptr; // 删除对象,自动调用析构函数 ptr = new CStudent(16); // 动态创建对象,给出了初值,所以调用带参数的构造函数 delete ptr; // 删除对象,自动调用析构函数 return 0; }
程序运行结果是:
Default constructor called.
Destructor called.
Constructor called.
Destructor called.
用运算符new也可以建立数组类型的堆对象,动态创建一维数组的语法形式为:
new 类型名[下标表达式];
上面的下标表达式指明了数组的元素个数。如果动态分配内存成功,会返回一个指向分配内存首地址的指针,如果分配失败则返回空指针NULL。这里要注意的是,为数组动态分配内存时不能指定数组元素的初值。
用new动态建立的数组,也可以用delete删除,但是指针名前要加“[]”。delete数组的语法形式为:
delete []指针名;
鸡啄米再给大家写一个动态创建对象数组的简单例子:
#include <iostream> using namespace std; class CStudent { public: CStudent() { cout<<"Default constructor called."<<endl; } // 无参数的构造函数 CStudent(int nAge) { m_nAge = nAge; cout<<"Constructor called."<<endl; } // 带参数的构造函数 ~CStudent() { cout<<"Destructor called."<<endl; } // 析构函数 int GetAge() { return m_nAge; } // 内联函数,返回m_nAge private: int m_nAge; // 私有数据 }; int main() { CStudent *ptr = new CStudent[2]; // 动态创建对象数组,为每个对象元素调用无参数的构造函数 delete []ptr; // 删除对象数组,自动为每个对象调用析构函数 return 0; }
程序运行结果是:
Default constructor called.
Default constructor called.
Destructor called.
Destructor called.
用new动态创建多维数组的语法形式为:
new 类型名T[下标表达式1][下标表达式2]...;
上面的下标表达式1可以是任何结果为正整数的表达式,而其他下标表达式必须是结果为正整数的常量表达式。这个语句如果执行成功则返回指向分配内存首地址的指针,但是这个指针不是T类型的指针,而是指向T类型数组的指针。
例如:int *p = new int[2][3];是错误的,因为这里new返回的是指向一个有3个元素的一维int型数组的指针,而p是一个指向int型数据的指针。这里大家可能有两个疑问:1.不是分配了一个二维数组吗,怎么返回的是指向一维数组的指针?2.怎样声明指向数组的指针?
鸡啄米为大家解答:1.我们可以把多维数组看成是由除第一维外其他维数据组成的一维数组,此一维数组的元素个数就是第一个下标表达式的值,比如上面的int[2][3]可以看成是有2个元素的一维数组,每个元素又含有3个整数。new创建多维数组返回的是指向一维数组第一个元素(也是数组)的指针,对于new int[2][3]来说第一个元素实际上是二维数组的第一行,对于new int[2][3][4]第一个元素实际上是一个3x4的二维数组。
2.声明指向数组的指针的形式是:
类型名 (*指针名)[下标表达式1][下标表达式2]...;
动态创建上面的二维数组的正确写法如:
int (*p)[3]; // 声明一个指向数组的指针 p = new int[2][3]; // 动态创建二维数组
可以按照下面的形式访问此二维数组的元素:
for (int i=0; i<2; i++) { for (int j=0; j<3; j++) { *(*(p+i)+j) = 0; // 通过指针访问数组元素 } }
二.继承自C语言的动态内存分配与释放的函数
C++语言除了可以使用new和delete运算符进行动态内存管理外,还继承了C语言的动态内存管理函数。
1.动态内存分配函数
void *malloc(size);参数size是要分配的字节数。函数执行成功时返回void类型的指针,执行失败时返回空指针NULL。
2.动态内存释放函数
void free(void *memblock);参数memblock是指向要释放的内存的指针。此函数没有返回值。
关于动态内存分配的内容鸡啄米就讲这么多了,软件开发中会经常用到,所以大家在编程入门的时候多多思考,掌握的扎实些,以后会减少很多出错的机会。有问题欢迎来鸡啄米博客交流,或者给我发邮件沟通。