本文主要是介绍小议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);
将程序补充完整运行后得到如下结果:
如果将数组名理解为指针常量,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));
将程序补充完整运行后结果如下:
结果跟预料的一样,在上面的表达式中,字符串常量的值也是该字符串的首地址。"abc"+1的结果是一个指向字符b的指针;*"abc"表示首地址指向的一个字符;"abc"[2]和*("abc"+2)等价,表示将首地址加2,然后取出新地址指向的一个字符。这和我们对数组名的使用方式类似,字符串常量的值是该字符串的首地址。字符串常量的这种用法可读性较差,但在某些场合下使用会有很好的效果。例如下面的函数实现将10进制数转换为16进制数。
Btoascii(unsigned int num)
{
由于字符0到9和字符A到F的ascII码并不连续,所以上面的程序中使用了if分支来对取余后的结果分别处理转换成相应的16进制字母。如果使用字符串常量则比较简单。改写后的函数如下:
Btoascii(unsigned int num)
{
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]);
将程序补充完整后运行结果为:
可以看出2[a]也是可以编译通过的写法,并且就等于a[2],这只有按指针法来理解才说的通,否则它是不符合C语言里关于标识符的命名规则的。理解了下标法的编译处理方式,也就不难理解为什么C语言中不对数组进行越界检查了。因为数组访问元素的下标运算实际是变址运算,与指针表示法是等同的,所以即使下标值超过了数组的长度,也是可以计算地址并访问数据的,只不过访问到的数据是不确定的。
5.下标法和指针法的效率比较
既然下标法和指针法是等价的,那么哪种表示法的效率更高呢?在很多教材里都提到指针的代码效率高,但它究竟是如何做到的呢,我们试着来分析一下。例如:将数组元素依次赋值为0,下标法代码如下:
int a[10],i;
for(i=0;i<10;i++)
a[i]=0;
在VC6.0中编写这段代码并查看对应的汇编语言结果如下:
00401028
0040102F
00401031
00401034
00401037
0040103A
0040103E
00401040
00401043
0040104B
将源代码改写成指针的形式:
int a[10],*p;
f
or(p=a;p<a+10;p++)
*p=0;
在VC6.0中编写这段代码并查看对应的汇编语言结果如下:
00401028 lea
0040102B mov
0040102E jmp
00401030 mov
00401033 add
00401036 mov
00401039 lea
0040103C cmp
0040103F jae
00401041 mov
00401044 mov
0040104A jmp
比较这两段汇编代码,可以看到对a[i]的求值需要先取得i的值,并将i与4(整型的长度)相乘然后从地址中取值,而乘法是要花费一定的时间和空间的。指针表示法中,p++的实现是直接将p的值加4,没有用到乘法。显然这两段代码,第2种使用了指针表示的代码执行效率较高。
从上面的举例可以看出,当要让数组1次1步的移动时,指针比下标要更有效率。但要注意仅仅将下标法改写成指针法是没有任何区别的。例如:
int a[10],i;
for(i=0;i<10;i++)
a[i]=0;
这两段代码产生的编译代码完全一样。指针的高效率体现在将下标变址语言中的乘法转换成了指针变量的加法,实现这种转换的代码才会获得效率的提高。对程序而言,可读性对代码的维护也是非常重要的,这点下标法要比指针法好。因此,在对可读性影响不大的前提下,可选择用指针法代替下标法。
6.小结
数组和指针是C语言中频率很高的两种数据类型,其中涉及到的语法内容很多。本文主要对教材中较少涉及或未深入分析的一些语法现象(例如,数组名的含义、数组如何访问元素、下标法和指针法的效率比较)进行了分析研究。通过这样的分析,可以帮助理解数组和指针两者之间的一些语法关联,并促使学习者对C语言中更多关联语法现象进行思考,更好地掌握C语言的语法规则。
这篇关于小议C语言中的数组和指针的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!