
学习阶段:高中信竞、大学编程。
前置知识:二进制与十六进制,C语言基础,数组。
指针初学可能比较难理解,我这篇文章尽量用通俗易懂的方式来讲解。
1. 指针概述
为什么有指针这个东西?因为指针很贴近计算机内部的实际工作原理,与内存实际的寻址方式类似。C语言可以说是高级语言中最贴近机器的语言,而像Python、JavaScript这种更亲和于人类的语言与机器的关系则比较远了,甚至它们本身可能还是用C语言写出来的呢。
指针可以形象地比喻为在内存中定位的导航员。内存那么大,怎么知道我需要的东西存在哪里呢?可以让指针来记录与导航。我们先来了解一下内存。
2. 预备知识
2.1 内存
研究过组装机、电脑配件的话,一定知道内存条这个东西。现在这个时代,一台家用电脑的内存一般是2GB、4GB、8GB、16GB、32GB、64GB等等。
把CPU比作干活的人,则内存就相当于工作台。CPU在跑的程序以及很多相关数据都存在内存内,这就相当于人在干活时要把相关资料放在工作台上使用。
内存可以看成是一个巨大的数组,我这里记为
memory[0..n]memory的每一个单元存1B=8bit,memory[0]就是内存的第一个单元。这个中括号里面的数,即数组下标,被称为内存地址,简称为地址。我确定了一个地址,也就相当于确定了内存中的一个单元。
通常,我们说的32位机器,意思就是地址是32bit的,最大支持的内存是
memory[0x00000000..0xFFFFFFFF]最小地址是0,最大地址是0xFFFFFFFF=2^32-1,最大支持
也就是说32位机器理论上最大支持4GB的内存,这就是它逐渐被淘汰的原因。
而64位机器理论上最大支持的内存是
这个量级远大于目前的需求,因此128位机器在相当长的时间内不会出现。
2.2 变量与数据类型
C语言有很多数据类型,不同的数据类型在内存中的占用空间和存储格式也不一样。不同数据类型的存储格式比较复杂,这里不详述。我只谈谈不同的占用空间。
一个变量在内存中占用都是连续空间,记T类型的变量在内存中占用sizeof(T)字节的空间。当我声明一个T类型的变量a时,内存会寻找连续且可用的sizeof(T)个单元,把它们分配给变量a,比如说是memory[100..103]这4B的空间。此后我对变量a进行读写,也就相当于对memory[100..103]这4B的空间进行读写。
某些数据类型的占用空间:short短整形占用2B,int整形占用4B;float浮点形占用4B,double双浮点型占用8B;char字符型占用1B.
3. 指针
指针是一种特殊的数据类型,指针类型的变量应存储的是内存地址。在32位机器上,任何一个内存地址都是32bit=4B,故任何一个指针型变量都占用4B.
现在问题来了,指针指向内存中的一个单元,我怎么知道这个单元里面存的是什么东西,是什么类型的数据?因此,在声明指针变量的时候,也要声明这个指针指向数据的类型,比如int型指针、char型指针等。
3.1 指针的声明
在语法上,声明指针类型使用*符号,例如
int *a, b, *c; //a和c是指针,b不是指针
char *ch;这两句代码声明了int*型的变量a与c、int型变量b以及char*型变量ch. 根据语法,我们习惯上称T型指针为T*型,含义就是T*型变量是指针,其所指的相关内存单元存的是T型数据。 (注意,这里的b变量不是指针,仅仅为普通的int整形。)
3.2 指针的使用
指针有很多种使用方式,包括动态申请内存、函数地址传参等等。我这里仅介绍最简单的使用方式。我提供一份例程,可直接从例程中学习指针的声明与使用。
例程:
int x=1, y=2; //声明x与y并赋初值
int *p=&x; //声明p且p指向x
*p=11; //p修改x
p=&y; //p指向y
*p=12; //p修改y
printf("x=%d, y=%d", x, y); //打印x与y第一行,在内存中申请连续的4B区域存入int型数据1,记为变量x;再在内存中申请连续的4B区域存入int型数据2,记为变量y. 假设x对应内存区域memory[100..103],y对应内存区域memory[200..203]. 第一行执行完毕,内存如图1所示:

第二行, 在内存中申请连续的4B区域存入int*型地址数据100,记为变量p. 这里&符号是取地址运算符,表示取变量x的首地址,在本例中就是100. 这一行代码是声明指针同时赋初值,相当于以下两行代码:
int *p;
p=&x;第二行执行完毕,内存如图2所示(100的十六进制是0x64):

第三行,将p所指的地址起4B空间内存入int型数据11. 这里*是解地址运算符, 表示取得指针所指的内存空间。 第三行执行完毕,内存如图3所示:

如果第三行改为执行p=(int*)11;,则是先把int型数据11强制转换为int*型数据11,然后赋值给p,如图3.2所示:

此时p所指的内存空间不一定是可用的。因此像这样直接给指针赋值一个常数的情况非常罕见。
第四行,p存y的首地址。第四行执行完毕,内存如图4所示:

第五行,把变量y的值改为12. 第五行执行完毕,内存如图5所示:

第六行,打印x与y的值,打印结果应为:
x=11, y=124. 多级指针
指针也可以指向指针,称为多级指针。
例程:
int x=1;
int *p=&x;
int **p2=&p;这里p2就是一个二级指针,它的类型是int**,它也存了一个地址,但是这个地址是某个int*型变量的地址,在这里是存了int*型变量p的地址。
假设x的首地址是0x10,p的首地址是0x20,那么内存的情况如图6所示:

三级、四级等等更多级的指针也是存在的,但是几乎不会用到。二级指针一般只在二维数组中会用到,其他情况也很少见。尽量不要使用多级指针,不然真的容易弄晕自己。
5. 答疑
5.1 星号*的不同作用
在指针声明、数据类型中,*表示是指针类型;在已声明变量前面的*是解地址运算符,比如说*p就是用p的内容求得其所指的区域;当然,*还有算数乘法等含义。