到目前为止,演示过的所有的程序在完成它的任务时所使用的内存空间都是固定不变的。
这个固定不变的内存空间其实是在编写程序时就可以知道和确定(一般以变量的形式)。这些程序都是不能在程序运行期间动态增加或减少内存空间。
你见过要求用户输入的文本必须不多不少包含多少个字符的程序吗?这是不现实的。
在很多时候,需要存储的数据量到底有多大在事先往往是一个未知数,要想处理好这类情况,就需要在C++程序里使用动态内存。
动态内存支持程序员创建和使用种种能够根据具体需要扩大和缩小的数据结构,它们只受限于计算机的硬件内存总量和系统特殊约束。
接下来,我们将学到如何以这种灵活的方式与内存打交道。
静态内存
静态内存就是我们在此前一直在使用的东西:变量(包括指针变量)、固定长度的数组、某给定类的对象。我们可以在程序代码里通过它们的名字或者地址来访问和使用它们。
使用静态内存的最大弊端是:你不得不在编写程序时为有关变量分配一块尽可能大的内存(以防不够存放数据)。一旦程序开始运行,不管实际情况如何,那个变量都将占用那么多的内存,没有任何办法能改变静态内存的大小。
动态内存
动态内存由一些没有名字、只有地址的内存块构成,那些内存块是在程序运行期间动态分配的。
它们来自一个有标准C++库替你管理的“内存池”
从内存池申请一些内存需要使用new语句,它将根据你提供的数据类型分配一块大小适当的内存。你不必担心内存块的尺寸问题,编译器能够记住每一种数据类型的单位长度并迅速计算出需要分配多少个字节。
如果有足够的可用内存满足你的申请,new语句将返回新分配地址块的起始地址。
如果没有可用的内存空间?
- 那么new语句将抛出std::bad_alloc异常!
注意在用完内存块之后,应该用delete语句将它还给内存池。另外作为一种附加的保险措施,在释放了内存块之后还应该把与之关联的指针设置为NULL。
NULL指针
有一个特殊的地址值叫做NULL指针。当把一个指针变量设置为NULL时,它的含义是那个指针将不再指向任何东西:
int *x;
x = NULL //x这个时候什么都不指向
我们无法通过一个被设置为NULL的指针去访问数据。事实上,试图对一个NULL指针进行解引用将在运行时被检测到并将导致程序中止执行。
所以在用delete释放内存后,指针将保留一个毫无意义的地址,所以我们要将指针变量赋值为NULL。
注意
静态内存这个术语和C++保留字static没有任何关系。静态内存意思是指内存块的长度在程序编译时被设定为一个固定的值,而这个值在程序运行时是无法改变的。
new语句返回的内存块很可能充满“垃圾”数据,所以我们通常先往里面写一些东西进行覆盖再访问它们,或者在类里直接写一个构造器来初始化。
在使用动态内存时,最重要的一个原则是每一条new语句都必须有一条与之对应的delete语句,没有配对的delete语句或者有两个配对的delete语句都属于变成漏洞(尤其是前者,将导致内存泄漏)。
为对象分配内存
为对象分配内存和为各种基本数据类型(int、char、float....)分配内存在做法上完全一样。
- 用new向内存池申请内存
- 用delete来释放内存
这个概念其实我们之前已经讲过了,就是在虚函数那一讲。
下面再来一个例子:
#include <iostream>
#include <string>
class Company
{
public:
Company(std::string theName);
virtual void printInfo();
protected:
std::string name;
};
class TechCompany : public Company
{
public:
TechCompany(std::string theName, std::string product);
virtual void printInfo(); //必须写成虚函数,要不然编译器不能识别是该调用这个还是基类的那个,会默认全部调用基类的
private:
std::string product;
};
Company::Company(std::string theName)
{
name = theName;
}
void Company::printInfo()
{
std::cout << "这个公司的名字叫:" << name << std::endl;
}
TechCompany::TechCompany(std::string theName, std::string product) : Company(theName)
{
this->product = product;
}
void TechCompany::printInfo()
{
std::cout << name << "公司大量生产了" << product << "这款产品" << std::endl;
}
int main()
{
Company *company = new Company("APPLE");
company->printInfo();
delete company;
company = NULL;
company = new TechCompany("APPLE", "IPHONE");
company->printInfo();
delete company;
company = NULL;
return 0;
}
注意:
1. 在用对象的时候,千万不要忘记把方法声明为虚方法。
2. 在重新使用某个指针之前千万不要忘记调用delete语句,如果不这么样做,那个指针将得到一个新内存块的地址,而程序将永远也无法释放原先那个内存块,因为它的地址已经被覆盖了。
3. 请记住,delete语句只释放给定指针变量正指向的内存块,不影响这个指针。在执行delete语句之后,那个内存块被释放了,但指针变量还依然存在,所以要将这个指针=NULL。