🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
**叁** ***数组与指针(一)*** 指针是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