指针浅谈

By JiangLingJun

post-cover

此篇博客是由日常阅读、总结的文章,旨在通过通俗易懂的文字理解C/C++中的指针。
目录一览:

  • 什么是指针
  • 指针与数组
  • new和delete,申请、释放内存空间

一、什么是指针?

首先,提一句我看到过的让我理解指针最形象的比喻:变量和指向该变量的指针是同一个硬币的两面。我们从以下代码片段进行阐述:

int value = 5;//①
int* pointer = &value;//②
cout << pointer << endl;//③
cout << *pointer << endl;//④
  • ①表示变量value的值是5,数据类型int,int类型占用存储空间一般是4个字节,因为计算机世界里只有0和1,所以变量value在大端模式的计算机世界里是长这个样子的:00000000 00000000 00000000 00000101,其中8个bit表示1个byte。不同的数据类型,其存储空间大小不一样,可以理解为不同大小的鞋子应该放在不同大小的鞋盒里面。value存储在计算机某块内存中,内存地址对程序员不直接可见,这里假设我们使用的机器是32位的,并假设变量value的内存地址的值的16进制表示为0x12345678
  • ②表示指针变量pointer指向变量value的地址,可以把这一行中的&符号理解为”取地址操作”,只有取地址操作后,等于号”=”右边的值才是指针类型,类型相同才能赋值这是基础语法。
  • ③是C++语法的输出语句,效果同C中println,其输出值为0x12345678,说明指针变量的值就是它所指向的变量value的内存地址的值,pointer既然也称之为变量,那就一定也要占据存储空间,指针的大小由内存寻址空间决定(即x86还是x64),刚才说pointer的值是0x12345678,所以它在大端模式计算机里长这样:00010010 00110100 01010110 01111000
  • ④也是输出语句,输出值为5,表示指针变量pointer所指向的那个地址的实际存储的内容是整数5,这里*符号理解为”解引用操作”,意思就是把pointer指针指向的地址的值取出来。

所以,实际上,指针变量pointer的值是变量value的地址,变量value的值既可以用指向它自己的指针通过”解引用操作”表示,又能用该变量自己表示。

那为什么需要指针这个概念呢?

我个人的理解是便于操作,减少变量,因为指针也可以加1减1这样操作的,比如说上面提到的指针pointer+1,结果将是0x1234567C,因为pointer存储空间是4个字节,它加1相当于把它指向的地址往后移动4个字节(16进制的8加上4等于C,不需要进位,所以前面的7不变),假如我有一万个在存储空间中相邻排布的相同类型的变量,我可以用一个该类型的指针变量表示这一万个值,因为我移动该指针的地址就行了,然后”解引用操作”取出该地址存储的值。你可能会说我也可以用a表示所有啊,a=3,用完之后a=4,并非如此,你说的这个a在它的作用域内的地址是固定的,从3改为4是指把a实际存储的值变化了,用指针当然也可以改变其中实际的值,不过更多的是通过一个指针变量取出存储在不同地址的值。另外值得一提到是,上面说的指针变量pointer,刚才只说了它的值是value的地址,那它肯定也有自己的地址啊,没错,它肯定也有地址,它的地址将会是某个二级指针的值,也就是**这个玩意儿了。


脚下留心:

1.大端模式与小端模式

大小端的问题是由CPU决定的,它表示的是对内存中排布的数据的不同解析方式。

假设一个占4个byte的int类型变量的值固定是0x12345678(其中我们称12在高字节位,78在低字节位)。如果该变量在内存空间中如下排布(其中我们称0x100在低地址位,0x103在高地址位),则表示高字节位的12排布在低地址0x100上,说明此款CPU是以大端方式进行解析的。

数据存储地址 0x100 0x101 0x102 0x103
数据存储值 12 34 56 78

如果该变量在内存空间中如下排布,则表示高字节位的12排布在高地址0x103上,说明此款CPU是以小端方式进行解析的。

