前言:
期待已久的指针篇来啦,这篇全都是有关指针的知识,喜欢指针的读者可以大饱眼福^ ^。关于指针的声明和初始化等部分,我在共用、结构里讲过了,需要的读者可以点进去看一下再返回来。
链接:《共、枚、指1》
1.指针未初始化的危险性
我们知道指针是存放地址的变量。当我们创建了一个指针的时候,但并没有初始化,在后面我们还对这个未初始化的指针解引用了,就会对内存产生破坏,导致程序挂掉。
创建指针变量的时候,计算机不会为指针指向的内存单元编号开辟空间。
当我们用p指针里的地址去找到相应的内存空间并对它进行操作,可不敢这样做,那样是犯法的。故事性理解:就好像你知道了一个旅馆的一个房间地址,但你知道并不代表你拥有这个房间,你来到这个旅馆,没跟馆主说,你径直的冲到这个房间门口,强行打开房间门,里面有小明躺在床上睡觉,你一脚把它踹开,自己躺在床上睡觉,还赖皮的不走了,馆主表示无奈,并报了警。
2.指针与十六进制数字
我们知道,地址是用十六进制表示的,但数字的十六进制和地址的十六进制的意思是截然不同的,虽然计算机通常把地址当做整数来处理。但我们知道数字是描述数值的,指针描述的是位置。
计算机把地址当做整数来处理的意思是:指针变量存储的值(也就是地址),就是在按照存储整型的方式存到内存中的,取出来的时候也是按整型取的。
但我们要记得的是:十六进制数值,和十六进制地址不是一回事就OK。
3.使用new分配内存空间
使用new运算符在运行阶段开辟堆区空间,开辟的空间大小根据我们给new后面加的数据类型,而且我们需要使用同类型的指针来接收new返回来的地址。
例子:
指针的大小是固定不变的,不管指针指向什么类型的地址,指针的大小只与平台位数有关。是32位平台就是4字节,64位平台就是8字节。
这里面pn、pd是用来接收new开辟出来的内存空间的地址,这块内存空间没有名称,只能用指针去访问。而普通变量,内存空间有名称。比如int a = 10;&a是a变量的内存地址,用a这个名称来标记这块空间,就可以直接用a来访问里面的值。
4.使用delete释放内存
在用new申请内存的时候,当我们不需要用了的时候,必须将这块申请的内存交还给操作系统,否则内存将会越来越少,直到没有多余的内存空间让程序运行起来。
在C++中,我们使用delete来释放使用new开辟的空间,格式是:delete 指针;(接收new开辟空间返回地址的那个指针),本质上是释放掉new开辟的空间,因为这个接收的指针就是new开辟的地址,所以这么写也是没问题的。
特别注意的是new和delete是要配套使用的。
delete使用注意事项
delete只能用来释放用new开辟的空间 |
不能对同一块new开辟的空间释放两次 |
对空指针使用delete是没事的,相当于delete什么都没做 |
我们这里是用delete释放pd,pd指向的那块地址也是new开辟的,因为ps将值赋给了pd,其实delete释放ps也可以,但不要都释放,不然就对同一块new空间释放两次了。
5.使用new来创建动态数组
静态联编:通过声明创建数组,则在程序被编译时将为它分配内存空间,不管程序最终使不使用数组,它都存在,并占用空间。在编译时给数组分配内存被称为静态离联编。
而在运行阶段需要数组,则创建数组,不需要就不创建。还能在程序运行时确定数组的长度。这被称为动态联编。这种数组叫做动态数组。
使用静态联编,必须在编写程序时指定数组的长度。使用动态联编时,程序可以在运行时确定数组的长度。
当我们使用new时带上方括号以及数目的时候,也就是在跟new传达开辟数组空间的信号,相应的释放掉数组空间也要给delete加上方括号。用new没有用方括号,delete也不用带。
补充:对于ANSIC和ISO标准来说,new和delete的格式不匹配的结果是未知的。
总之就是:使用new来创建动态数组的时候,要用对应的格式和new匹配起来,需要补充的一点是这种情况new int[1];,new[]只为一个实体分配内存,用不带[]的delete来释放。看下面代码:
pd是指向一个double(数组第一个元素)的指针,我们需要负责把数组的元素确定好,因为编译器不能对pd是指向10个double元素中的第一个进行确定。什么意思呢?也就是说当我们忽略数组的元素个数的时候,编译器不知道你这个首元素地址是10个元素大的数组的首元素地址,还是20个元素大的数组的首元素地址,所以不能省略数组元素的个数。
知道动态数组怎么创建和怎么释放后,我们来讨论如何使用动态数组。
6.使用动态数组
数组表示法:将指针当做数组名使用即可。
数组名是一般情况下是首元素地址,数组名的值是不能变的。而指针是变量,可以通过加减来改变所指向的地址。例如parr = parr + 1;,使得parr原本指向第一个元素变成了指向第二个元素。
parr[0]就是第一个元素。parr[0]是第二个元素是因为parr+1了,指向了第二个元素。把指针当做数组名来使用,并不是就不能用指针的方式来访问数组了,我们知道parr是数组第一个元素的地址,用解引用可以访问到该地址的值,所以*parr和parr[0]是等价的。我们来讲完指针的运算,就会更清晰了。
7.指针运算
在C和C++中数组和指针基本是等价的。等价的原因不只是因为C和C++内部都使用指针来处理数组,也在于指针算术。
将一个整数加1,其值将增加1,但指针增加1,它的值增加的大小取决于指针的类型。
i的值增加1,这我们都理解。指针的值从E8变到EC(十六进制 C是12),增加了4,恰好是一个整型的大小,所以指针的加减运算是让指针变量跳过n个指针类型,值也相应加减n个指针类型的大小。例子:double df = 3.14;double* pd = &df;pd+1;pd的值将增加8。
加油加油,再看一个就结束啦!坚持。
在这里面,我们将wages(数组名)给double类型的指针初始化,可行的原因正是因为数组名是首元素的地址,数组的元素是double类型的,double类型的地址给double指针初始化那再合理不过了。
pd在加1前,打印的值是A8,加一后是B0,增加了8。因为pd跳过了一个double类型(占8个字节)。有些读者可能就要问了,为什么让值增加8呢?其实是因为,每个地址编号对应的内存单元占一个字节。第一个元素占8个字节也就是8个编号,而指针指向头个内存单元。A8是第一个元素的头个字节,还有A9,AA一直到AF,算上A8是8个字节。然后指针加一指向下一个元素,而数组又是连续存储元素的,紧接着指针就指向了B0,这就是为什么会加8的原因。
来到20、21行,这里就是指针访问和数组表示的关系。我们已经知道数组是首元素地址了,stacks[0]就相当于*(stacks+0),stacks加0还是首地址,解引用就是首个元素。
也就是假设:在这里我们用指针ps来接收stacks,就可以用这些操作,*ps就是首元素,*(ps+1)是指针指向首元素的下一个元素后解引用得到第二个元素,ps[0]是*(ps+0),ps[1]是*(ps+1)。
数组名的意思:在最后sizeof(wages),wages不是数组首元素地址的意思,而是整个数组的意思。wages每个元素8字节,数组有三个元素,就是24字节。
数组名
&arr |
整个数组的地址 |
arr |
数组首元素地址 |
sizeof(arr) |
整个数组的大小 |
一般情况下数组名都是首元素地址,有且仅有两种情况是值整个数组,单纯&arr和单纯sizeof(arr),加单纯的意思就是说只能是这种形式下的数组名才指整个数组。sizeof(arr+1),这里的arr就是一般情况了。
到这里指针的大部分基本知识就讲完啦,还有指针与字符串、指针与类型的组合等,我们下节再讲。最重要的是对使用要熟练使用指针解决问题。
希望读者读完有所收获,如果本篇博客有内容上的错误或排版、内容分布不合理,请评论跟博主讲!
求点赞,求点赞,求点赞!你的点赞是我更新的动力^ ^。