指针和自由存储空间

指针和自由存储空间

计算机程序在存储数据时必须跟踪的三种基本属性:

  • 信息存储在何处
  • 存储的值为多少
  • 存储的信息是什么类型

您使用过一种策略达到上述目的:定义一个简单变量。声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。

下面来看另一种策略。这种策略以指针为基础,指针是一个变量,其存储的是值的地址,而不是值本身。

指针与c++基本原理
面向对象编程与传统过程性编程区别在于,OOP强调的是在运行阶段(而不是编译阶段)进行决策。运行阶段是指程序正在运行时,编译阶段指的是编译器将程序组合起来时。

运行阶段决策提供了灵活性,可以根据当时的情况进行调整。例如,考虑为数组分配内存的情况。传统的方法是声明一个数组。要在c++中声明数组,必须指定数组的长度。因此数组的长度在程序编译时就决定好了,这就是编译阶段决策。您可能认为在80%的情况下,数组长为20就足够了,但程序有时需要处理200个元素。为了安全起见使用了一个长为200的数组。这样程序在大多数情况下都浪费了内存。OOP通过将这样的决策推迟到运行阶段,使程序更加灵活。在程序运行后可以告诉它这次使用20个元素,下次还可以告诉它使用202个元素。

总之,使用OOP时,您可以在运行阶段确定数组长度。为使用这种方法,语言必须允许在程序运行时创建数组。c++采用的方法是,使用关键字new请求正确数量的内存以及使用指针来跟踪新分配的内存位置。

在运行阶段做决策并非OOP所独有,但c++编写这样的代码比c简单。

指针用于存储值的地址,将地址视为指定的量,而将值视为派生量。因此指针名表示的是地址。*运算符被称为间接值或解除引用运算符,将其应用于指针可以得到该地址存储的值。

指针的声明和初始化

int ducks = 12 ;
int *birddog = &ducks;

在这里插入图片描述

指针的危险

在c++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向数据的内存。为数据提供空间是一个独立的步骤,忽略这一步无疑是自找麻烦,如下所示:

long *p;
*p=123;

在这里插入图片描述

fellow确实是一个指针,但它指向哪里呢?上述代码没有将地址赋给fellow。那么123将被放在哪里呢?我们不知道。由于fellow没有被初始化,他可能有任何值。不管值是什么,程序都将它解释为储存123的地址。假设fellow的值恰好时1200,计算机将把数据放在1200地址上,即使这段地址是我们不应当使用的。fellow指向的地方很可能不是所要存储123的地方。这种错误可能会导致一些最隐匿、最难以跟踪的bug。

警告:一定要在对指针应用解引用运算符*之前,将指针初始化为一个确定的、适当的地址。
在“使用new来创建动态数组”一节中有更多内容。

使用new来分配内存

前面我们将指针初始化为变量的地址。变量是在编译时分配的有名称的地址,而指针只为可以通过名称直接访问的内存提供了一个别名。指针的真正用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在c语言中用malloc,在c++中仍然可以用,但是c++还有更好的–new。

int *pn = new int;

new int 告诉程序,需要存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后他找到这样的内存,并返回其地址。

pn指向一个数据对象,术语“数据对象”,指为数据项分配的内存。变量是有名称的内存,因此变量也是数据对象,但pn指向的内存不是变量。

为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:

typeName * pointer_name = new typeName;

需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。

为什么必须声明指针所指向的类型的原因:地址本身只指出了对象存储地址的开始,而没有指出其类型(使用的字节数)。指向double的指针的长度和指向int的指针相同。他们都是地址,但是声明了指针的类型,因此程序数据对象有几个字节以及如何解释它。

对于指针,new分配的内存块和常规变量声明分配的内存块不同。变量的值存储在栈stack中,而new从堆heap或自由存储区free store 的内存区域分配内存。

使用delete释放内存

int * ps = new int;
delete ps;
delete ps;   //not ok
int jugs = 5;
int * pi = &jugs;
delete pi;   //not allowed

delete ps 将释放ps指向的内存,但不会删除ps指针本身。不要尝试释放已释放的内存,虽然不会报错,但这样做的结果是不确定的,这意味着什么情况都有可能发生。只能用delete来释放new分配的内存。对空指针使用delete是安全的。

使用new来创建动态数组

对于管理小型数据对象来说,使用简单变量就足够了,但是对于大型数据(如数组、字符串和结构),应使用new,这正是new的用武之地。 例如假设程序是否 需要数组取决于运行时用户提供的信息。如果通过声明来创建数组,则在程序编译时将为他分配内存空间。不管程序最终是否需要,他都将存在,占用内存。在编译时给数组分配内存称为静态联编(static binding),意味着数组是在编译时加入程序的。但是使用new时,如果在运行阶段需要数组,就创建它;否则不创建。还可以在程序运行时选择数组的长度。这称为动态联编(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫做动态数组。使用静态联编,必须在编写程序时指定数组的长度;使用动态联编,程序将在运行时确定数组的长度。

使用new创建动态数组

int * psome = new int [10];
delete [] psome;

注意delete的方式。方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。

int * pt = new int;
short * ps = new short [500];
delete [] pt;   //effect is undefined,而且不报错,所以难以发现错误
delete ps;      //effect is undefined,而且不报错,所以难以发现错误

new和delete的格式不匹配导致的后果是不确定的。

使用new和delete时,应遵循以下规则。

  • 不要使用delete来释放不是new分配的内存
  • 不要使用delete释放同一个内存块2次
  • 如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放
  • 如果使用new为数组分配内存,则应使用delete(没有方括号)来释放

程序跟踪了分配的内存量,以便以后使用delete[ ]运算符时能够正确释放内存。但这种信息不是公用的,例如不能使用sizeof运算符来确定动态分配的数组包含的字节数。

为数组分配内存的通用格式是:

typeName * pointer_name = new typeName [num_elems];

使用动态数组

使用动态数组和普通数组类似。例如对于第一个元素,可以使用psome[0].

# include <iostrem>
int main()
{
    using namespace std;
    double *p3 = new double [3];
    p3[0] = 0.2;
    p3[1] = 0.5;
    p3[2] = 0.8;
    cout << "p3[1] is " << p3[1] << ".\n";
    p3 = p3 + 1;
    cout << "Now p3[0] is " << p3[0] << " and ";
    cout << "p3[1] is " << p3[1] << ".\n";
    p3 = p3 -1;
    delete [] p3;
    return 0;
}

输出:

p3[1] is 0.5.
Now p3[0] is 0.5 and p3[1] is 0.8.

下边的代码指出了数组名和指针的根本差别:

p3 = p3 + 1;

不能修改数组名的值。但是指针是变量,因此可以修改它的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值