小议C语言中的数组和指针

1.引言
  在C语言中,数组和指针是其中非常重要又联系紧密的两种数据类型,同时也是重点难点集中的地方。在学习这些内容时,经常会碰到这样一些问题,例如:数组名是什么,就是首地址吗?数组如何访问元素?数组为什么不能进行越界检查?数组表示法和指针表示法有何关系,谁更好?正确理解这些问题,对指针和数组的使用是非常有帮助的。
2.数组名的含义
  在大多数的教材中都对数组名作出这样的解释:数组是一组数据的集合,它们在内存中占据一片连续的存储空间,数组名并不代表整个数组,而是数组占据的连续空间的起始地址[1]。例如:
  int a[10], *p;
  p=a;
  在上面的语句直接将数组名赋值给指针变量p,指针变量p就获取了数组a的首地址。在这里a表示数组的首地址,但并不是所有场合数组名都是首地址。例如:
  int a[10];
  printf("%dn",sizeof(a));
  printf("%dn",a);
  printf("%dn",&a);
  将程序补充完整运行后得到如下结果:
  小议C语言中的数组和指针
  如果将数组名理解为指针常量,sizeof(a)的值应该是4,因为所有类型的指针都只占4个字节。而实际运行的结果是40,sizeof(a)返回的是整个数组所占的字节数。同样,如果数组名是指针常量的话,&a的值应该是指针的地址, &a和a应该是两个不同的结果。而结果是数组的首地址,&a和a结果相同。可以看出,在sizeof(a)和&a这两种用法中,a都被理解为数组本身而不是数组的首地址。
  所以,数组名和指针常量并不等同,数组具有确定数量的元素,数组名除了是首地址外还包含了数组长度、数组类型等的属性信息。而指针是一个标量,只有一个表示地址的值。当数组名出现在表达式(sizeof和&除外)中,编译器会其生成一个指针常量。因此,应该说数组名的值是指针常量,而不能说数组名就是指针常量[2]。
3.字符串常量和指针
  字符串常量在内存中占据一块连续的存储空间,不能当作一个简单的标量,这点和数组是非常相似的。如果表达式中出现字符串常量该如何理解呢?既然数组名的值是数组的首地址,那么字符串常量是否也有类似的用法呢?我们知道字符串常量可以赋值给字符指针变量,例如:
  char *ptr="Hello";
  "Hello"是个字符串常量,通过上面的操作指针ptr获取了该字符串在内存中的首地址
这里字符串常量理解为其首地址。如果字符串"Hello"出现在其他地方是否也可以同样理解呢?请看下面的程序。
  printf("%sn","abc"+1);
  printf("%cn",*"abc");
  printf("%cn","abc"[2]);
  printf("%cn",*("abc"+2));
  将程序补充完整运行后结果如下:
  小议C语言中的数组和指针
  结果跟预料的一样,在上面的表达式中,字符串常量的值也是该字符串的首地址。"abc"+1的结果是一个指向字符b的指针;*"abc"表示首地址指向的一个字符;"abc"[2]和*("abc"+2)等价,表示将首地址加2,然后取出新地址指向的一个字符。这和我们对数组名的使用方式类似,字符串常量的值是该字符串的首地址。字符串常量的这种用法可读性较差,但在某些场合下使用会有很好的效果。例如下面的函数实现将10进制数转换为16进制数。
  Btoascii(unsigned int num)
  {   unsigned int quo,rem;   //quo为除16的商,rem为除16的余数
      quo=num/16;
      if(quo!=0)  Btoascii(quo);
      rem = num;
      if(rem<10)  putchar(rem+'0');
      else        putchar(rem-10+'A');}
  由于字符0到9和字符A到F的ascII码并不连续,所以上面的程序中使用了if分支来对取余后的结果分别处理转换成相应的16进制字母。如果使用字符串常量则比较简单。改写后的函数如下:
  Btoascii(unsigned int num)
  {   unsigned int quo,rem;   //quo为除16的商,rem为除16的余数
      quo=num/16;
      if(quo!=0)  Btoascii(quo);
      rem = num;
      putchar("0123456789ABCDE"[rem]);}
