学习阶段:高中信竞、大学编程。
前置知识:二进制与十六进制,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=12
4. 多级指针
指针也可以指向指针,称为多级指针。
例程:
int x=1;
int *p=&x;
int **p2=&p;
这里p2
就是一个二级指针,它的类型是int**
,它也存了一个地址,但是这个地址是某个int*
型变量的地址,在这里是存了int*
型变量p
的地址。
假设x
的首地址是0x10,p
的首地址是0x20,那么内存的情况如图6所示:
三级、四级等等更多级的指针也是存在的,但是几乎不会用到。二级指针一般只在二维数组中会用到,其他情况也很少见。尽量不要使用多级指针,不然真的容易弄晕自己。
5. 答疑
5.1 星号*
的不同作用
在指针声明、数据类型中,*
表示是指针类型;在已声明变量前面的*
是解地址运算符,比如说*p
就是用p
的内容求得其所指的区域;当然,*
还有算数乘法等含义。