




已阅读5页,还剩3页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
深入浅出C指针(一)基本概念 初学者在学习C语言时,通常会遇到两个瓶颈,一个是“递归”,一个是“指针”。大学老师在讲述这两个知识点时通常都是照本宣科,而没有站在一个初学者的角度来审视问题,更没有剖析其内部机理。本人在此将发表一系列技术文章,希望能将C语言中“指针”这一概念讲述清楚,希望初学者能从中收益。在此笔者也极力推荐Kenneth A.Reek写的Pointers On C这本书。1.内存和地址初学者面对内存一词时总是有一种既陌生又熟悉的感觉。首先,在日常生活中大家总是会讨论某某设备内存有多大,是不是该加一个内存条等等。但是,内存究竟是什么?在此,笔者并不想深入探讨内存的本质,以及内存在不同操作系统上的结构有什么区别等等这一系列问题。这里只希望初学者知道以下三个问题即可:(1)内存是计算机在运行过程中存储数据的地方(2)内存被分割成了“无数”个小区域,每个区域的大小在不同的环境下有所不同,可能是1个字节,2个字节,或者多个字节。(3)每个小区域都有一个独一无二的标识,即我们后面所说的地址(指针)。(4)每个小区中都有包含一个值,可以是整形,浮点型,字符型等等。我们可以形象的用下图表示内存的结构:100 104 108 112 116| | | | |-(542) (3.14) (A) (12323) (-12)如图所示,上方表示内存的地址,下方括号中的内容表示该地址下内存中的值。我们想访问这些内存中的值,只需要知道内存地址即可。但是,我们怎么知道我们要访问的内容其内存地址是多少?的确,要记住这些内存地址几乎是不可能的,所以,编译器可以让我们用变量的形式访问他们,于是这张图可以变成如下所示:total_count Pi m_ch money number| | | | |-(542) (3.14) (A) (12323) (-12)我们将内存地址变成了我们更容易记住的变量,这样我们在编写程序的时候就可以方便的访问内存中的数据。但是,请记住,这只是编译器帮助我们进行了优化,而真正编译后的机器码则是通过真正的内存地址来访问内存中的数据,即寻址,关于计算机的寻址过程,有兴趣的读者可以参考计算机组成原理或者汇编语言等书。2.值和类型我们首先来看一下下面这几个表达式:int total_count = 542;float Pi = 3.14;int *p1 = &total_count;char *p2 = &m_ch;前两个表达式很好理解,我们申明了一个整形和一个浮点型变量,并分别赋值为542和3.14。那后面两个表达式是什么意思呢?我们姑且可以简单记住:在申请变量的表达式中,如果类型的后面出现了*号,那么这个变量就叫做指针,或者指针变量。指针变量分为很多种类型,例如整形指针,浮点型指针,字符型指针等等。好了,我们知道了指针的概念,那么,指针到底是什么?指针也是变量,即指针变量,它和其他的变量在本质上是没有区别的。但是指针变量只能保存一种值,就是地址。也许你会问了,既然指针保存的是地址,那么为什么要将指针分为那么多的种类,整形值要有整形指针,字符型值要有字符型指针?为什么不能用一种指针就把所有的地址都包含了呢?难道不同类型变量的地址也不同吗?这个问题,本人没有查阅过官方的解释,首先可以肯定,内存地址在理论上是没有任何区别的,无论是用来保存什么类型变量的内存,其本质都是01Bit构成的区域,内存地址当然也不会有任何区别,从纯技术技术的角度上讲,笔者认为编译器完全可以建立一种制度,用统一的指针类型保存不同变量的地址,这完全不会影响程序的运行。但是编译器没有这样做的原因,笔者分析主要是出于安全性的考虑。当程序员有意或无意的将两种不同类型的指针所指向的内存内容进行赋值时,如果编译器事先不能做出检查,那么也许会在程序运行过程中出现异常,例如产生非常严重的缓冲区溢出错误。3.指针的间接访问符好了,我们将问题回到这几个表达式上来。现在的问题是p1和p2这两个变量中保存的是什么。拿p1为例,也许你可以这样理解,因为p1是指针变量,所以他的值应该和他指向的内存中的值一样,所以p1 = 542. 这样理解看似非常符合逻辑,但却是一个大的错误。虽然p1很特殊,但是指针变量也是变量,它不会聪明到自动去完成一个非常复杂的自动间接访问操作。p1的值实际是100,即变量total_count的地址。但是读者请注意,100并不是传统意义上整形100,例如 int *p1 = 100;这是一个错误的表达式,因为不能直接将整形值赋值给指针变量,作如下变化即可 int *p1 = (int *)100; 也就是说,如果我们输出p1的值,打印出来将会是一串莫名其妙的数字。当然,我们通常对这些莫名其妙的地址不感兴趣,我们更感兴趣的是这些地址背后影藏这怎样的信息。所以我们可以这样来访问int count = *p1;等等.读者读到这里也许有些糊涂了,int *p1 = &total_count; int count = *p1; 这么多的*号和&号也许读者有些搞不清了。我们来理清一下思路。在int *p1 = &total_count;中:*p1只是一个标识,代表p1是一个指针,以后访问这个指针时直接p1即可,访问得到的值是一个地址编码。&total_count代表一个地址,在任意一个变量前(包括指针)加上&符号,都将代表这个变量所在内存的地址。&p1则代表指针所在内存的地址,即指针的指针(稍后详细介绍)其实这么有什么可疑问的,因为指针本身也是变量,所以和其他变量本质上没有任何区别。在 int count = *p1;中,p1和前面说的一样,访问它的值是一个地址,而*号与之前的*号有些不同,之前的*号只是一个标识,表示当前申请的变量是一个指针,而这里的*号我们给它一个新的名字,叫指针的间接访问符。听起来有些别扭,简单地理解就是,在一个指针变量前加上*号则可以访问该指针所所表示内存的实际值。这里可以教初学者一个小技巧,当利理解一个和指针有关的语句是,“指针”和“地址”这两个词可以互换,初学者姑且可以认为“指针”就是“地址”,“地址”就是“指针”,这样理解不确切,但却很实用。好了,下面让我们看一个更复杂的例子:int *p1 = &total_count;int count2 = *(*(&p1);请读者在10秒钟之内告诉我count2的值是多少。哈哈,好吧,让我们来一点点分析。从括号最里面开始:p1是一个指针,他的值是total_count的地址;&p1是一个地址,即指针p1的地址,我们用刚才的小技巧来看看,即地址p1地址,即指针p1的指针。*(&p1)是一个值,这个值是&p1表示的地址所在内存的值实际上就是p1的值,&对p1进行了一次引用操作,*对其再解引用,实际上没有变化。此时*(&p1)的值为total_count的地址。*(*(&p1)为total_count的值,也就是542.到此,该表达式的分析结束,不过在实际编程中,没有人会用如此复杂的表达式进行编码。4.指针的指针指针的指针,即地址的地址。第一个地址是狭义上的地址,该地址实际上已经被“值”化,第二个地址是我们传统意义上的地址。这句话理解起来有些困难,我们先来看看下面的表达式;int *p1 = &total_count;int *p2 = &p1;第一个表达式的意义我们已经清楚了,我们申请了一个指针变量,或者说是地址变量,它保存的是total_count的地址。第二个表达式我们来一步步分析,p1是一个指针,即一个地址,&为地址符,加起来就是地址的地址,或者说指针的指针。而int *p2 我们可以这样理解 int *X,我们定义了一个指针变量X ,X为*p2,即X也是一个指针变量,加起来的意思就是我们申请了一个p2指针变量,这个指针变量指向的也是一个指针变量。同理: int *p3 = &p2;int *p4 = &p3;.都是成立的表达式,不过在实际编码中,遇到的最多情况是指针和指针的指针这两种情况。深入浅出C指针(二)一维数组 1. 数组名我们看一下下面两个声明:int a;int b10;我们把变量a称为标量,因为它是个单一的值。我们把变量b称为数组,因为它是一些值的集合。b0表示数组中的第一个数,b1表示数组中的第二个数,以此类推。我们知道b0的类型是整形,那么,b的类型是什么呢?它所表示的是什么呢?一个合乎逻辑的答案似乎是它表示整个数组,但事实并非如此。在C语言中,数组名永远代表一个指针常量,也就是数组第一个元素的地址,即 &b0。它的类型取决于数组的类型。注意,这里说的是“指针常量”,而不是上一篇文章说的“指针变量”。指针常量即不能改变指针所指向的值。即这样的代码是错误的:int number = 0;int a10 = 0;a = &number;a虽然是一个整形指针,但是他是一个指针常量,不能将其值改变。请不要根据数组名是指针这个事实得出指针和数组是相同的结论。数组具有一些和指针完全不同的特性。例如,数组具有确定的数量值,而指针只是一个标量值。2.下标引用在前面的声明的上下文环境中,下面这个表达式是什么意思呢?*(b + 3);首先,b的值是一个指向整形的指针常量。所以3这个值根据整形值得长度进行调整。加法运算的结果是另一个指向整形的指针,即 &b3。再通过*号对其解引用操作,相当于*(&b3)或者直接表示为b3.我们姑且可以这样认为:除了优先级顺序不同外,下标引用和指针间接访问完全相同。例如,下面这两个表达式是等同的:arraysubsricpt*(array + (subsript) )让我们看一个例子,巩固复习一下刚才的知识int array10;int *ap = array + 2;在下面有关ap的表达式中,看看你能不能写出对应的array表达式:ap ,这个很容易,&array2或者*(array + 2)*ap ,这个也很容易,相当于对ap进行解引用操作,相当于array2或者*(array+2)ap0,将其转化成为*(ap + 0),即*ap,和上一个表达式等价。ap+6,这个表达式相当于&array2+6或者*(array+2+6),即&array8或者*(array+8)*ap+6,小心,这里有两个操作符,不过间接访问的优先级高于加法操作,所以该式等价于array2+6ap6,你也许会疑问?这是错的吗?的确,在别的语言里他也许是错的,但是在C语言里他是正确的,我们说过,C语言中的下表操作与指针间接访问操作完全相同,即*(ap+6)& ap,这个表达式是完全合法的,它表示ap指针的地址,即指针的指针。好了如果上面的问题都难不倒你的话,拿来看看下面这个:2ap是的,你没有看错,2ap,不是ap2.他的答案也许会令你大吃一惊:它是合法的。把它转化成为对等的间接访问表达式,你就会发现它的有效性:*(2 + (array))内层的括号是多余的,即他和*(array + 2)是等价的。3.关于指针和数组的效率在此笔者不想通过反汇编来实验数组与指针的效率,读者暂时可以这样记住:“假定这两种方法都是正确的,下标绝不会比指针更有效率,但是指针有时会比下标更有效率”深入浅出C指针(三)多维数组 1.概念如果某个数组的维数不止1个,它就被称为多维数组。例如,下面这个声明:int matrix610;创建了一个包含60个元素的矩阵。但是,它是6行每行10个元素,还是10行每行6个元素?为了回答这个问题,你需要从一个不同的观点视察多维数组。考虑下列这些维数不断增加的声明:int a;int b10;int c610;int d3610;a和b很好理解。c只是在b的基础上在增加一维,所以我们把c看作是一个包含6个元素的向量,只不过他的每个元素本身又是一个包含10个元素的向量。换句话说,c是一个一维数组的一维数组。d也是如此:它本身是一个含有3个元素的向量,而这三个元素中每个元素本身又是一个含有6个元素的向量,而这6个元素各自本身又是一个含有10个元素的向量。 2.存储顺序请记住,多维数组只是表示上的一种写法,而在实际的内存空间存储中,还是按照顺序的存储方式。例如:int25;在内存中是按顺序排列的10个小区域。看看下面这个例子:int matrix610;int *mp;.mp = &matrix38printf(First value is %dn,*mp);printf(Second value is %dn,*+mp);printf(Third value is %dn,*+mp);很显然,第一个被打印的值将是matrix38的内容,那下一个被打印的又是什么?顺序存储可以回答这个问题-下一个元素将是最右边下标首先变化的那个,也就是matrix39。再接下去又轮到谁呢?第9列已是最后一列。不过,按顺序存储的规定,一行存满后就轮到下一行,所以下一个被打印的元素将是matrix40。2.数组名一维数组的数组名是一个指针常量,这一点大家应该已经十分清楚了。它的类型是“指向元素类型的指针”,它指向数组元素的第一个元素。多维数组也差不多,但是这个“差不多”往往会给初学者带来一定的误导,多维数组的数组名,也是一个指针,这个指针指向的是该数组的第一个“元素”,这个“元素”也是一个数组。例如,下面这个声明:int matrix310;接着看以下的表达式:matrix15这个很简单,该表达式表示这个数组的第二行,第六列所包含的元素。matrix + 1如果你认为这个表达式指向了第一行的第二个元素,那么你就错了。实际上,这个表达式指向的是第二行整个数组,而不是任何一个元素。而*(matrix + 1)是一个指向第二行第一个元素的指针。*(*(matrix + 1))表示了第二行第一个元素的具体值。其实就笔者感觉,C语言多维数组这样的设计方式不是十分恰当,这样做和一维数组的规则没有十分紧密的联系起来,就易于理解而言,matrix + 1表示第一行的第二个元素更为容易理解一些。不过可能是出于编码的简洁性考虑,C语言的发明人采用了这样“自动换行”的处理过程。小提示:在许多其他的语言,比如大家熟悉的C#中,多重下标被写成用逗号分割的形式。比如:matrix3,4;如果你在C语言中这样写会发生什么呢?你的第一感觉一定是:编译错误。不过很可惜,C语言可以让这样的表达式编译通过(还记得2array吗?C语言的灵活性是其他语言所不能比拟的)。这样看上去没有什么问题,实际这样的表达式运行结果一定会和你预想的结果不一样。C语言的逗号表达式会将其转换成matrix3。如果编译器
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 河南省驻马店市正阳县2022-2023学年五年级下学期期末测试科学试卷(含答案)
- 2025企业长期不与员工签订合同该如何解决
- 2025南沙中心市场租赁合同
- 江苏省苏州市张家港市梁丰初级中学2026届中考英语最后一模试卷含答案
- 2025年政府与企业合作项目合同特性解析
- 2025上海市租房协议下载
- 道交宣传课件
- 2025建筑工程雇佣合同
- 2025版人才发展合同(标准文本)
- 畜禽寄生虫病防治课件
- 新《医用X射线诊断与介入放射学》考试复习题库(含答案)
- 云仓课件教学课件
- Python快速编程入门(第3版) 课件 第8章 面向对象
- ISO9001-2015质量管理体系内审培训课件
- 统编版语文二年级下册-25黄帝的传说-教学课件多篇
- 盾构始发正式安全交底
- DL∕T 1901-2018 水电站大坝运行安全应急预案编制导则
- 起重机行业市场分析报告2024年
- 北京联合大学微观经济学期末试卷
- 培训师破冰小游戏含内容
- 智研咨询发布:锦纶纤维行业市场动态分析、发展方向及投资前景分析报告
评论
0/150
提交评论