数组和指针是C语言里相当重要的两部分内容,也是新手程序员最容易搞混的两个地方,本章我们锁定指针与数组,探讨它们的异同点。

  首先来看指针与数组在声明上的区别:

  int a[10];

  int *p;

  很明显的,第一个是数组a,第二个是指针p。下一个问题是a的类型是什么?p的类型是什么?a[0]的类型是int,而a是个数组名,它是否表示整个数组呢?事实并非如此,a是一个指针常量,是一个指向int的指针常量,而p是一个指向int的指针,是一个变量。这是它们的第一个区别:一个是常量,一个是变量。那么常量和变量之间存在的区别,它们都存在。比方说,变量可以相互赋值,而常量则不行,如下面这个例子:

  int a[10];

  int b[10];

  int *c;

  a与b均是数组名,为常量指针,而c是一个变量,所以你可以对c进行赋值,比如说c = &a[0],或者c = a,都没有问题,可是考虑一下b = a吧,一下子就会发现这条语句是非法的,如果你要复制一个数组,只能一个变量一个变量的用循环赋值。

  第二个重要的区别是两者的访问方式不同,尽管它们的访问形式看起来都可以用下标引用。

  char a[] = "helloworld";

  char *p = "helloworld";

  比如在上面两个定义中,如果你查看a[3]的值,那么肯定是 'l',但是同样的你也可以查看p[3],答案同样也是 ‘l’,不过访问的方式稍有区别:

  首先看到a[3],它等同于*(a + 3),我们假定a这个常量指针的地址为1000,那么数组在内存中可能是这样子的:

  

  直接代入a的地址看看,那么就是*(1000 + 3),就是1003号地址,显而易见的就是字符 ’l‘。再来看看指针访问的方式:

  这里面的“Helloworld”是一个字符串常量,系统会为它分配一片内存,2345这个地址是随机分配的,指针的地址1032也是随机的,让指针p指向一个常量字符串就是将地址1032的内容改为字符串常量的首地址,那么现在来看p[3]的意思,首先要取p的地址中的内容也就是2345,然后将下标所表示的偏移量加上指针的值,产生一个地址,那么就是2345+3,这个地址就是2348,之后在再对2348这个地址进行访问,从而得到字符 'l'.

  用char说明可能比较简单,在这里用char作为类型会让你忽略一个细节,如果使用int的话,这个细节将会被你注意到,那就是它们计算地址的方式,比方说还是以上面的例子,不过这次换成int作为类型。

  int a[10];

  int *p;

  同样考虑a[3]与p[3],这下子问题就来了,char在内存中刚好占一个字节,而int,一般来说,在32位机器上占了4个字节,那么a[3]是否还等于*( a + 3)呢?那明显不对,因为假定a[0]的地址为1000,那么a[1]的地址就是1004了,a + 3的值为1003的话应该取多少呢?答案是,在这里应该这么计算,它等于:

  *( a + 3 * int)

  要乘以一个类型长度,所以它实际上就是*( a + 3 * 4),取的地址是1012,。

  那么对于p[3]呢?也是一样的,由于指针的类型不同,指针也会要乘以一个int的长度值。

  经过对两种访问方式的探讨,我们可以发现,如果你把声明和定义搞混了,那是要出问题的,比如说,你定义了一个数组char p[10],但是你在其他文件使用它的时候对它进行了这样的声明extern char *p,那么你就有大麻烦了,由于数组和指针的访问方式不同,你的数组中的那个值可能要被作为一个地址,把一个数值当成一个地址,再去取里面的值,它是一个垃圾值。

  当然,除了访问方式的不同,它们保存数据的方法也有区别

  仍然如上例,当你声明定义一个数组时,它会实际为你分配一片区域;而你声明定义一个指针时,它只为你分配指针变量的内存,指针所指向的值并不分配内存,并且这种情况只对字符有用,比如说你可以这样:char *p = "helloworld",但是,你却不能这样:float *p = 3.14,这条语句是非法的。

  主要区别就在于上面的几点,另外下面来看看它们之间的相同之处:体现得最明显的一点就在于当数组名作为函数的参数传递时:

  int function(char a[]);

  像上面这条语句肯定是没有问题的,它说明一个字符数组被作为参数传递给函数function,不过它居然没有说明数组的大小呢?仔细想想,你就会觉得这个用法相当机智,有下面几点原因:首先这说明所有的数组无论大小,都可以作为同一个函数的参数,你想想,如果处理只有两个元素的数组和只有三个元素的数组都要编写不同的函数,那将会是一个相当恐怖的事情;

  后面的优点则要基于它的传递行为,上面那个语句等价于下面这个:

  int function(char *a);

  这能够很好的应用于函数的一个性质:所有的参数传递均为传值调用,也就是说形参都只是实参的一份拷贝,在function这个函数里,由于传递的参数是指向原数组a的一份指针拷贝,所以你可以放心的使用a++之类的语句,而不用担心编译器会警告你修改数组名。

  从上面的比较可以看出,指针和数组之间关系密切,两者既相似又有很大的区别,所以大家使用时一定要小心,上面只是列出它们之间的主要区别和主要共同点,至于有其它的一些关系,大家可以参考其他资料。