数据存储地址 0x100 0x101 0x102 0x103
数据存储值 78 56 34 12

2.内存寻址空间

我们都听说过x86操作系统和x64操作系统的概念,其实就是说32位和64位操作系统,但与之对应的应该是硬件处理器CPU,操作系统本质上还是一款软件,它的绝对性能取决于底层硬件设备。什么是内存寻址空间?我们知道32位的CPU连接32根地址线,那么它总共可以表示2^32个内存地址,每个内存地址指向内存中的一个byte,所以总共2^32bytes=4G,即32位的电脑最多可利用的内存空间是4G,亦即32位的电脑只需配4G的内存条就够了,多了也是浪费,根本利用不上。


二、指针与数组

惯例,先上代码段:

int arrayA[3] = {6, 7, 8};//①
int* p_arrayA = arrayA;//②
  • ①是初始化一个长度为3的int型数组arrayA
  • ②是将一个int类型指针p_arrayA指向该数组

这两行代码不报错说明什么呢?说明数组名其实可以当做指针,该指针指向本数组中首个元素,即6所在的地址,而指针p_arrayA也指向数组的首个元素的地址。

它俩有什么区别?

  • 指针的值可修改,而数组名是常量。这里提到常量,常量指不可更改的值,比如说出生日期。常量相对而言的是变量,比如说年龄。刚才提到的p_arrayA是变量,arrayA是常量对应代码的表现就是:
    p_arrayA = p_arrayA + 1; // 合理
    arrayA = arrayA + 1;// 报错
    虽然不能显式的给arrayA重新赋值,但是用它来取值是可以的,比如说arrayA[1]*(arrayA+1)都是数组arrayA的第二个变量的值,即7。
  • 对数组名应用sizeof运算符得到的是该数组的占用内存空间的大小(单位是byte),对指针应用sizeof运算符得到的是该类型的指针变量的大小。
    为什么说sizeof是运算符?
    因为sizeof返回的单位是byte,即变量实际占用的内存大小,而size()或者length()这样的函数返回值一般是数组的长度,即数组成员是多少个。

综上,数组名arrayA可以当做指针用,所以以后看到*(arrayA++)这样的代码不要奇怪。最后,我曾经自己写例子得到指向&arrayA的指针是一个int(*)[3]类型的,int(*)[3]可以理解为长度为3的int类型数组的指针。


三、new和delete,申请、释放内存空间

C++中使用new和delete关键字对内存进行分配和回收,C中使用malloc和free。

1.使用new申请的内存空间为什么需要用delete释放?

举个例子:

你租用了政府一块地(new),并在上面盖了个房子(初始化),并在里面住了一段时间,做了一些事情(使用)。当你使用期限到了, 你应该把地归还政府(delete),公有制不是你想造作就能造作的,人人都申请不归还,白嫖?难不成你想被请去喝茶(计算机资源有限)。

2.被delete了的内存空间为什么不要再使用?

接上一个例子:

你把地归还给政府后,政府这时只是登记一下,某块地现在已经空闲了,它并不会去清除那块地上的房子,但政府随时可能把地提供给其他大老板,虽然房子可能暂时还没拆,但如果你继续住在里面的话(访问已释放的内存),你随时都可能在梦中被推土机碾成肉酱。

3.delete一定要作用于被new的那个变量吗?

不是,delete只要作用于被new的地址就行了。
比如以下代码段:

int* ps = new int;// allocate memory
int* pq = ps;// set second pointer to same block
delete pq;// delete with second pointer
ps = NULL;
pq = NULL;

以上代码仍然可以释放被new申请的内存空间,而不一定非要delete ps;

4.接上一个代码段,为什么要将指针变量显式地置为NULL?

delete一个指针后,编译器只会释放该指针指向的内存空间,而不会删除这个指针本身,且指针重新指向一个未知地址,置为NULL之后再次使用该指针编译器将报错,可以避免后续代码无意使用该指针造成的难以预料的问题,即避免问题2中提到的“被碾成肉酱”。