指针就是指向一个特定内存地址的一个变量。简单的说可以理解为一个一维的线性空间,其中的每一个数对应一个存储单元,就是1个字节。指针有两个性质:指向性和偏移性。指向性指的是指针一定要有一个确定的指向,偏移性就是说指针可以加上一个数后偏移位置!指向别的存储空间。
指针的应用往往体现在数组,我们从数组开始解释指针的偏移。数组就是许多的变量,它的一个重要特征就是在内存空间中连续地存放,而且是按下标顺序存放。比如我们定义一个有100个变量的一维整型数组,它一定从内存的某一个存储单元(这个存储单元是电脑随机分配的)开始按数组下标顺序存放,连续占用100*4=400个字节。当我们定义一个数组时,系统就会自动为它分配一个指针,这个指针指向数组的首地址。
为了让系统了解每一次指针偏移的单位,也为了方便编程人员进行指针偏移(让编程人员记住一个整形变量占用4字节,一个字符型变量占用1字节……等等是很麻烦的),不用每次去计算要偏移多少个字节,C语言引入了指针的基类型的概念。基类型的作用就是*让系统了解某个指针每次偏移的字节数*。比如,对于一个字符型指针,它每次偏移(比如p=p+1)所起到的作用就是让指针偏移1个字节;而对于一个整型指针,它每次偏移就应该是4个字节。这样操作数组时就带来了方便。比如对于一个指向某个整型数组起始存储单元(称为首地址)的指针p,p=p+1就表示将该指针指向这个数组的下一个元素的存储单元,即向后移动4字节,而不仅仅是移动一个存储单元(即移动1字节)。
我对&()、*()、和[ ]运算符的理解
将&()、*()和[ ]都看成是运算符(我自己只这样想的)。这样可以方便理解他们的作用。简单地说,&()将某个变量转化为其在内存空间中的地址,而*()是指向一个对应于某个地址的标示符(变量或者类型,后面在解释2级指针的时候可能不是变量),[ ]要更复杂一点,p[i]表示将p这个指针虚拟地按其基类型进行i个单位的后移,再进行*(p)运算。但这是一个虚拟的后移,即p[i]并不改变p的指向,只是将其后移i个单位并取*()运算的结果算出来了而已。要改变指针的指向,我们只能通过类似于p=p+i这样的语句来实现。
实际中,我们往往不愿意经常改变指针的指向,因为指针的移动虽然是自由的,但移动后往往会“移不回来”,因为我们可能无法清楚地确定指针的偏移量。而且容易把你搞晕~!
指针类型和系统自动分配的指针(自动分配的指针在数组中出现)
指针可以指向几乎所有我们所设计东西:函数、数组、结构体、链表节点(结构体中有链表节点)等等。其中不同函数间往往并不存在严格的线性关系。链表节点可以根据算法需要在逻辑上(或物理上)不按线性连续存储。但数组、结构体的共同点就是它们在物理上都是线性连续存储的。只要指针指向了它们的首地址,就可以通过简单的偏移来访问各个它们的元素。指针的偏移性在这两种数据结构中发挥着非常非常重要的作用。这时,我们再想想基类型的定义的目的,就会有更好的的认识了。对于一个数组或结构体,它的基类型长度应当是其元素(比如整型,字符型)的长度(这里的长度即指在内存空间中占用的字节数),而不再限于定义为某种简单数据类型的长度。
在我们定义数组和函数时,系统都会为其自动分配一个指向其首地址的指针。其中,指针在数组中的应用是最多的。对于一个数组,其名称就是一个指针变量,亦即假如我们定义“int a[10];”的同时就定义了“int *a=a;”(这个只是我这么想的,这样的语句显然是不合法的,但是可以帮助我们去理解自动分配的指针,就是在数组中为什么可以用a[3]=*(a+3))。
数组应用的二级指针(这里只是说二维)
设定一个指向指针的指针,即设定一个二级指针。一般指针不宜超过二级,否则你肯定被弄晕。
(注意下面指针所指的基类型)
首先,我们分解二维数组,即将一个M*N的二维数组分解为有M个元素的一维数组,它的每个元素都是一个具有N个元素的一维数组。实际上,二维数组把每一行看作它的一个元素,然后按照一维数组的按下标顺序排列的原则以每一行为单位进行排列。而对于每一行,也还是按照一维数组按下标顺序排列的原则进行排列。也就是说,我们可以按行优先的方式将数组的数字逐个“填入”内存空间。或者也可以说,多维数组在内存中的排列方式是递归的。
既然如此,当我们定义 “int a[10][10]”的时候,a是什么样的指针呢?a就是一个二级指针。它的基类型是有10个元素的一维数组,不再是整型变量了。它所指向的是一维数组指针(第一行的数组指针)。当我们执行a=a+1的时候,a将指向二维数组第二行的数组指针,而不是第一行的第二个元素,因为基类型的长度决定了a+1跨越了一整行。
因此,我们要得到数组a的(i,j)位置上的元素的值,应该按照下面的步骤来进行:
1、 a+i,这表示将a指针移到第i行的首地址。
2、*(a+i),这表示将第i行的首地址转化为第i行的标识符(就是说他所指的不是一个变凉了),前面已经述及,*()运算符的作用就是将地址转化为标识符。但*(a+i)不是第i行的第一个元素而是一个指针(可以理解为指针降级了),这个指针的基类型已经变成了整型变量(就是和老师讲的指针是一个整型变量),不再是有10个元素的一维数组了。或许你认为,第i行的首地址不就是第i行第一个元素的地址吗?那么*(a+i)不就是第i行第一个元素的值了?首先,我们可以肯定*(a+i)不是第i行第一个元素的值,但第i行的首地址的确就是第i行第一个元素的地址。前面对*()运算符的说明只是一个表面现象(用逻辑思维理解),下面的说法可以辅助你理解*()运算符的真正本质:*()将指针还原为其所指,而不是简单地将地址变成这个地址所存储的值。*()将地址变成这个地址所存储的值这样的说法只对一级指针是正确的。对于二级指针,*()只是将二级指针还原为其所指,即还原为一级指针(降级)。物理理解上“第i行的首地址同时就是第i行第一个元素的地址”这一事实,是容易导致混淆的根本原因。但只我们要从逻辑的角度出发,就可以较为轻松地理解这个问题了。
3、*(a+i)+j,这表示将一级指针向后偏移j个单位, 要注意*(a+i)这个指针已经是一个以整型变量为基类型的指针了。这时*(a+i)+j是一个偏移后的一级指针,它的值是a[i][j]元素的地址(注意是地址),亦即它所指的就是a[i][j]元素。
4、*(*(a+i)+j),将一级指针还原为其所指,即得到了a[i][j]元素的值。
1
2
3
4
5
6
7
|
int
some_func(
struct
something *a)
{
…
…
memset
(a,0,
sizeof
(a));
…
}
|