《C专家编程》第四章——令人震惊的事实:数组和指针并不相同的更多相关文章

  1. [转]Windows Shell 编程 第四章 【来源 http://blog.csdn.net/wangqiulin123456/article/details/7987933】

    第四章 文件的本质 以前,所有文件和目录都有一个确定的属性集:时间,日期,尺寸,以及表示‘只读的’,‘隐藏的,‘存档的’,或‘系统的’状态标志.然而,Windos95(及后来的WindowsNT4.0 ...

  2. 《C专家编程》第二章——这不是Bug,而是语言特性

    无论一门语言有多么流行或多么优秀,它总是存在一些问题,C语言也不例外.本章讨论的重点是C语言本身存在的问题,作者煞费苦心的用一个太空任务和软件的故事开头,也用另一个太空任务和软件的故事结尾,引人入胜. ...

  3. 读高性能JavaScript编程 第四章 Duff's Device

    又要开始罗里吧嗦的 第四章  Summary 了. 这一次我尽量精简语言. 如果你认为 重复调用一个方法数次有点辣眼睛的话 比如: function test(i){ process(i++); pr ...

  4. 读高性能JavaScript编程 第四章 Conditionals

    if else 和 switch    &&    递归 if else 和 switch 一般来说,if-else 适用于判断两个离散的值或者判断几个不同的值域.如果判断多于两个离散 ...

  5. Windows核心编程 第四章 进程(下)

    4.3 终止进程的运行 若要终止进程的运行,可以使用下面四种方法: • 主线程的进入点函数返回(最好使用这个方法) . • 进程中的一个线程调用E x i t P r o c e s s函数(应该避免 ...

  6. Windows核心编程 第四章 进程(上)

    第4章 进 程     本章介绍系统如何管理所有正在运行的应用程序.首先讲述什么是进程,以及系统如何创建进程内核对象,以便管理每个进程.然后将说明如何使用相关的内核对象来对进程进行操作.接着,要介绍进 ...

  7. 【读书笔记】C#高级编程 第四章 继承

    (一)继承的类型 1.实现继承和接口继承 在面向对象的编程中,有两种截然不同的继承类型:实现继承和接口继承. 实现继承:表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数.在实现继承中, ...

  8. 《C专家编程》第一章——C:穿越时空的迷雾

    这一章讲的是C语言的发展史,包括它是多么不经意的诞生,而后又经历了早期C.K&C.ANSI C的各种阶段,直到它现在形成的这个样子.C语言从来不是一门完美的语言,所以它一直在发展,直到今日,它 ...

  9. windows核心编程---第四章 进程

    上一章介绍了内核对象,这一节开始就要不断接触各种内核对象了.首先要给大家介绍的是进程内核对象.进程大家都不陌生,它是资源和分配的基本单位,而进程内核对象就是与进程相关联的一个数据结构.操作系统内核通过 ...

随机推荐

  1. ubuntu方块乱码

    更改下环境变量/etc/default/locale LANG="en_US.UTF-8"LANGUAGE="en_US:en"

  2. 去除 UINavigationController.navigationBar下方的横线

    self.navigationController.navigationBar.clipsToBounds=YES;

  3. 第一章 JavaScript简史

    JavaScript:  一种使网页具有交互能力的程序设计语言. BOM: 浏览器对象模型,指通过JS用来调整Web浏览器的高度.宽度和位置属性的办法. DHTML: 1.利用HTML标记各种元素   ...

  4. Thinkphp 带查询条件数据分页

    //查询条件中如果有中文 $keyword= urldecode(I("request.keyword")); if ($keyword!=""){ $Mode ...

  5. SparkStreaming运行出现 java.lang.NoClassDefFoundError: org/apache/htrace/Trace 错误

    1.简介 最近在摸索利用sparkstreaming从kafka中准实时的读取数据,并将在读取的过程中,可以做一个简单的分析,最后将分析结果写入hbase中. 2.出现的问题 (1)将从kafka中读 ...

  6. 可滑动的ExpandableListView

    可以向左滑动的扩展列表 向左滑动源码是参照GitHub上的里的 ListView的思路写出来的,按照他的思路,由于本人水平有限,只写了关键代码,能够完美运行,adapter改变之后能自动收回. 滑出状 ...

  7. String类和StringBuffer类的区别

    首先,String和StringBuffer主要有2个区别: (1)String类对象为不可变对象,一旦你修改了String对象的值,隐性重新创建了一个新的对象,释放原String对象,StringB ...

  8. redis基础总结

    Redis 数据类型: String Hash String类型: 一个key对应一个value,二进制安全的. set方法:设置对应值的value set name value get方法:获取对应 ...

  9. 关于ASPXGridview的双击事件弹出 【转】

    在网上找了好长时间,关于ASPXGridview的双击事件弹出ASPxPopupControl,也没有找到适合自己的代码,这里将自己编写出来的代码与大家分享一下 希望我的代码能够对你有所帮助. 直接上 ...

  10. 【转】C#多线程

    C#中的多线程编程 C#是.Net平台的通用开发工具,它能够建造所有的.Net应用.在.Net中所有线程都运行在应用程序域(AppDomain)中,这也许让你想到Win32进程,实际上它们还是有很大的 ...