C语言指针和数组的本质区别
来源:互联网 发布:java高级证书试题 编辑:程序博客网 时间:2024/05/22 18:24
前注:
很长一段时间以来,我都模糊地认为指针和数组的本质是一回事,甚至在对别人讲这门课时也不止一次地反复强调这一点以便加深对方的“融汇性理解”(我有罪……),这两天在品读《高质量C++编程》(一本100来页的小册子,语言简洁精炼,强烈推荐)时,由其中的几处细节引起了对这个问题的重新思考,并在反汇编调试工具和CSDN精华区文献的帮助下彻底弄清了这个问题。
在此对曾接受我错误讲解的同志们表示非常道歉,并收集整理出这篇文章供大家了解C语言中数组和指针最本质的区别。
正文:
首先,我们要清楚地建立起这样的概念:数组是一种数据结构,指针是一种真正的指针; 编译器知道的是数组的第一个元素的地址,而编译器只知道的是指针变量所在的地址。
例如:
char a[10];
char *p;
char ch;
则,ch=a[1] 与 ch= *p 在操作上的差别如下:
编译器要找到数组的内容只需要两步: 1、获取数组第一个元素的地址并加上偏移量,形成一个地址;2、从该地址获取数值;
而指针的操作是:1 、获取指针变量所在的地址;2 、获取指针变量的值;3 、以指针变量的值为地址获取所要的值。
而要知道两种表示的本质差别应当从编译器的层次来考量,或者从生成的汇编代码来考量才可以认识其本质,不然也只是雾里看花。
指针是C语言具有低级语言特征的最直接的证据。在汇编语言里面,指针的概念随处可见。比如SP,SP寄存器又叫堆栈指针,它的值是地址,由于SP保存的是地址,并且SP的值是不断变化的,因此可以看作一个变量,而且是一个地址变量。地址也是C语言指针的值,C语言的指针跟SP这样的寄存器虽然不完全一样,但原理却是相通的。C语言的指针也是一种地址变量,C89明确规定,指针是一个保存对象地址的变量。这里要注意的是,指针跟地址概念的不同,指针是一种地址变量,通常也叫指针变量,统称指针。而地址则是地址变量的值。
看到这里,也许你会觉得,这么简单的东西还用你来说吗?的确,对于p与&p来说,99%的人都能在0.1秒内脱口而出谁是指针,谁是地址,但是,又有多少人在使用指针的过程中能够始终如一毫不动摇地遵循这两个概念呢?不少人使用指针的时候就会自觉或不自觉地把指针和地址两个概念混淆得一塌糊涂了,数组名的滥用就是一个活生生的例子。这一点甚至连一些经典著作也没能避免。
不过也不能全怪你自己,我认为某些国内教材应该承担最大的责任。这些教材一开始就没有给读者好好地分清指针与地址的区别,相反还在讲述的过程中有意无意地混用这两个概念。更有甚者,甚至在书中明言指针就是地址……说这话的家伙简直最应该在C语言这个地图上抹掉。曾在购书中心随手翻开了某个作者主编的一本被冠以国家“十五”规划重点研究项目的书,书里就是这么写的。现在看来,不知道又要有多少人的思想被这家伙“强jian”了。
而实际上,地址这个东西,本来就是一种基本数据类型,本应该在介绍整数、浮点、字符等基本类型的时候把地址显式地放在一起讨论,这样在后面介绍指针与数组的时候就能避免许多误解。可惜不少教材或者根本没有谈及,或者就算提起这个类型也用了指针类型这个字眼。这就错了,指针不是类型,真正的类型是地址,指针只是存储地址这种数据类型的变量。
打个比方,对于“int i=10;”而言,10是整数,而i是存储整数的变量,指针就好比这个i,地址就好比那个10。指针能够进行加减法,原因并不是因为它是指针,加减法则不是属于指针这种变量的,而是地址这种数据类型的本能,正是因为地址具有加减的能力,所以才使指针作为存放地址的变量能够进行加减运算。这跟整数变量因为整数能够进行加减乘除因而它也能进行加减乘除一个道理。
那么数组名又应该如何理解呢?用来存放数组的区域是一块在栈中静态分配的内存(非static),而数组名是这块内存的代表,它被定义为这块内存的首地址。这就说明了数组名是一个地址,而且,还是一个不可修改的常量,完整地说,就是一个地址常量。数组名跟枚举常量类似,都属于符号常量。数组名这个符号,就代表了那块内存的首地址。
注意了,不是数组名这个符号的值是那块内存的首地址,而是数组名这个符号本身就代表了首地址这个地址值,它就是这个地址,这就是数组名属于符号常量的意义所在。由于数组名是一种符号常量,因此它是一个右值,而指针,作为变量,却是一个左值,一个右值永远都不会是左值,那么,数组名永远都不会是指针。不管什么话,只要说数组名是一个指针的,都是错误的。就象把刚才int i=10例子中的10说成是整数变量一样,在最基本的立足点上就已经完错了。
总之要牢牢记住,数组名是一个地址,一个符号地址常量,不是一个变量,更不是一个作为变量的指针。
在数组名并非指针这个问题上,通常会产生两种疑问:
1、作为形参的数组,不是会被转换为指针吗?
2、如果形参是一个指针,数组名可以作为实参传递给那个指针,难道不是说明了数组名是一个指针吗?
首先,C语言之所以把作为形参的数组看作指针,并非因为数组名可以转换为指针,而是因为当初ANSI委员会制定标准的时候,从C程序的执行效率出发,不主张参数传递时复制整个数组,而是传递数组的首地址,由被调函数根据这个首地址处理数组中的内容。
那么谁能承担这种“转换”呢?这个主体必须具有地址数据类型,同时应该是一个变量,满足这两个条件的,非指针莫属了。要注意的是,这种“转换”只是一种逻辑看法上的转换,实际当中并没有发生这个过程,没有任何数组实体被转换为指针实体。另一方面,大家不要被“转换”这个字眼给蒙蔽了,转换并不意味着相同,实际上,正是因为不相同才会有转换,相同的话还转来干吗?这好比现在社会上有不少人“变性”,一个男人可以“转换”为一个女人,那是不是应该认为男人跟女人是相同的?这不是笑话么。
第二,函数参数传递的过程,本质上是一种赋值过程。C89对函数调用是这样规定的:函数调用由一个后缀表达式(称为函数标志符,function designator)后跟由圆括号括起来的赋值表达式列表组成,在调用函数之前,函数的每个实际参数将被复制,所有的实际参数严格地按值传递。因此,形参实际上所期望得到的东西,并不是实参本身,而是实参的值或者实参所代表的值。
举个例来说,对于一个函数声明:
void fun(int i);
我们可以用一个整数变量int n作实参来调用fun,就是fun(n);当然,也正如大家所熟悉的那样,可以用一个整数常量例如10来做实参,就是fun(10);那么,按照第二个疑问的看法,由于形参是一个整数变量,而10可以作为实参传递给i,岂不就说明10是一个整数变量吗?这显然是谬误。实际上,对于形参i来说,用来声明i的类型说明符int,所起的作用是用来说明需要传递给i一个整数,并非要求实参也是一个整数变量,i真正所期望的,只是一个整数,仅此而已,至于实参是什么,跟i没有任何关系,它才不管呢,只要能正确给i传递一个整数就OK了。当形参是指针的时候,所发生的事情跟这个是相同的。指针形参并没有要求实参也是一个指针,它需要的是一个地址,谁能给予它一个地址?显然指针、地址常量和符号地址常量都能满足这个要求,而数组名作为符号地址常量正是指针形参所需要的地址,这个过程就跟把一个整数赋值给一个整数变量一样简单。
最后,希望再版或新出的相关教材中,编者能严格地使用地址这一概念,该是地址时就用地址,该是指针时就用指针,以免象其它教材那样给读者一个错误的暗示。
- C语言指针和数组的本质区别
- C语言指针、数组指针和指针数组的区别
- C语言:数组和指针的区别
- C语言数组和指针的区别
- C语言:数组和指针的区别
- 数组和指针的本质区别
- 【C语言】指针和数组的区别和联系
- 精通C语言2-数组和指针的区别
- C语言中数组名和指针的区别
- C语言 字符指针和字符数组的区别
- c语言中数组和指针的区别与联系
- C语言指针字符串和数组字符串的一点区别
- C语言中数组名和指针的区别
- C语言中数组和指针的区别
- C语言字符指针和字符数组的区别
- c语言中字符数组和字符指针的区别
- C语言数组与指针的区别
- 黑马程序员——C语言之指针数组和数组指针的区别
- gpu 编程简介
- MFC 消息类型
- MFC程序得到本身路径
- SnakeLayout源码
- 把一个有序整数数组放到二叉树中
- C语言指针和数组的本质区别
- AS3事件机制概述
- 题目:一个数如果恰好等于它的因子之和,这个数就称为 "完数 "。例如6=1+2+3.编程 找出1000以内的所有完数。
- Linux内核的同步机制:completion
- sql 2000 server 重复数据查询
- java.lang.NoSuchMethodError- javax.servlet.JSP.PageContext.getELContext()LJAVAx-el-ELContext
- ORACLE 命令
- MATLAB实际单位时间计时函数
- 【原】使用批处理BAT文件处理Mysql数据库