这台仪器的动态鼠标指针主题包会动。句中会字应做何解释????

 您现在的位置:&&>>&&>>&&>>&测控仪器设计教案正文
测控仪器设计教案
测控仪器设计教案
  []课件下载网测控03《测控仪器设计》第一章绪论1.1、仪器发展过程仪器:是对物质世界信息进行与控制的设备,[|]。1、按所采用的电子器件:真空管-→晶体管-→集成电(三个时代)2、按组成结构、原理和功能特点:模拟式-→数字式-→能化(三个发展阶段)第一代:模拟式仪器。如指针式的电压表、电流表、功率表。机械式特点:功能简单、精度低、响应速度慢。第二代:数字式仪器。如数字万用表、数字频率计。数电基本特点:是将待测的模拟信号转换成数字信号进行测量,测量结果以数字形式输出显示并向外传送。精度高,响应速度快,读数清晰、直观。第三代:智能仪器。:是计算机技术与测量仪器相结合的产物,是含有微型计算机或微处理器的测量仪器,由于它拥有对数据的存储、运算、逻辑判断及自动化操作等功能,具有一定智能的作用(为智能的延伸或加强等),因而被称之为智能仪器。(1)是在数字化的基础上发展起来的,是计算机技术与测量仪器相结合的产物。微处理器在智能仪器中的作用主要体现在以下两方面:①对测试过程的控制:接受来自键盘或通信接口的命令,解释并这些命令,控制各部分的工作过程,同时对工作状态进行监测。②对测试数据的处理:表现为硬件电路只需具备最基本的测试能力,能向微型计算机提供原始数据即可。对数据的进一步处理如滤波、运算等可由软件完成。(2)智能仪器基本技术指标测量的准确度和可靠性是智能仪器的两项基本技术指标。对仪器的误差进行校准可以保证仪器具有规定睥准确度。而对仪器的故障进行检测和诊断则可及进发现、排除故障,使仪器可靠的工作。保证仪器准确度(精度):温度补偿、非线性校正、滤波;保证仪器可靠性措施:冗余设计、故障诊断。故障诊断:给被测对象施加一定的检测信号,根据其输出响应信号来判断是否存在故障。由于具有数字存储、运算、逻辑判断能力,可根据被测参数的自动量程,具有自动校正、自动补偿、自寻故障等功能,可以完成需要人类的智慧才能胜任的工作,即具备了一定的&智能&,故称之为智能仪器。智能仪器的出现,极大地扩充了传统仪器的应用范围。智能仪器凭借其体积小、功能强、功耗低等优势,迅速地在家用电器、科研和工业企业中得到了广泛的应用。3、智能仪器的硬件发展(1)单片机以8位的8031等51系列单片机为仪器硬件主流。(2)DSP芯片(数字信号处理)利用硬件处理一些运算的芯片:AMD29500 16位双极型DSP完成1024点FFT运算仅需2ms。(3)ASIC芯片(专用集成电路)1.2智能仪器的特点与传统仪器仪表相比,智能仪器具有以下特点:一、测量过程的软件控制在测量软件出现以前,是采用数字电路的时序控制。由于硬件的体积和成本的原因,现已采用CPU控制下的软件指令替代硬件时序,使仪器测量变得灵活方便,误差补偿、诊断等。二、数据处理对测量数据进行存储和运算。它主要表现为:1,改善测量的精确度;2,对测量结果进行再加工。测量精确度:对随机误差(软件滤波:限幅、中值、平均值)和系统误差(温度补偿、非线性补偿、零点补偿、增益补偿)进行处理。测量结果:时域的波形分析、频域分析等。使智能仪器提供更多高质量的信息量。三、多功能化智能仪器的测量过程、软件控制及数据处理功能使一机多用的多功能化易于实现。例如:可以显示实时数值、历史趋势、存储打印、报警联网等。1.3智能仪器的基本结构两种结构:微机内置式、微机扩展式1、微机内置式(嵌入式)智能仪器微机内置式:主要是单片机加调理电路2、微机扩展式智能仪器微机扩展式为计算机为核心加上测量板卡(内置)或插件箱(外置),《》()。1.4虚拟仪器测量仪器的主要功能都是由数据采集、数据分析和数据显示等三大部分组成的。在虚拟现实系统中,数据分析和显示完全用PC机的软件来完成。因此,只要额外提供一定的数据采集硬件,就可以与PC机组成测量仪器。这种基于PC机的测量仪器称为虚拟仪器。虚拟仪器=传统测量板卡+PC+LV软件。在虚拟仪器中,使用同一个硬件系统,只要应用不同的软件编程,就可得到功能完全不同的测量仪器。可见,软件系统是虚拟仪器的核心,&软件就是仪器&。因此,有人提出未来仪器的一种可能模式。(参阅教材P8图1-4)图1虚拟仪器平台的硬件组成图LABVIEW7.0 U12采集器AD-JS20测控箱电脑传统的智能仪器主要在仪器技术中用了某种计算机技术,而虚拟仪器则强调在通用的计算机技术中吸收仪器技术。作为虚拟仪器核心的软件系统具有通用性、通俗性、可视性、可扩展性和升级性,能为用户带来极大的利益,因此,具有传统的智能仪器所无法比拟的应用前景和。虚拟仪器技术的优势在于可由用户定义自己的专用仪器系统,且功能灵活,很容易构建,所以应用面极为广泛。尤其在科研、开发、测量、检测、计量、测控等领域更是不可多得的好工具。虚拟仪器技术,十分符合国际上流行的&硬件软件化&的发展趋势,因而常被称作&软件仪器&。它功能强大,可实现示波器、逻辑分析仪、频谱仪、信号发生器等多种普通仪器全部功能,配以专用探头和软件还可检测特定系统的参数,如汽车发动机参数、汽油标号、炉窑温度、血液脉搏波、心电参数等多种数据;它操作灵活,完全图形化界面,风格简约,符合传统设备的使用,用户不经即可迅速掌握操作规程;它集成方便,不但可以和高速数据采集设备构成自动测量系统,而且可以和控制设备构成自动控制系统。在仪器计量系统方面,示波器、频谱仪、信号发生器、逻辑分析仪、电压电流表是科研机关、企业研发实验室、大专院所的必备测量设备。随着计算机技术在测绘系统的广泛应用,传统的仪器设备缺乏相应的计算机接口,因而配合数据采集及数据处理十分困难。而且,传统仪器体积相对庞大,多种数据测量时常常感到捉襟见肘,手足无措。我们常见到硬件工程师的工作台上堆砌着纷乱的仪器,交错的线缆和繁多待测器件。然而在集成的虚拟测量系统中,我们见到的是整洁的桌面,条理的操作,不但使测量人员从繁复的仪器堆中解放出来,而且还可实现自动测量、自动记录、自动数据处理。其方便之极固不必多言,而设备成本的大幅降低却不可不提。一套完整的实验测量设备少则几万元,多则几十万元。在同等的性能条件下,相应的虚拟仪器价格要低二分之一甚至更多。虚拟仪器强大的功能和价格优势,使得它在仪器计量领域具有很强的力和十分广阔的前景。图1.虚拟示波器在专用测量系统方面,虚拟仪器的发展空间更为广阔。环顾当今,信息技术的迅猛发展,各行各业无不转向智能化、自动化、集成化。无所不在的计算机应用为虚拟仪器的推广提供了良好的基础。虚拟仪器的概念就是用专用的软硬件配合计算机实现专有设备的功能,并使其自动化、智能化。因此,虚拟仪器适合于一切需要计算机辅助进行数据存储、数据处理、数据传输的计量场合。测量与处理、结果与分析相脱节的面貌将大为改观。数据的拾取、存储、处理、分析一条龙操作,既有条不紊又迅捷快速。推而广之,一切计量系统,只要技术上可行,都可用虚拟仪器代替,由此可见虚拟仪器应用空间是多么的宽广。在自动控制和工业控制领域,虚拟仪器同样应用广泛。决大部分闭环控制系统要求精确的采样,及时的数据处理和快速的数据传输。虚拟仪器系统恰恰符合上述特点,十分适合测控一体化的设计。尤其在制造业,虚拟仪器的卓越计算能力和巨大数据吞吐能力必将使其在温控系统、在线监测系统、电力仪表系统、流程控制系统等工控领域发挥更大的作用。图2.模糊PID温度控制器1.5结束语智能仪器是计算机科学、电子学、数字信号处理、人工智能、VLSI等新兴技术与传统的仪器仪表技术的结合。随着专用集成电路、个人仪器等相关技术的发展,智能仪器将会得到更加广泛的应用。作为智能仪器核心部件的单片计算机技术是推动智能仪器向小型化、多功能化、更加灵活的方向发展的动力。可以预料,各种功能的智能仪器在不远的将来会广泛地使用在社会的各个领域。本课程学习中需要注意的几个问题:1、智能仪器是多种技术的结合,虽然智能仪器与微处理器是紧密相连,但决不能将智能仪器简单地与单片机等同。2、如果对数据处理的实时性要求不是很高,对于软件的开发完全可由C语言进行,不但可以避免复杂的汇编语言编程,维护难的特点,而且可以大大提高开发进程。3、智能仪器主要从两个方面学习,一是由单片机硬件为主的智能仪器;二是以LV软件为主的虚拟仪器。思考题:1、什么是智能仪器?2、仪器的基本技术指标?如何保证?3、故障诊断方法?4、智能仪器特点?5、智能仪器有哪两种结构?6、什么是虚拟仪器?第2章概述一、目标及基本要求教学目标:使学生掌握计算机工业控制的组成和本质,并初步掌握三种计算机控制系统,即DCS、FCS、PLC控制系统。基本要求:学生经过学习,应掌握计算机工业控制的组成,并熟悉DCS、FCS、PLC这三种控制系统。二、教学内容及学时分配教学内容学时分配第1节计算机工业控制的概念1第2节计算机工来控制系统的分类1计算机工业控制的概念:图1-1计算机工业控制系统随着计算机技术的普及和发展,越来越多的领域用上了电脑,而电脑软、硬件技术和可靠性的提高,使得计算机用于工业控制日趋成熟。工业计算机由原来的开关量为主的PLC控制,到以模拟量控制为主的DCS控制,以及将来以总线、网络为主的FCS控制,也只不过了暂短的十几年。1
    〖预览〗
