数组和指针是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. MFC-01-Chapter01:Hello,MFC---1.3 第一个MFC程序(05)

    1.3.4 绘制窗口 如何在屏幕上随心所欲的进行绘制?应用程序通过响应来自Windows的WM_PAINT消息进行绘制的,此消息通知它更新窗口. WM_PAINT消息如何发生:窗口位置改变:窗口大小改 ...

  2. JSON入门学习

    JSON是一种与开发语言无关的轻量级的数据格式(JavaScript Object Notation) 优点:易于阅读和编写,易于程序解析和生产 JSON数据格式中没有日期及时间的数据格式的.一般直接 ...

  3. 如何修复Outlook 2007源文件.PST及性能优化补丁

    kb961752 微软发布了改善 Outlook 2007 个人数据文件性能的补丁,该补丁未来将会集成于 SP2 中,但是现在对于SP1用户可以提前得到它.   获取地址: http://suppor ...

  4. 【转】 数据库系统——B+树索引

    原文来自于:http://blog.csdn.net/cjfeii/article/details/10858721 1. B+树索引概述 在上一篇文章中,我们讨论了关于index的几个中重要的课题: ...

  5. volatile关键字和mutable关键字

    如果不用volatile关键字会如何?可能会造成一个后果就是:编译器发现你多次使用同一个变量的值,然后它可能会假设这个变量是不变的值,并且把这个变量的值放入寄存器中,方便下一次使用,提高存取速度. 一 ...

  6. [SSH] SSH学习笔记 - 远程登录

    1.SSH登陆/登出命令 $ ssh <hostname> #登入 $ exit #登出 known_hosts 每个用户都有自己的known_hosts文件,路径:(username)/ ...

  7. C#中datatable导出excel(三种方法)

    方法一:(拷贝直接可以使用,适合大批量资料, 上万笔) Microsoft.Office.Interop.Excel.Application appexcel = new Microsoft.Offi ...

  8. WKWebView

    按错了...,原帖地址http://blog.csdn.net/cyforce/article/details/37657009 webkit使用WKWebView来代替IOS的UIWebView和O ...

  9. Ubuntu系统下Xen虚拟机的基本安装方法(代码创建)

    Ubuntu上Xen安装虚拟机方法一dd一个空的磁盘sudo dd if=/dev/zero of=/home/vm1.img bs=1G count=8 下载Xen VM通用配置文件 sudo wg ...

  10. java 实现一个简易计算器

    import java.util.Scanner;public class Test { public static void main(String[] args) { count(); } pub ...