Motivation
最近我总是试图利用指针去访问数组或者进行相反的操作,然而结果往往不尽人意。而刚好我又想撰写一篇稍微具有深度一点的文章来表达我的思考,在持续三天(4.20~4.23)的阅读和思考过后,是时候去完成这样一篇博客了。
An introduction
我所阅读的书是鼎鼎有名的《C专家编程》。其中第四章、第八章、第九章持续地讨论了C中的指针何时相同、何时不同、在函数的调用过程中,传递数组和传递指针的区别和本质、多维数组如何作为函数参数等等问题。
What’s the problem?
#The broken program
when I define an array and visit it as a pointer
When I define a pointer and visit it as an array
Why?
- 指针和数组并不相同。
访问a[5]对于数组char a[10]=”abcdef”来说,在程序编译阶段,我们就可以得到数组首地址;运行的第一阶段取偏移量5,将10和首地址相加得到要访问的数组元素的地址,运行的第二阶段根据计算得到的地址访问对应元素。
而对于指针char *p=”abcdef”来说,程序编译阶段,我们首先得到指针变量p的地址,在运行阶段,根据p的地址得到p的内容(也就是p指向的元素的地址),再根据p的内容访问所指向的元素。
简单来说,因为数组中本身存的是数据,指针中存在的是数据的地址;因此数组直接访问数据,指针间接访问数据。
因为二者在底层实现逻辑上的不同,导致了混淆使用时的错误。
在这本书的第四章,作者给出了这样的建议“声明与定义相匹配”。当我们想使用一个数组,就定义一个数组;想使用一个指针,就定义一个指针。 - 之所以我们会认为二者可以等同,是因为在大多数的场景之中,我们在使用二者的时候可以互换(尤其是在使用数组的时候用指针替换)。我们被告知当数组作为函数的参数之时,我们既可以传递数组,同时也可以传递数组首个元素的指针;典型例子包括在我以上程序中所使用的printf(“%d ”,p)和puts§。
在C语言的函数参数传递中,采用的是值传递的方法。简单来说,就是将实参拷贝一份传递给调用的函数。而对于数组这种特殊的庞大的数据结构,对其进行拷贝太浪费时间、空间了,因此在传参的过程中,默认传递数组首个元素的指针。(这里我仅就一维数组进行讨论,因为多维数组的规则有些复杂,作者在书中228页,第10章也有说明)。
再往前追溯历史,C语言的缔造者们认为数组的存在似乎没有必要,因为数组能做的指针都能做,数组不能做的,指针照样能做。因此对于数组的访问来讲,总可以用指针+对应偏移量来替换。
例如:
char a[10] = “hello world”;
char *p = a;
c = a[5]; //第一种方法
c =*(p+5);//第二种方法
c=p[5];//第三种方法
p = a+5; c=*p; //第四种方法
所得到的c值总是相同的。
Something inward
在阅读的过程中,我愈发能体会到编程的乐趣和快感,也能感受到C语言旺盛的生命力和历史底蕴,也因此感到自己的无知,所以我准备在以后的每一篇博客开启一个sharing的版块来追溯一下C语言的历史。