阳泉七中2010年电教仪器工作计划
根据区教育局电教仪器站2010年工作计划精神,特制订我校2010年电教仪器工作计划,内容如下。
一、重点工作目标
1、加强“两室”建设,进一步提升“两室”标准化水平。
按照区教育局要求“加强管理、促进应用,完善档案建设,努力实现三个目标:实验开出率95%,文体器材使用率100%,图书借阅率5册/生?学期以上。”
--加强实验室建设,充分利用与维护好现有资源。
--积极推进图书馆自动化建设,提升图书管理水平和图情服务能力。
2、加强教育网络应用与服务,深入开展“教育信息化课堂教学应用”活动。加强领导,精心组织,相互协作,有效实……【】    〖预览〗
实验教学是中小学素质教育的重要组成部分,是培养学生综合能力的重要手段,特别是对学生的动手能力,实际操作能力的培养,更有其不可替代的作用。为加强对实验教学与仪器管理工作的管理,更好地为教学提供服务,特制定本计划。
一、指导思想:
认真贯彻执行党的教育方针,紧密联系本校实际情况,为进一步提高小学实验的管理水平和能力,以及实验室材料实现科学化、分类、分档、档案管理,加强实验水平和实验效果,更好,更全面地实施素质教育,最大限度地为教育教学服务,推动学校实验工作进一步发展,大力开展课外科技活动,为培养实践创新性人才作贡献。
二& 主要任务、目标
1、按国家教委颁布的课程标准开齐开足实验教学课程,实验开出率达到90%……【】
&&&&推荐专题:
  〔测控仪器设计教案〕
  测控仪器设计教案所属栏目:〖〗〖〗
  “测控仪器设计教案”相关:
  〖〗链接地址:
  教案提供的测控仪器设计教案由网友原创或转发,若测控仪器设计教案侵犯了您的权益,请与本站联系,谢谢!
