杨玉磊 · 更新于 2018-06-18 06:01:22

数组与指针(一)

指针是 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] //请与⑴-⑷ ⑹-⑼对比

进入正题:

数组:

数组是指具有相同类型的数据组成的序列,是有序集合。(教科书上的定义)
(即:数组就是内存中一段连续的存储空间。那么我们怎么使用它呢?用数组名。也就是我们用数组名可以在内存中找到对应的数组空间,即数组名对应着地址
那么数组中有这么多元素,对应的是哪个元素的地址呢?对应着首元素的地址。 所以,我们可以通过数组的首元素地址来找到数组)

故:数组名是一个地址(首元素地址),即是一个指针常量。(不是指针变量)
只有在两种场合下,数组名并不用指针常量来表示:

  1. sizeof(数组名) ; sizeof返回整个数组的长度,而不是指向数组的指针长度。
  2. &数组名 ; 产生的是一个指向整个数组的指针,而不是一个指向某个指针常量的指针。

&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

上一篇: 小测试 下一篇: 数组与指针(二)