牛骨文教育服务平台(让学习变的简单)

叁(数组与指针①)

数组与指针(一)

指针是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