https://www.unjs.com/
上一篇范文: 下一篇范文:
测控仪器设计教案相关范文正在初始化报价器电子仪器的使用方法谁会水_0688测量仪器有:水准仪,经纬仪,全站仪和GPS等。全站和GPS是电子的,水准和经纬也有电子的。所以说,就这几种仪器来看,都是有电子的。电子水准仪和经纬仪的使用更一般仪器是一样的,只是加上了电子计算系统,用于记录和一些限差的设定,还有些是加了电子测距功能。
一、频谱分析仪的使用
频谱分析仪在频域信号分析、测试、研究、维修中有着广泛的应用。它能同时测量信号的幅度及频率,测试比较多路的信号及分析信号的组成,还可测试手机逻辑和射频电路的信号。例如:逻辑电路的控制信号、基带信号,射频电路的本振信号、中频信号、发射信号等。
更多回答疯子嘿抠3不同电子测量仪器的使用方法:
一、频谱分析仪的使用
频谱分析仪在频域信号分析、测试、研究、维修中有着广泛的应用。它能同时测量信号的幅度及频率,测试比较多路的信号及分析信号的组成,还可测试手机逻辑和射频电路的信号。例如:逻辑电路的控制信号、基带信号,射频电路的本振信号、中频信号、发射信号等。
二、LCR参数测试仪的使用
电感、电容、电阻参数测量仪,不仅能自动判断元件性质,而且能将符号图形显示出来,并显示出其值。还能测量Q、D、Z、Lp、Ls、Cp、Cs、Kp、Ks等参数,且显示出等效电路图形。
三、集成电路测试仪的使用
集成电路测试仪可对TI1、PM0S、CM0S数字集成电路功能和参数进行测试,还可判断抹去字的芯片型号及对集成电路在线功能测试、在线状态测试。
四、红外测试仪的使用
红外测试仪是一种非接触式测温仪器,它包括光学系统、电子线路,在将信息进行调制、线性化处理后达到指示、显示及控制的目的。目前已应用的红外测温仪有光子测温和热测温仪两种,主要用于电热炉、农作物、铁路钢轨、深埋地下超高压电缆接头、消防、气体分析、激光接收等温度测量及控制场合。
五、兆欧表的使用
兆欧表(俗称摇表)是一种检查电气设备、测量高电阻的简便直读式仪表,通常用来测量电路、电机绕组、电缆等绝缘电阻。兆欧表大多采用手摇发电机供电,故称摇表。由于它的刻度是以兆欧(MΩ)为单位,因此称兆欧表。
六、信号发生器的使用
信号发生器(包括函数发生器)为检修、调试电子设备和仪器时提供信号源。它是一种能够产生一定波形、频率和幅度的振荡器。例如:产生正弦波、方波、三角波、斜波和矩形脉冲波等。
七、示波器的使用
示波器是一种测量电压波形的电子仪器,它可以把被测电压信号随时间变化的规律,用图形显示出来。使用示波器不仅可以直观而形象地观察被测物理量的变化全貌,而且可以通过它显示的波形,测量电压和电流,进行频率和相位的比较,以及描绘特性曲线等。
八、多用电表的使用
模拟式电压表、模拟多用表(即指针式万用表VOM)、数字电压表、数字多用表(即数字万用表DMM)都属此类。这是经常使用的仪表。它可以用来测量交流/直流电压、交流/直流电流、电阻阻。
希望有所帮助。
Kyoya69VE7常见电子仪器使用方法
北欧表使用方法:
1、兆欧表必须水平放置于平稳牢固的地方,以免在摇动时因抖动和倾斜产生测量误差。
2、接线必须正确无误,兆欧表有三个接线桩,“E”(接地)、“L”(线路)和“G”(保护环或叫屏蔽端子)。保护环的作用是消除表壳表面“L”与“E”接线桩间的漏电和被测绝缘物表面漏电的影响。在测量电气设备对地绝缘电阻时,“L”用单根导线接设备的待测部位,“E”用单根导线接设备外壳;如测电气设备内两绕组之间的绝缘电阻时,将“L”和“E”分别接两绕组的接线端;当测量电缆的绝缘电阻时,为消除因表面漏电产生的误差,“L”接线芯,“E”接外壳,“G”接线芯与外壳之间的绝缘层。
“L”、“E”、“G”与被测物的连接线必须用单根线,绝缘良好,不得绞合,表面不得与被测物体接触。
3、摇动手柄的转速要均匀,一般规定为120 转/分钟,允许有±20%的变化,最多不应超过±25%。通常都要摇动一分钟后,待指针稳定下来再读数。如被测电路中有电容时,先持续摇动一段时间,让兆欧表对电容充电,指针稳定后再读数,测完后先拆去接线,再停止摇动。若测量中发现指针指零,应立即停止摇动手柄。
4、测量完毕,应对设备充分放电,否则容易引起触电事故。
5、禁止在雷电时或附近有高压导体的设备上测量绝缘电阻。只有在设备不带电又不可能受其他电源感应而带电的情况下才可测量。
6、兆欧表未停止转动以前,切勿用手去触及设备的测量部分或兆欧表接线桩。拆线时也不可直接去触及引线的裸露部分。
7、兆欧表应定期校验。校验方法是直接测量有确定值的标准电阻,检查其测量误差是否在允许范围以内。
兆欧表的使用方法
(一)使用前的准备工作
1、检查兆欧表是否能正常工作将兆欧表水平放置,空摇兆欧表手柄,指针应该指到。o处,再慢慢摇动手柄,使L和E两接线桩输出线瞬时短接,指针应迅速指零。注意在摇动手柄时不得让L和E短接时间过长,否则将损坏兆欧表。,
2、检查被测电气设备和电路,看是否已全部切断电源。绝对不允许设备和线路带电时用兆欧表去测量。
3、测量前,应对设备和线路先行放电,以免设备或线路的电容放电危及人身安全和损坏兆欧表,这样还可以减少测量误差,同时注意将被测试点擦拭干净。
(二)正确使用
1、兆欧表必须水平放置于平稳牢固的地方,以免在摇动时因抖动和倾斜产生测量误差。
2、接线必须正确无误,兆欧表有三个接线桩,“E”(接地)、“L”(线路)和“G”(保护环或叫屏蔽端子)。保护环的作用是消除表壳表面“L”与“E”接线桩间的漏电和被测绝缘物表面漏电的影响。在测量电气设备对地绝缘电阻时,“L”用单根导线接设备的待测部位,“E”用单根导线接设备外壳;如测电气设备内两绕组之间的绝缘电阻时,将“L”和“E”分别接两绕组的接线端;当测量电缆的绝缘电阻时,为消除因表面漏电产生的误差,“L”接线芯,“E”接外壳,“G”接线芯与外壳之间的绝缘层。
“L”、“E”、“G”与被测物的连接线必须用单根线,绝缘良好,不得绞合,表面不得与被测物体接触。
3、摇动手柄的转速要均匀,一般规定为120 转/分钟,允许有±20%的变化,最多不应超过±25%。通常都要摇动一分钟后,待指针稳定下来再读数。如被测电路中有电容时,先持续摇动一段时间,让兆欧表对电容充电,指针稳定后再读数,测完后先拆去接线,再停止摇动。若测量中发现指针指零,应立即停止摇动手柄。
4、测量完毕,应对设备充分放电,否则容易引起触电事故。
5、禁止在雷电时或附近有高压导体的设备上测量绝缘电阻。只有在设备不带电又不可能受其他电源感应而带电的情况下才可测量。
6、兆欧表未停止转动以前,切勿用手去触及设备的测量部分或兆欧表接线桩。拆线时也不可直接去触及引线的裸露部分。
7、兆欧表应定期校验。校验方法是直接测量有确定值的标准电阻,检查其测量误差是否在允许范围以内。
万用表的使用方法:
(1)熟悉表盘上各符号的意义及各个旋钮和选择开关的主要作用。
(2)进行机械调零。
(3)根据被测量的种类及大小,选择转换开关的挡位及量程,找出对应的刻度线。
(4)选择表笔插孔的位置。
(5)测量电压:测量电压(或电流)时要选择好量程,如果用小量程去测量大电压,则会有烧表的危险;如果用大量程去测量小电压,那么指针偏转太小,无法读数。量程的选择应尽量使指针偏转到满刻度的2/3左右。如果事先不清楚被测电压的大小时,应先选择最高量程挡,然后逐渐减小到合适的量程。 a交流电压的测量:将万用表的一个转换开关置于交、直流电压挡,另一个转换开关置于交流电压的合适量程上,万用表两表笔和被测电路或负载并联即可。 b直流电压的测量:将万用表的一个转换开关置于交、直流电压挡,另一个转换开关置于直流电压的合适量程上,且“+”表笔(红表笔)接到高电位处,“-”表笔(黑表笔)接到低电位处,即让电流从“+”表笔流入,从“-”表笔流出。若表笔接反,表头指针会反方向偏转,容易撞弯指针。
实际值=指示值×量程/满偏
(7)测电阻:用万用表测量电阻时,应按下列方法操作:
a选择合适的倍率挡。万用表欧姆挡的刻度线是不均匀的,所以倍率挡的选择应使指针停留在刻度线较稀的部分为宜,且指针越接近刻度尺的中间,读数越准确。一般情况下,应使指针指在刻度尺的1/3~2/3间。 b欧姆调零。测量电阻之前,应将2个表笔短接,同时调节“欧姆(电气)调零旋钮”,使指针刚好指在欧姆刻度线右边的零位。如果指针不能调到零位,说明电池电压不足或仪表内部有问题。并且每换一次倍率挡,都要再次进行欧姆调零,以保证测量准确。 c读数:表头的读数乘以倍率,就是所测电阻的电阻值。
热门问答123456789101112131415161718192021222324252627282930豆丁微信公众号
君,已阅读到文档的结尾了呢~~
指针式仪表自动读数的研究及应用,指针式电动功率仪表,指针式万用表,指针式电流表,指针式石英电子手表,指针式温湿度计,指针式电压表,指针式推拉力计,指针式喷灌机,指针式频率表
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
指针式仪表自动读数的研究及应用
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='http://www.docin.com/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口Delphi的指针(有图,很清楚)
时间: 10:42:15
&&&& 阅读:30
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&&Pointers are like jumps, leading wildly from one part of the data structure to another. Their introduction into high-level languages has been a step backwards from which we may never recover.&& Anthony Hoare
对指针可能有最让人误解和惧怕的数据类型,因此很多程序员喜欢躲避他们.
但是指针很重要.即使不显式支持指针或使其很难应用的语言,在其背后指针也起到非常重要的作用.因此理解指针是很重要的.有几个不同的途径理解指针.
本文针对理解指针和使用指针有问题的读者.在Win32的Delphi环境下进行讨论.可能不会涉及到所有方面(例如,一个应用程序的内存不是一个大的连续区域),但出于实用目的是有所帮助的.我认为指针是很容易理解的.
You probably already know what I write in this paragraph, but it is probably good to read it anyway, since it shows my view on things, which may differ a bit from your own.
指针是一个指向其他变量的变量.为解释他们,有必要先理解内存地址和变量的概念.首先先简要的解释一下计算机内存.
计算机内存简化的可以看做很长的字节行.字节是最小存储单元,包括256种不同的值(0到255).在当前32为Delphi中,内存可以看做是最大有2G字节长度的数组.字节中存储的内容与内容的解读方式有关,例如,使用方式.值97可以看做是一个byte类型的97,可以看做字符a.如果包含多个字节,可以存储更多的值.两个字节可以表示256*256中不同的值.
内存中的字节可以按编号进行访问,从0开始直到(假如有2G字节,即使你不知道这些,Windows也会试图虚拟这种效果).在这个巨大的数组中字节索引叫做地址.
也可以说:字节是内存中最小的可用地址表示的块.
事实上,内存非常复杂.有些计算机的字节不是8位,其能表示或大于或小于256种值,但Win32的Delphi不会遇到这样的计算机.内存由软件和硬件共同管理,全部的内存并非真正存在(内存管理程序处理同样与硬盘交换空间来解决这些问题),但本文中,将内存看做是字节组成的大块并在多个程序间共用有助于理解问题.
变量是巨大数组中的一个或多个字节组成的存储单元,可供程序读写.有名字,类型,值及其地址进行标示.
如果声明了一个变量,编辑器将保留一块适当大小的内存区域.变量存储的具体地址由编译器及运行时代码决定.不能对变量具体存放地址做任何假设.
变量类型定义了如何使用内存存储单元.例如其定义了size&(尺寸)决定占用多少字节,以及其structure(结构).例如,下图是一个内存片段.显示了起始地址在$个字节.字节值分别为$4D, $65, $6D和$00.
注意上图虽然使用了$作为起始地址,但这是杜撰的,只是为了区别其他内存位置.并不是真实反映的内存地址,其依赖于很多事情,不可预测.
数据类型决定了如何使用这些字节.例如Integer类型其值为7169357 ($006D654D),或一个array[0..3] of Char类型,表示C风格的字符串‘Mem‘,或其他内容,如集合变量,几个单字节变量,一个小结构体,&Single或Double类型的一部分等等.换句话说,在不知道存储的变量类型前,内存中存储的值的意义是无法推测的.
变量的地址是其第一个字节的地址.上图中,假设是一个Integer,其地址为 $.
未初始化变量
内存对于变量来说是可以重用的.通常内存为变量预留的时间与变量的生命周期一样长.例如,函数或过程(两者总称例程)中的局部变量仅在例程运行期间可用.对象的域(也是一个变量)在对象存在期间可用.
如果声明一个变量,编译器预留出变量需要的字节数.但其中的内容是以前函数或过程使用的时候在字节中存放的.换句话说,未初始化的变量值是未定义的(但不是未定的).例如在如下简单的控制台程序中:
program uninitializedV
{$APPTYPE CONSOLE}
procedure T
& Writeln(A);&// uninitialized yet
& A := 12345;
& Writeln(A);&// initialized: 12345
第一个显示的值(未初始化的变量A)依赖于变量A存储的地址中以前的值.本例中,显示为 ($7FFD8000) ,但在其他计算机上会显示不同的值.值是未定义的,因为其未初始化.在复杂的程序中, 尤其就指针而言这经常会导致程序瘫痪或意想不到的结果.赋值语句将变量A初始化为12345 ($), 第二个值显示正常.
指针也是变量.但其中不存储数值或字符,而是一个内存存储单元的地址.如果将内存看做是一个大数组,指针可以看做是这个数组中的一个入口,指向数组中另一个数组的入口索引.( If you see memory as an array,a pointer can be seen as an entry in the array which contains the index of another entry in the array.)
假设有如下的声明和初始化过程:
& I := 4222;
& J := 1357;
& C := ‘A‘;
并假如有如下的内存布局:
执行完这些代码后,假如P是一个指针,
既有如下情形:
上图中,显示出了每个字节.通常是不必要的,因此可简化为:
这个图不能在反映出真实的内存占用大小(C看起来可I或J一样大小),但对理解指针来说足够了.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.&& Henry Spencer
Nil&是一个特殊的指针值.可以赋值给任意类型的指针.其表示空指针(nil在拉丁语中是nihil,表示啥也没有或零;或者说NIL表示Not In List).表示指针有定义的状态,但不能访问任何值(C语言中nil表示为NULL--见上面的引用).
Nil&不执行可用的内存,但作为一个预定义的值,很多例程可以对其进行检查 (例如使用Assigned()函数).赋予了一个有效值后就无法检测了,旧指针或未初始化指针与正常的指针没有什么不同. 没有方法区别他们.程序逻辑必须确保指针或是有效的或是nil.
Delphi中,&nil&的值是0,指向内存区域中的第一个字节.很明显这个字节Delphi代码是无法访问到的.除非对后台原理非常理解,通常不必关心nil等于0.nil&的值可能会在以后版本中出于某种目的进行修改.
上例中P是Pointer类型的.意味着P包含一个地址,但不知道地址处的保存的变量信息. 指针之所以是通用的类型,指针可以解读指向的内存区域中存储的特定类型的内容.
假设有另一个指针, Q:
Q 的类型是^Integer,可以读作&指向Integer& (^Integer相当于&Integer).即这不是一个整数,而是指向一个常用的内存存储单元.要将变量J的地址赋予Q,可以使用@&地址操作符或定价的伪函数Addr,
& Q := @J;&// Q := Addr(J);
Q指向了局部地址$ (指向了有变量J标识的内存存储单元).但由于Q是一个类型化指针,编译器将Q指向的内存单元看做是一个整数.Integer是Q的基本类型.
虽然很少看到使用伪函数Addr的代码,其等价于@.对于复杂的表达式@有时很难看出是作用于那个部分.而Addr使用的是函数语法,配合小括号减少了混淆:
& P := @PMyRec^.Integers^[6];
& Q := Addr(PMyRec^.Integers^[6]);
通过指针赋值与直接使用变量有少许不同.通常只能通过指针进行操作.如果对普通变量赋值,形式如下:
& J := 98765;
将整数值98765 (十六进制$000181CD)赋予内存存储单元.使用指针Q来存取内存存储单元,必须通过间接方式,使用^操作符:
& Q^ := 98765;
这叫做降低引用(dereferencing). 假想虚拟箭头指向Q中的地址(这里是$)并将对其赋值.
对于结构体,如果没有异议,语法上可以省去^操作符.为了清晰,建议保留..
通常将常用的类型指针预先定义好.例如,^Integer不能用于参数传递,需要预先定义一个类型:
& PInteger = ^I
procedure Abracadabra(I: PInteger);
事实上,&Pinteger类型和其他常用指针类型已经在Delphi的运行时库 (例如System和SysUtils单元)中预先定义.通常命名为P加上指向的类型名称.如果基本类型名称以T作为前缀,则忽略T.例如:
& PByte = ^B
& PDouble = ^D
& PRect = ^TR
& PPoint = ^TP
上例中按需定义了变量.有时无法确认是否需要一个变量,以及多少个变量.通过指针就可以使用匿名变量. 可在运行时预留内存,并返回一个指针.使用伪函数New():
& New(PI);
New()&是一个编译器伪函数.其为PI预留基本类型大小的内存,并使PI指向这块内存区域(其中存储了内存区域的地址).变量没有名称,因此是匿名的.只能提供指针间接访问.现在可对其赋值,在函数间传递,在不需要的时候调用Dispose(PI)释放:
& PI^ := 12345;
& ListBox1.Add(IntToStr(PI^));
&&// lots of code
& Dispose(PI);
除了New和Dispose,也可调用更低级别的函数GetMem和FreeMem.但New 和Dispose有几个优点.他们已经知道指针的基本类型,并对内存存储单元进行必要的初始化和释放.因此无论何时都优先使用New和Dispose替代GetMem和FreeMem.
确保每个New()都有一个对应的使用相同类型和指针的Dispose()调用,否则变量就不会被正确的释放.
可能相对于直接使用变量的优点不是很明显,但对于不知道需要多少个变量的情形很有用.假如一个链接列表(see&),或一个TList. TList存储指针,如果想在List中存储Double值,可以简单的对每个值调用New()并将指针存储在Tlist中:
& while HasValues(SomeThing) do
&&& New(P);
&&& P^ := ReadValue(SomeThing);
&&& MyList.Add(P);
&&&&// etc...
当然在列表不再使用的阶段要对每个值调用Dispose().
使用匿名变量,可以容易的阐述通过类型指针操作内存.两个指针类型不同,执行同一块内存,可以显示出不同的值:
program InterpretM
{$APPTYPE CONSOLE}
& PC: PAnsiC
& New(PI);
& PI^ := $006D654D;&&&&&// Bytes $4D $65 $6D $00
& PC := PAnsiChar(PI);&&// Both point to same address now.
& Writeln(PI^);&&&&&&&&&// Write integer.
& Writeln(PC^);&&&&&&&&&// Write one character ($4D).
& Writeln(PC);&&&&&&&&&&// Interpret $4D $65 $6D $00 as C-style string.
& Dispose(PI);
PI&将内存存储单元填充为$006D654D (7169357).下图(注意地址纯粹是虚构的):
PC&指向了同一块内存(由于基本类型不同,不能直接进行赋值,必须进行类型转换).但PC&指向一个AnsiChar,因此如果调用PC^, 获取到的是一个AnsiChar, ASCII字符值为$4D或‘M‘.
PC&是特殊情况,尽管是PAnsiChar类型, 由于实际是指向AnsiChar的指针,处理起来和其他类型指向稍有不同.在其他文章中解释.PC,如果没有降低引用,通常看做是指向以#0结尾的字符串,调用Writeln(PC) 将$4D $65 $6D $00字节显示为‘Mem‘.
当思考指针问题,尤其是复杂指针,我都准备一页纸和钢笔或铅笔,绘制如本文所见的图.变量的地址也是伪造的(并不是32位的,而是像30000,40000,&40004,&40008&和&50000便于理解就好).
适当使用指针是很有用而且灵活的.但一旦出错将是一个大问题.这也是很多人尽量避免使用指针的原因.这里描述常见错误.
未初始化指针
指针是变量,可其他变量一样需要初始化,或赋予其他指针值,或使用New或&GetMem&等等:
& P1 := @I;&&// OK: using @ operator
& P2 := P1;&&// OK: assign other pointer
& New(P3);&&&// OK: New
& Dispose(P3);
例如如果简单的声明PInteger,但没有初始化,指针包含了一个随机字节值,其指向了随机的内存区域.
如果访问这个随机的内存地址,会发生令人厌恶的事情.如果这个内存地址不在当前应用程序预留范围内,获取一个非法访问(Access Violation)错误,程序崩溃.但如果内存在应用程序预留范围,并写了数据,可能修改了不应修改的数据.如果数据在程序的其他部分稍后使用,会导致程序的数据错误.这样的错误很难查找.
事实上,如果获取到AV或其他类型的明显错误,是值得高兴的(除了硬盘损坏).应用程序崩溃不好,但问题很容易定位和修改.但对于错误数据和结果,问题更加糟糕,可能没有注意到或很久才爆发.因此使用指针必须及其谨慎.要细致的检查未初始化的指针.
旧指针是以前有效的指针,但后来过时了.经常因为指针指向的内存区域被释放和重用.
经常发生旧指针的情况是内存被释放,但指针仍旧被使用.为避免这种情况,有些程序员在释放内存后总将指针设置为nil.并在访问内存前检查其是否为nil.换句话说,nil是指针不可用的标志.这是一种途径,但并不总有效.
另一种常见情况是多个指针执行同一个内存区域,然后用其中的一个指针释放内存.即使将那个指针置为nil,其他指针还是指向了这块被释放的内存.如果幸运报非法指针错误,但具体会发生什么事情是不确定的.
第三,相似的问题是指向不稳定的数据,例如,数据会随时消失.最大的错误是在函数中返回一个指向局部数据的指针.一旦例程结束,数据消失,局部变量不再存在.典型(愚蠢)的范例:
function VersionData: PC
& V: array[0..11] of C
& CalculateVersion(V);
& Result := V;
V位于过程栈.这是每个运行时函数用来存放局部变量和参数,以及敏感的函数返回地址的地方.结果值指向V&(PChar&可以直接指向数组,见提及的).VersionData&结束后,栈被另一个运行的例程改变,无论CalculateVersion计算的结果是什么都以过时,指针指向了同一栈位置的新内容.
同样的问题还有Pchar指向一个字符串,请见.指针指向动态数组元素也是一个经典问题,因为动态数组变小或调用SetLength后会被移动.
使用错误的基本类型
事实上指针可以指向任意内存存储单元,两个不同类型的指针可以指向同一个区域,意味着可以按不同方式存取同一个内存区域.使用指向Byte(^Byte)的指针,可以按字节修改整数或其他类型的内容.
但也可能会写覆盖(overwrite)或读覆盖(overread).例如,如果用整数指针去访问存储一个字节的内存区域,将会写4个字节,而不仅仅是Byte类型的单字节,还有其后的3个字节,因为编译器将这连续的4个字节看做是一个整数.同样读取这个字节,也会多读3个字节:
& PI := PInteger(@B);
& I := PI^;
J&有正确的值,因为编译器会填充0将单字节扩展成为一个整数(4字节).但变量I则不同,其包括一个字节,以及其后的3个字节,组成了未定义的值.
指针允许不通过变量本身来设置变量的值.这可能在调试过程中很疑惑.知道一个变量的值错误,但不知道哪里的代码修改了变量,因为其是通过指针设置的.
所有者和孤儿
指针不但有不同的基本类型,还有不同的所有者语义.如果使用New或GetMem,或其他特定例程申请内存,你就是内存的所有者.很好,如果要持有这块内存,需要将指针保存到一个安全的地方.这个指针是唯一可访问这块内存的途径,如果其中的地址丢失,就无法在访问或释放这块内存了.一个规则是申请的内存必须被释放,因此你有责任照顾好他.设计良好的程序必须考虑到这些.
理解所有者是很重要的.拥有内存的人必须负责释放内存.可以找代理来执行这个任务,但必须确保执行正确..
一个常见的错误是使用指针指向一块分配的内存,而后又将这个指针指向另外一个内存块.指针指向第一个内存块又再次指向了另外一个内存块,原来的内存块丢失.已经没有办法找回第一个申请到的内存块.内存块成为了孤儿.已无法再次访问并处理他.这也叫做内存泄露.
这是摘自Borland的新闻组中的范例代码:
& bitdata: array of B
& pbBitmap: P
& SetLength(bitdata, nBufSize);
& GetMem(pbBitmap, nBufSize);
& pbBitmap := Addr(bitdata);
& VbMediaGetCurrentFrame(VBDev, @bmpinfo.bmiHeader, @pbBitmap, nBufSize);
事实上,这个代码有几个冲突的地方.&SetLength为bitdata分配字节.出于某种原因程序员使用GetMem为pbBitmap分配了同样数量的字节.而后将pbBitmap指向了另外一个内存地址,导致由分配GetMem的内存无法被访问.(&pbBitmap是唯一访问的途径,但现在不在指向他了).换句话说,内存泄露了.
事实上,还有几个错误.&bitdata&是一个动态数组,获取bitdata的地址只是得到了一个指针的地址,而不是缓冲区中第一个字节的地址(更多信息参见下面的动态数组).而且,由于pbBitmap也是指针,在函数调用时使用@操作符传递参数是错误的.
正确的代码如下:
& bitdata: array of B
& pbBitmap: P
& if nBufSize & 0 then
&&& SetLength(bitdata, nBufSize);
&&& pbBitmap := Addr(bitdata[0]);
&&& VbMediaGetCurrentFrame(VBDev, @bmpinfo.bmiHeader, pbBitmap, nBufSize);
& bitdata: array of B
& if nBufSize & 0 then
&&& SetLength(bitdata, nBufSize);
&&& VbMediaGetCurrentFrame(VBDev, @bmpinfo.bmiHeader, @bitdata[0], nBufSize);
看起来是很严重的问题,但是在复杂代码中很容易出现.
注意指针不必一定执行自己的内存块.指针通常用来遍历数组(如下),或操作结构体中的成员.如果没有为其分配内存,就不必负责对内存块进行控制.可以看做是用完就失效的临时变量.
指针运算和数组
You can either have software quality or you can have pointer arithmetic, but you cannot have both at the same time.&& Bertrand Meyer
Delphi allows some simple manipulations of a pointer. Of course you can assign to them, and compare them for equality (if P1 = P2 then) or inequality, but you can also increment and decrement them, usingInc&and&Dec. The neat thing is that these increments and decrements arescaled by the size of the base type of the pointer. An example (note that I set the pointer to a fake address. As long as I don‘t access anything with it, nothing bad will happen):
program PointerA
{$APPTYPE CONSOLE}
procedureWritePointer(P: PDouble);
& Writeln(Format(‘%8p‘, [P]));
& P := Pointer($50000);
& WritePointer(P);
& Inc(P);&&&&//
+ 1*SizeOf(Double)
& WritePointer(P);
& Inc(P, 6);&//
+ 7*Sizeof(Double)
& WritePointer(P);
& Dec(P, 4);&//
+ 3*Sizeof(Double)
& WritePointer(P);
The output is:
The utility of this is to provide sequential access to arrays of such types. Since (one-dimensional) arrays contain consecutive items of the same type & i.e. if one element is at addressN, then the next element is at address&N+SizeOf(element)&&, it makes sense to use this to access items of an array in a loop. You start with the base address of the array, at which you can access the first element. In the next iteration of the loop, you increment the pointer to access the next element of the array, and so on, and so forth:
program IterateA
{$APPTYPE CONSOLE}
& Fractions: array[1..8] of D
&&// Fill the array with random values.
& for I := Low(Fractions) to High(Fractions) do
&&& Fractions[I] := 100.0 * R
&&// Access using pointer.
& PD := @Fractions[Low(Fractions)];&
& for I := Low(Fractions) to High(Fractions) do
&&& Write(PD^:9:5);
&&& Inc(PD);&// Point to next item
&&// Conventional access, using index.
& for I := Low(Fractions) to High(Fractions) do
&&& Write(Fractions[I]:9:5);
Incrementing a pointer is, at least on older processors, probably slightly faster than multiplying the index with the size of the base type and adding that to the base address of the array for each iteration.
In reality, the effect of doing it this way is not nearly as big as you might expect. First, modern processors have special ways of addressing the most common cases using an index, so there is no need to update the pointer too. Second, the compiler will generally optimize indexed access into the pointer using version anyway, if this is more beneficial. And in the above, the gain found by using a slightly more optimized access is largely overshadowed by the time it takes to perform the Write().
As you can see in the program above, you can easily forget to increment the pointer inside the loop. And you must either usefor-to-do&anyway, or use another way or counter to terminate the loop (which you must then also decrement and compare manually). IOW, the code using the pointer is generally much harder to maintain. Since it is not faster anyway, except perhaps in a very tight loop, I would be very wary of using that kind of access in Delphi. Only do this if you have profiled your code and found pointer access to be beneficial and necessary.
Pointers to arrays
But sometimes you only have a pointer to access memory. Windows API functions often return data in buffers, which then contain arrays of a certain size. Even then, it is probably easier to cast the buffer to a pointer to an array than to use Inc or Dec. An example:
& PIntegerArray = ^TIntegerA
& TIntegerArray = array[0..65535] of I
& Buffer: array of I
& PInt: PI
& PArr: PIntegerA
&&// Using pointer arithmetic:
& PInt := @Buffer[0];
& for I := 0 to Count - 1 do
&&& Writeln(PInt^);
&&& Inc(PInt);
&&// Using array pointer and indexing:
& PArr := PIntegerArray(@Buffer[0]);
& for I := 0 to Count - 1 do
&&& Writeln(PArr^[I]);
Delphi 2009
In Delphi 2009, pointer arithmetic, as usable for the&PChar&type (andPAnsiChar&and&PWideChar), is now also possible for other pointer types. When and where this is possible is governed by the new$POINTERMATH&compiler directive.
Pointer arithmetic is generally switched off, but it can be switched on for a piece of code using {$POINTERMATH ON}, and off again using {$POINTERMATH OFF}. For pointer types compiled with pointer arithmetic (pointer math) turned on, pointer arithmetic is generally possible.
Currently, besides&PChar,&PAnsiChar&and&PWideChar, the only other type for which pointer arithmetic is enabled by default is thePByte&type. But switching it on for, say,&PInteger&would simplify the code above considerably:
{$POINTERMATH ON}
& Buffer: array of I
& PInt: PI
&&// Using new pointer arithmetic:
& PInt := @Buffer[0];
& for I := 0 to Count - 1 do
&&& Writeln(PInt[I]);
{$POINTERMATH OFF}
So there is no need for the declaration of special&TIntegerArray&andPIntegerArray&types to be able to access the type as an array anymore. Alternatively, instead of PInt[I], the (PInt + I)^ syntax could have been used, with the same result.
Apparently, in&Delphi 2009, the new pointer arithmetic doesn‘t work as intended for pointers togeneric&types yet. Whatever type the parametric type is instantiated as, indices are not scaled bySizeOf(T), as expected.
References
Many types in Delphi are in fact pointers, but pretend not to be. I like to call these typesreferences. Examples are dynamic arrays, strings, objects and interfaces. These types are all pointers behind the scenes, but with some extra semantics and often also some hidden content.
What distinguishes references from pointers is:
&&&&&&References are immutable.&You can not increment or decrement a reference. References point to certain structures, but never into them, like for instance the pointers that point into an array, in the examples above.
&&&&&&References do not use pointer syntax.&This hides that they&are&in fact pointers, and makes them hard to understand for many, who do not know this, and therefore do things with them they would better not do.
Do not confuse such references with C++‘s&reference types. These are different in many ways.
在Delphi4之前,动态数组还不是语言的一个特性,但存在这个概念.动态数组是运行时分配的内存块,并通过指针进行管理.动态数组可以增长或压缩.这意味着需要重新分配指定大小的内存块,原来内存块中的内容需要拷贝到新的内存块中,原来内存块被释放掉,指针指向新的内存块.
Delphi中的动态数组(如array of Integer)类型也是这样的.但由运行时附件的代码来管理内存的读取和分配.如下的内存存储单元指针指向的地址有两个附加的域:分配的元素数量和引用数量.
如果如上图所示,N是动态数组变量的地址,那么引用数量(reference count)的地址就是N-8,分配的元素数量(length指示器)是N-4.第一个元素的地址是N.
每增加一个引用(如赋值,参数传递等),引用计数就加一,每次解除引用(如离开变量的作用域,或包含动态数组成员的对象被释放,或指向动态数组的变量指向了其他动态数组或nil)引用计数就减一.
使用低层次例程Move或FillChar或其他例程如TStream.Write存取动态数组经常出错.对于一个正常的数组(为区别于动态数组,将其叫做静态数组),变量与内存块等价的.而对于动态数组,情况就不是这样的了.因此如果一个例程想要按内存块的方式存取数组中的元素,就不能引用动态数组变量,而是需要使用动态数组中的第一个元素.
& Items: array of I
&&// Wrong: address of Items variable is passed
& MyStream.Write(Items, Length(Items) * SizeOf(Integer));
&&// Correct: address of first element is passed
& MyStream.Write(Items[0], Length(Items) * SizeOf(Integer));
注意上面代码中,&Stream.Write&使用无类型的var&参数,也是引用传值.下面会进行讨论.
多维动态数组
上面讨论的是一维动态数组.动态数组也可以是多维的.但只是语法层面上的,事实上不是的.多维动态数组实际上是一个指向另外一维数组的一维数组.
假如有如下声明:
& TMultiIntegerArray = array of array of I
& MyIntegers: TMultiIntegerA
现在看起来声明了一个多维数组,而且可以通过MyIntegers[0, 3]的方式存取其中的元素.但是声明的类型应该这样读(语法层面上):
& TMultiIntegerArray = array of (array of Integer);
或者更加明确的描述为:
& TSingleIntegerArray = array of I
& TMultiIntegerArray = array of TSingleIntegerA
可见,&TMultiIntegerArray&事实上是一个指向TSingleIntegerArray的一维数组.这样TMultiIntegerArray存储区域就不是按行和列排列的连续内存块,实际上是不定长的数组,如每个元素都指向了另一个数组,每个子数组都有不同的大小.因此对于
SetLength(MyIntegers, 10, 20);
(将分配10个TSingleIntegerArrays&,每个子数组有20个整数,表面上是一个矩形数组), 可以存取和修改每个子数组:
SetLength(MyIntegers, 10);
SetLength(MyIntegers[0], 40);
SetLength(MyIntegers[1], 31);
Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration.&& Stan Kelly-Bootle
字符串在很多方面都与动态数组相同.也有引用计数,有相同的内部结构,一个引用计数和指示其中存储的字符串数据的长度(在同样的偏移量上).
不同之处在语法和语义上.不能将字符串设置为nil,可以设置为‘‘&(空字符串)来清空他.字符串也可是一个常量(引用计数是-1,运行时例程中将其作为一个特殊值对其进行增减或释放字符串).第一个元素的索引是1,而动态数组的第一个元素索引是0.
字符串更多信息见.
对象& 更确切的说是类的实例,编译器不会管理其生命周期.其内部结构很简单.每个类实例的0偏移量处(相对于每个引用的指针执行的地址)有一个指针指向VMT表.其中包含指向类中的每个虚方法的指针.这个表的负偏移量中是关于类的其他信息.对此不作过多介绍.每个类只有一个VMT表(而不是每个对象).
实现了接口的类也有一个指向含有接口中被实现的方法表的指针,每个被实现的接口对应一个.这个表在负偏移量中也有一些额外的信息.这些偏移量处的对象指针指向与基类相关域的信息.编译器知道具体细节.
在VMT指针和接口表指针后面,是对象的域,与结构体相似.
对象的RTTI数据和其他类的信息从对象的这些引用中获取,如VMT指针指向的VMT表中等.编译器知道如何获取其余的数据,通常通过包含其他结构体指针的复杂结构体,甚至会循环引用来获得.
下例中,假设如下声明:
& TWhatsit = class(TAncestor, IPrintable, IEditable, IComparable)
&&&&// other field and method declarations
&&& procedure Notify(Aspect: TAspect);
&&& procedure C
&&& procedure E
&&& procedure ClearLine(Line: Integer);
&&& function Update(Region: Integer): B
&&&&// etc...
&&& Whatsit: TW
&&& Whatsit := TWhatsit.C
对象布局如下图:
接口实际上上方法的集合.在内部,他们是指向一个指向了代码的指针数组.假如有如下声明:
& IEditable = interface
&&& procedure E
&&& procedure ClearLine(Line: Integer);
&&& function Update(Region: Integer): B
& TWhatsit = class(TAncestor, IPrintable, IEditable, IComparable)
&&& procedure Notify(Aspect: TAspect);
&&& procedure C
&&& procedure E
&&& procedure ClearLine(Line: Integer);
&&& function Update(Region: Integer): B
&&&&// etc...
& MyEditable: IE
& MyEditable := TWhatsit.C
接口,实现对象,实现类和方法的关系如下:
MyEditable&指向了由TMyClass.Create创建对象中的IEditable指针.注意MyEditable&不是指向对象的起始地址,而是有一个偏移量.MyEditable指向对象中的一个指向指针列表的指针,其中包括接口中的每个方法实现.代码会调整Self&指针(事实上指向了MyEditable) 指向一个对象的起始地址(通过在传递的指针中减去对象中IEditable的偏移量,然后调用真正的方法).这是类实现的接口中方法的存根.
例如,假设实例的地址是50000,&TWhatsit实现的IEditable接口在实例中指针偏移量是16.那么MyEditable指向5处的IEditable&指针指向了在类中实现的接口方法表(例如在30000),而后指向方法的存根(如在60000).存根知道由Self传递的值在50016,减去16得到50000.只是实现接口的对象地址.存根而后通过将50000作为Self的地址调用真实的函数.
上图为简化忽略的QueryInterface, _AddRef和_Release的存根.
知道为什么需要用铅笔盒纸张了吧?&&;-)
引用参数通常叫做var&参数,但out&参数也是引用参数.
引用参数在实际传参时并不会将真正的值传递给例程,而是传递参数地址.例如:
procedure SetBit(var Int: I Bit: Integer);
& Int := Int or (1 shl Bit);
或多或少等价于下面的代码:
procedure SetBit(Int: PI Bit: Integer);
& Int^ := Int^ or (1 shl Bit);
&&&&&& 没有使用指针语法.使用参数名称自动进行对参数解引用,即使用参数名称就可操作目标变量,而不是指针.
&&&&&& 引用参数不能被修改.使用参数名称来执行目标变量,不能将其指向其他地址,或对其增减操作.
&&&&&& 必须传递有地址的变量,例如,一个实际的内存存储单元需要做一些变换.因此对于整型的引用参数,不能传递为17, 98765,或 Abs(MyInteger). 必须是一个变量(包括数组中的元素,对象或结构体中的成员等).
&&&&&& 实参必须与声明的参数类型相同,如,对声明为TObject的参数不能传递为TEdit.为避免这个问题需要声明无类型引用参数(untyped reference parameters).
语法上使用引用参数要比使用指针参数简单.但需要注意一些潜规则.传递指针,增加了一级间接引用.换句话说如果使用指针P指向一个整数,要传递参数必须转换为P^:
& Arr: array of I
&&// Initialisation of Int, Ptr and Arr not shown...
& SetBit(Ptr^, 3);&&&&// Ptr is passed
& SetBit(Arr[2], 11);&// @Arr[2] is passed
& SetBit(Int, 7);&&&&&// @Int is passed
无类型参数
无类型参数也是引用参数,但可以是var,&const或out.可以传递任意类型的参数,简化了可以接受任意大小和类型参数的例程编写,但是这也需要有机制传递参数的类型信息,或作为类型无关的例程.访问参数时必须进行类型转换.
内部无类型参数也是作为指针传递的.如下两个范例中,第一个通用例程可以填充任意大小的缓冲区,而参数Buffer的类型并不重要:
// Example of routine where type doesn‘t matter
procedure FillBytes(var B Count: I
& Values: array of Byte);
& LenValues: I
& LenValues := Length(Values);
& if LenValues & 0 then
&&& P := @B&// Treat buffer as array of byte.
&&& I := 0;
&&& while Count & 0 do
&&&&& P^ := Values[I];
&&&&& I := (I + 1) mod LenV
&&&&& Inc(P);
&&&&& Dec(Count);
第二个TTypedList 的子类TIntegerList中的方法:
function TIntegerList.Add(const Value): I
& Grow(1);
& Result := Count - 1;
&&// FInternalArray: array of I
& FInternalArray[Result] := Integer(Value);
可见,使用指针必须传递一个实参地址,即使参数已经是需要的指针了.而且,间接引用级别增加.
要存取引用目标,可以简单的作为正常的引用参数使用,但必须进行类型转换,编译器指定如何对指针进行解引用.
注意间接引用级别.如果需要使用FillBytes函数初始化动态数组,不能传递变量,而是需要传递数组中第一个元素.事实上,也可传递静态数组的首地址.因此如果要将数组作为无类型引用参数的实参进行传递,最后传递第一个元素而不是数组本身,除非要故意的访问错误的动态数组.
指针很广泛的用于数据结构中,如链接列表,树和层等.在此不作讨论.需要说明的是高级的结构没有指针和引用是无法实现的,虽然很多语言(Java)官方说不使用指针.要了解更多结构的信息请阅读相关专题的文档.
如下是简单的数据结构图表,非常依赖于指针的链接列表:
如果使用这样的结构,通常都是在类内部进行了封装,使用指针可以减少类内部的实现难度,但不会将指针暴露在公共接口中.指针很强大,但很难驾驭,尽量避免使用.
这里给出了很多指针的知识面.另一方面,使用带箭头的图表对理解复杂指针或接口变量,对象,类和代码间的关系很有帮助.绘制的图都是用于理解非常复杂的指针.
本文要阐述的是指针无处不在,即使没有看到他们.但不能滥用,理解指针可以更好的理解下层机制,可以避免很多错误.
希望对于大家有所帮助.本文一定不是很全面,需要改进之处请.
Rudy Velthuis
https://blog.csdn.net/henreash/article/details/7368088标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:https://www.cnblogs.com/findumars/p/8949587.html
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!}

我要回帖

更多关于 鼠标指针不动 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信