数组和指针是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. hihoCoder#1039

    刚开始学习C语言,准备在做hiho的题目的过程中来学习,在此进行记录,如果代码中有错误或者不当的地方还请指正. 时间限制:1000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi最近在 ...

  2. 003-Tuple、Array、Map与文件操作入门实战

    003-Tuple.Array.Map与文件操作入门实战 Tuple 各个元素可以类型不同 注意索引的方式 下标从1开始 灵活 Array 注意for循环的until用法 数组的索引方式 上面的for ...

  3. 访问本地Access 数据出错

    访问本地的access数据库时,出现了OleDbException 10Aug2015 晚: 好像找到了问题所在, 虽然不知道背后深层次的原因 改用了C#, 然后command 语句里也update了 ...

  4. 【python】操作excel——xlrd xlwt xlutils

    from xlutils.copy import copy import xlrd # import xlutils #打开已存在的excel rb=xlrd.open_workbook('D:\\1 ...

  5. java 终端练习

    Java第一天笔记 一.Window中常见的dos命令 在哪里操作dos命令: Win7 ---> 开始  ---->所有程序--->附件---->命令提示符 Win7--&g ...

  6. git中.gitignore配置项不起作用-解决办法

    在某个git项目中,.gitignore忽略了*.iml,但是git status命令依然列了出来,最后发现是由于git的缓存造成的. git rm -r --cached . git add . g ...

  7. 第七篇——Mobile Apps,软件的曙光。

    作业三: ShrinkWrap (在包装盒子里面的软件,软件在CD/DVD上): Web APP (基于网页的软件): Internal Software (企业或学校或某组织内部的软件): Game ...

  8. 各种url编码

    URLENCODE和URLDECODE是比较常用的URL参数转换方法,为以后使用方便,自己归类一下.   一.JavaScript: 编码:encodeURIComponent(URIString)  ...

  9. MVC 本地运行可以发布到IIS 报Sorry, an error occurred while processing your request.解决方案

    发布MVC程序的时候经常遇到这种情况,每次都要搞好久才找到问题.最终找到解决办法: 报500错误大部分的情况还是程序存在异常,但全部被MVC错误拦截成了友好提示, 只要在Web.config下添加一行 ...

  10. K均值聚类算法的MATLAB实现

    1.K-均值聚类法的概述    之前在参加数学建模的过程中用到过这种聚类方法,但是当时只是简单知道了在matlab中如何调用工具箱进行聚类,并不是特别清楚它的原理.最近因为在学模式识别,又重新接触了这 ...