**叁**
***数组与指针(一)***
指针是C的精华,如果未能很好地掌握指针,那C也基本等于没学。
关于指针、数组、字符串,本人当年也是有过一段“惨绝人寰”的痛。好在多看书,多思考,多总结,多实践,方才有些心得。现在把当年的笔记摘录如下,希望能给初学者一些启发。
**先附上两句话:**
**第一句话:指针就是存放地址的变量。(就是这么简单。)**
**第二句话:指针是指针,数组是数组。(只是它们经常穿着相似的衣服来逗你玩罢了。)**
*轻松一下*:(见识一下数组和指针的把戏)
1、引用一维数组某个值的方式:(先定义指针p=a)
① a[2] ② *(a+2) ③ (&a[1])[1] ④ *(p+2) ⑤ p[2]
2、引用二维数组某个值的方式:
~~~
例:int a[4][5];
⑴ a[i][j]
⑵ *(a[i]+j)
⑶ *(*(a+i)+j)
⑷ (*(a+i))[j]
⑸ *(&a[0][0]+i*5+j)
又定义:int * p[4], m ;
for(m=0; m<4;m++) p[m] = a[m] ;
⑹ p[i][j]
⑺ *(p[i]+j)
⑻ *(*(p+i)+j)
⑼ (*(p+i))[j] //请与⑴-⑷对比
又定义 int (*q)[5]; q=a ;
⑽ q[i][j]
⑾ *(q[i]+j)
⑿ *(*(q+i)+j)
⒀ (*(q+i))[j] //请与⑴-⑷ ⑹-⑼对比
~~~
好了,看到这的朋友,如果你还没被搞晕的话,那么这篇文章可能不适合你。如果你已经晕头转向、意乱情迷的话,那么也请不要害怕,我不是猥琐的大叔。下面我会带着你揭穿数组和指针之间的把戏。
**进入正题:**
**数组:**
数组是指具有相同类型的数据组成的序列,是有序集合。(国内教科书上的定义)
(即:数组就是内存中一段连续的存储空间。那么我们怎么使用它呢?用数组名。也就是我们用数组名可以在内存中找到对应的数组空间,即数组名对应着地址。
那么数组中有这么多元素,对应的是哪个元素的地址呢?对应着首元素的地址。 故:我们可以通过数组的首元素地址来找到数组 )
故:**数组名是一个地址(首元素地址),即是一个指针常量。(不是指针变量)**
**只有在两种场合下,数组名并不用指针常量来表示:**
⑴**sizeof(数组名) **; sizeof返回整个数组的长度,而不是指向数组的指针长度。
⑵**&数组名**; 产生的是一个指向整个数组的指针,而不是一个指向某个指针常量的指针。
&a[0] 与 &a 的区别:
两者的值相同,但意义不同。&a[0]是指数组首元素的地址。&a是整个数组的地址。
(问题来了,整个数组跨越几个存储单位,怎么表示这几个存储单位组成的整体呢?如果你是编译器,你会怎么做?呃,取其第一个存储单位的值来代表会比较好点。没错,编译器是这么做的。 所以两者的值相同)
a+1 与 &a+1 的区别:
**数组名a除了在上述两种情况下,均用&a[0]来代替。**(实际上编译器也是这么做的)
a+1即等同于&a[0]+1。
注意:**指针(地址)与常数相加减,不是简单地算术运算,而是以当前指针指向的对象的存储长度为单位来计算的。**即:指向的地址+常数*(指向的对象的存储长度)
&a[0]为数组首元素的地址,故&a[0]+1 越过一个数组元素长度的位置。即:&a[0]+1*sizeof(a[0])
&a为整个数组的地址,(只是用首元素地址来表示,其实际代表的意义是整个数组) 故&a+1 越过整个数组长度的位置,到达数组a后面第一个位置。 即:&a+1*sizeof(a)
**指针**:
例:int *p=NULL; 与 *p=NULL ; 的区别
指针的定义与解引用都用到* ,这是让人晕的一个地方。
(不妨这样理解:在定义时 星号只是表示这是一个指针,int * 表示这是一个int型的指针,把int * 放在一起看,表示这是一个整型指针类型。 如果我是C的设计者,那么用$符号来定义指针类型 会不会让大家少些迷惑)
向指针变量赋值,右值必须是一个地址。例:int * p=&i ; 这样,编译器在变量表里查询变量i对应的地址,然后用地址值把&i替换掉。 那么我们能不能直接把地址值写出来作为右值呢?当然。指针不就是存储地址的变量嘛,直接把数字型的地址值赋给它有什么问题。(前提是这个地址值必须是程序可访问的)
例:**int * p=(int *)0x12ff7c ;**
*p= 0x100 ;
这里的0x12ff7c可看做某个变量的地址。
需要注意的是:将地址0x12ff7c赋值给指针变量p的时候必须强制转换。(我们要保证赋值号两边的数据类型一致)
***地址的强制转换:***
~~~
例:double * p ;假设p的值为 0x100000
求下列表达式的值:
p + 0x1 =___
(unsigned long)p + 0x1 = ___
(unsigned int *)p + 0x1 = ___
~~~
**注意:一个指针与一个整数相加减。这个整数的单位不是字节,而是指针所指向的元素的实际存储大小。**
故:p + 0x1:p指向的是一个double型变量,故值应为:0x100000+0x1*8=0x100008
(unsigned long)p则意为:将表示地址值的p强制转换成无符号的长整型。(即:告诉编译器,以前变量p里存储的是内存中的某个地址,现在变量p里存储的是一个长整型。即让编译器看待变量p的眼光改变一下,以后p是一个整型变量了,不是指针了,不要把它里面的值当做某个变量的地址了,不能根据这个地址去找某变量了。)
任何数值一旦被强制转换,其类型就变了。(即编译器解释其值代表的含义就变了)
故:(unsignedlong)p + 0x1 是一个长整型值加一个整型值,结果为:0x100001
(unsigned int *)p则意为:将一个表示double型变量的地址值的指针,转换成一个表示unsigned int型变量地址的指针。
故(unsigned int*)p + 0x1值为:0x100000+sizeof(unsignedint)*0x1 等于 0x100004
【**强制转换指针类型的目的是为了:改变指针的步长(偏移的单位长度)**】
注意:两个指针直接相加是不允许的。(你要真想把两个地址值相加,把它们先都强制转换为int型即可)
两个指针直接相减在语法上是允许的。(但必须相对于同一个数组,结果是两指针指向位置相隔的元素个数)
**指针表达式:**
**注意:*与++优先级相同,且它们的结合性都是从右向左的。**
例:char ch ;char *cp=&ch ;
指针表达式:
①*++cp 先运算++cp,再解引用*。
当其为右值时,是ch下一个存储单元的值(是一个垃圾值)
当其为左值时,是ch的下一个存储单元
②(*cp)++
当其为右值时,表达式的值等于ch的值,(但它使ch值自增1)
当其为左值时,非法。
【**注意:++,--的表达式(及大部分的表达式,数组的后缀表达式除外)的值都只是一种映像(暂存于寄存器),不在内存区中,故无法得到它们的地址,它们也无法做左值**】
★故:(*cp)++表达式的值虽与ch相同,但它只是ch值的一份拷贝,不是真正的ch
③++*cp++
当其为右值时:表达式的值等于ch+1,这个值只是一个映像(寄存器中)。(但这个表达式实际做了一些工作:使cp指向ch的下一个单元,使ch中的值增1)
当其为左值时:非法。
【++,--,与*组合的指针表达式只是把几个工作融合在一个表达式中完成,使代码简洁,但可读性差】
例:对于 *cp++ ; 我们可以把它分解为: *cp 之后再 cp++
对于 *++cp ; 我们可以把它分解为:++cp 之后再*cp