4.数组如何访问元素
  在C语言中,数组访问元素的[ ]符号被解释为下标运算符。在大多数的教材中是这样解释的:在计算整型a[i]时,先将整型的字节数4×i,再加到首地址a上得到元素地址,然后通过地址取出该元素的值,即a[i]和*(a+i)等价。确实,从两种写法编译后的汇编语句来看是完全相同,但这只能证明两者是等价的,但并不能说明a[i]就是按*(a+i)编译处理的。那么有没有什么方法可以验证a[i]就是按*(a+i)编译处理的呢?
  如果下标表示法是按指针表示法编译执行的,那么可以设想2[a]的写法也应该是对的,因为它可以解释为*(2+a),同样可以访问数组的第3个元素。编程如下:
  int a[10]={0,1,2,3,4,5,6,7,8,9};
  printf("%dn",2[a]);
  将程序补充完整后运行结果为:
  小议C语言中的数组和指针
  可以看出2[a]也是可以编译通过的写法,并且就等于a[2],这只有按指针法来理解才说的通,否则它是不符合C语言里关于标识符的命名规则的。理解了下标法的编译处理方式,也就不难理解为什么C语言中不对数组进行越界检查了。因为数组访问元素的下标运算实际是变址运算,与指针表示法是等同的,所以即使下标值超过了数组的长度,也是可以计算地址并访问数据的,只不过访问到的数据是不确定的。
5.下标法和指针法的效率比较
  既然下标法和指针法是等价的,那么哪种表示法的效率更高呢?在很多教材里都提到指针的代码效率高,但它究竟是如何做到的呢,我们试着来分析一下。例如:将数组元素依次赋值为0,下标法代码如下:
  int a[10],i;
  for(i=0;i<10;i++)
  a[i]=0;
  在VC6.0中编写这段代码并查看对应的汇编语言结果如下:
  00401028 mov  dword ptr [ebp-2Ch],0
  0040102F jmp  main+2Ah (0040103a)
  00401031 mov  eax,dword ptr [ebp-2Ch] 
  00401034 add  eax,1
  00401037 mov  dword ptr [ebp-2Ch],eax 
  0040103A cmp  dword ptr [ebp-2Ch],0Ah
  0040103E jge  main+3Dh (0040104d)
  00401040 mov  ecx,dword ptr [ebp-2Ch] 
  00401043 mov  dword ptr [ebp+ecx*4-28h],0 
  0040104B jmp  main+21h (00401031)
  将源代码改写成指针的形式:
  int a[10],*p;
  f

or(p=a;p<a+10;p++)
  *p=0;
  在VC6.0中编写这段代码并查看对应的汇编语言结果如下:
  00401028 lea  eax,[ebp-28h]
  0040102B mov  dword ptr [ebp-2Ch],eax
  0040102E jmp  main+29h (00401039)
  00401030 mov  ecx,dword ptr [ebp-2Ch]
  00401033 add  ecx,4
  00401036 mov  dword ptr [ebp-2Ch],ecx
  00401039 lea  edx,[ebp]
  0040103C cmp  dword ptr [ebp-2Ch],edx
  0040103F jae  main+3Ch (0040104c)
  00401041 mov  eax,dword ptr [ebp-2Ch]
  00401044 mov  dword ptr [eax],0
  0040104A jmp  main+20h (00401030)
  比较这两段汇编代码,可以看到对a[i]的求值需要先取得i的值,并将i与4(整型的长度)相乘然后从地址中取值,而乘法是要花费一定的时间和空间的。指针表示法中,p++的实现是直接将p的值加4,没有用到乘法。显然这两段代码,第2种使用了指针表示的代码执行效率较高。
  从上面的举例可以看出,当要让数组1次1步的移动时,指针比下标要更有效率。但要注意仅仅将下标法改写成指针法是没有任何区别的。例如:
  int a[10],i;     int a[10],i;
  for(i=0;i<10;i++)    for(i=0;i<10;i++)
  a[i]=0;      *(a+i)=0;
  这两段代码产生的编译代码完全一样。指针的高效率体现在将下标变址语言中的乘法转换成了指针变量的加法,实现这种转换的代码才会获得效率的提高。对程序而言,可读性对代码的维护也是非常重要的,这点下标法要比指针法好。因此,在对可读性影响不大的前提下,可选择用指针法代替下标法。
6.小结
  数组和指针是C语言中频率很高的两种数据类型,其中涉及到的语法内容很多。本文主要对教材中较少涉及或未深入分析的一些语法现象(例如,数组名的含义、数组如何访问元素、下标法和指针法的效率比较)进行了分析研究。通过这样的分析,可以帮助理解数组和指针两者之间的一些语法关联,并促使学习者对C语言中更多关联语法现象进行思考,更好地掌握C语言的语法规则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值