描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302493181丛书名: 清华开发者书库
配套资源 本书配套提供源代码,下载地址为清华大学出版社网站本书页面。专家评论本书涉及的主题? Igor Pro的基本概念和使用? Igor Pro的基本对象? Igor Pro的图表绘制? Igor Pro数据拟合的方法和技巧? Igor Pro数据处理的基本方法? Igor Pro程序设计的基本概念? Igor Pro命令行程序的设计? Igor Pro窗口程序的设计? Igor Pro高级程序设计的技巧
本书介绍Igor Pro的基本使用技巧和数据分析处理的一般方法,全面涵盖Igor Pro基本操作、图表绘制、命令行、数据分析拟合和程序设计等方面内容。在内容设计上以实用性为目的,突出图表绘制、数据拟合和程序设计等数据处理中需要的内容模块。书中配有大量的示例代码,以便读者在学习的过程中参考和借鉴。 全书共分为7章和1个附录,第1章介绍Igor Pro的基本对象和基本使用,突出命令行的特色。第2章介绍图表的绘制和设置中涉及的概念和方法,包括曲线、二维数据和三维数据的绘制。第3章介绍数据拟合的技巧和方法,包括简单的调用菜单拟合到复杂的自定义函数拟合,并详细讨论Igor Pro的一些高级拟合技巧。第4章介绍一些常见的数据处理方法,如插值、傅里叶变换、解方程等。第5章介绍程序设计的基本概念、Igor Pro语法环境以及命令行程序的设计。第6章介绍窗口界面程序的设计方法以及窗口程序设计中可能用到的各种技巧。第7章介绍一些高级的程序设计方法,如多线程、钩子函数、计算机硬件操作等复杂用法。附录介绍本书所用术语、Igor Pro快捷键和*版本的特点。 本书可作为高等院校、科研机构等相关单位从事实验教学或者实验科学研究的教师、工程师的参考书籍,也可作为高年级本科生和研究生实验数据分析和处理的参考书籍。
目录
第1章Igor Pro基本介绍
1.1Igor概述
1.1.1特色定位
1.1.2安装和使用
1.1.3基本界面
1.1.4菜单
1.1.5数据浏览器
1.1.6数据表格
1.1.7命令行窗口
1.2Igor中的基本对象
1.2.1wave
1.2.2图(Graph)
1.2.3表格(Table)
1.2.4页面布局(Page Layout)
1.2.5变量(Variable)
1.2.6数据文件夹(Data Folder)
1.2.7记事本(Notebook)
1.2.8程序面板(Control Panel)
1.2.9三维图(3D Plot)
1.2.10程序(Procedure)
1.2.11命令和函数
第2章图表绘制
2.1曲线
2.1.1绘制曲线
2.1.2添加新曲线
2.2图表的设置和美化
2.2.1设置绘图区域
2.2.2设置外观
2.2.3设置坐标轴
2.2.4设置图注
2.2.5向曲线添加自定义形状
2.2.6样式脚本
2.3类别图
2.3.1类别图的绘制和设置
2.3.2类别图的设置
2.4二维wave绘制
2.4.1Image的绘制
2.4.2Image的设置
2.4.3Contour的绘制
2.4.4Contour的设置
2.4.5Waterfall的绘制
2.4.6Waterfall的设置
2.4.7Surface的绘制
2.4.8Surface的设置
2.5三维wave的绘制
2.5.1三维图形绘制的概念
2.5.2三维图形的绘制
2.6输出图片
第3章数据拟合
3.1拟合概述
3.1.1拟合的基本原理和步骤
3.1.2基本拟合
3.1.3快速拟合及结果查看
3.1.4自定义拟合
3.1.5数据拟合对话框详解
3.2拟合公式模型
3.2.1内置拟合公式
3.2.2普通自定义拟合函数
3.2.3保存自定义拟合函数
3.2.4自定义拟合函数的格式
3.3拟合命令详解
3.3.1拟合命令参数详解
3.3.2常用拟合命令选项
3.3.3限定拟合参数范围
3.4高级拟合技巧
3.4.1隐函数拟合
3.4.2复杂自定义拟合函数
3.4.3all at once拟合
3.4.4使用结构体类型变量参数的拟合函数
3.4.5拟合过程中的特殊变量
3.4.6多峰拟合
3.4.7拟合的几个例子
第4章数据处理
4.1插值
4.1.1基本插值方法
4.1.2插值与均匀数据
4.1.3逆插值
4.1.4曲线平滑
4.2数值计算与统计
4.2.1微分和积分
4.2.2wave统计信息
4.2.3求解数值方程
4.2.4微分方程求解
4.2.5直方图
4.2.6排序
4.3数学变换
4.3.1傅里叶变换
4.3.2傅里叶变换窗
4.3.3希尔伯特变换
4.3.4卷积
4.3.5相关
4.4图像分析
4.4.1Lookup Table方法
4.4.2直方图均衡化
4.5随机数生成
第5章程序设计
5.1程序设计概述
5.1.1程序窗口
5.1.2程序窗口说明
5.1.3编译程序
5.1.3程序代码构成
5.1.4程序类型
5.2基本语法
5.2.1表达式和命名规则
5.2.2变量和常量
5.2.3Structures
5.2.4流程控制语句
5.2.5函数
5.2.6程序子类型
5.2.7参数传递
5.2.8默认参数
5.2.9注释和代码风格
5.3程序设计技术
5.3.1Include指令
5.3.2Pragma参数
5.3.3IndependentModule
5.3.4Execute命令
5.3.5条件编译
5.3.6函数引用
5.3.7访问全局对象
5.3.8wave引用
5.3.9$运算符
5.3.10自动创建变量
5.3.11调试程序
第6章窗口程序设计
6.1窗口程序概述
6.1.1创建一个简单的窗口程序
6.1.2窗口程序构成
6.1.3窗口生成脚本
6.1.4控件命令
6.2窗口控件
6.2.1Button按钮
6.2.2CheckBox复选框
6.2.3SetVariable文本框
6.2.4ListBox列表框
6.2.5PopupMenu下拉列表框
6.2.6Slider滑动条控件
6.2.7ValDisplay数值显示控件
6.2.8TabControl控件
6.2.9CustomControl自定义控件
6.2.10TitleBox和GroupBox控件
6.2.11控件操作
6.2.12获取控件信息
6.2.13控件结构体变量类型应用
6.3窗口设计
6.3.1Pictures详解
6.3.2创建Pictures
6.3.3窗口设计
6.3.4Graph和Panel的区别
6.4菜单
6.4.1菜单概述
6.4.2创建动态菜单
6.4.3系统右键快捷菜单中添加菜单项
6.4.4特殊菜单项
6.4.5创建弹出式菜单
6.4.6菜单项中的特殊字符
第7章高级程序设计
7.1程序中的free对象
7.1.1free wave
7.1.2free data folder
7.2多线程技术
7.2.1简单多线程技术
7.2.2free对象与多线程
7.2.3多线程编程
7.2.4后台任务
7.2.5抢占式多任务
7.2.6定时器和多线程
7.3运行时交互
7.3.1简单的输入数据框
7.3.2利用PauseForUser创建输入对话框
7.3.3程序进度条
7.4钩子函数
7.4.1用户自定义钩子函数
7.4.2窗口钩子函数
7.4.3依赖
7.5数据采集
7.5.1FIFO与Charts
7.5.2串口读写
7.5.3XOP扩展
7.6多媒体
7.6.1播放声音
7.6.2视频播放和创建
7.7错误处理
7.7.1程序错误退出
7.7.2trycatchendtry
7.7.3Igor错误代码和描述
7.8文件读写
7.8.1文件读写函数和命令
7.8.2文件读写示例
7.9初始化技术
7.9.1新建实验文件时初始化
7.9.2打开窗口程序时初始化
7.10其他编程技术
7.10.1计时
7.10.2Cursor编程
7.10.3字符串及正则表达式
附录A本书术语说明
附录BIgor常用快捷键
附录CIgor Pro 7新特性
前言
《Igor Pro实用教程——图表绘制、数据分析与程序设计》终于要和读者见面了。此时,我内心非常激动。
这里首先介绍本书创作的缘由。
在笔者就读大学期间,还未听说过Igor Pro。当时,我处理数据用的是Turbo C 3.0。由于没有意识到数据处理软件这种工具的存在(比如基本的Excel),我觉得数据处理就是编程。以至于后来,我甚至构建了一个雄伟的计划: 利用Turbo C设计一个数据处理软件,基本功能是绘图和小二乘法,甚至连软件架构都写好了。遗憾的是,因为没有计算机,加之学校的机房上机费太贵,这个计划终被搁浅了(幸亏如此)。随着升入高年级,实验课结束,这个计划终于被彻底忘记了。不过,这种编程处理数据的思路终还是让我受益匪浅。本书介绍的Igor Pro就是适合通过编程处理数据的工具。
上研究生时,实验数据处理这个问题再次出现。不过我发现不能再继续用Turbo C 3.0了,因为实验室所有的人都在用Igor Pro,所以我开始了Igor Pro的学习和使用。学习Igor Pro的经历是值得回顾的。
记得次看到这个软件,感觉很茫然。
按照以往的经验,不懂的内容可以通过Google搜索。可是在Igor Pro的学习过程中,我从来没有用过一次Google或者百度,甚至连这种意识都没有(我想本书的读者和我也是一样的)。原因很简单,网上没有任何关于Igor Pro的学习资料。我能做的,就是向同实验室的人请教,自己在挫折中慢慢摸索; 阅读现有的代码,掌握Igor Pro的基本使用方法。这里我不得不感谢我的导师周兴江研究员,他不仅仅是一位在超导研究领域取得卓越成就的科学家,也是一位出色的Igor Pro编程大师。我今天关于Igor Pro的认识,应该说就是从研究他的代码开始的。
学习的经历是艰辛的。任何一个小问题的解决都不容易。现在回顾起来,我发现走了很多弯路,不仅仅是学习的弯路,还有使用的弯路。当时使用的很多方法其实非常笨拙,效率非常低。比如一个基本的问题,当时程序运行的速度比较慢,绘制一幅费米面的图需要半分钟左右,大家都认为是Igor Pro的问题。后来我发现不是,是我们没有理解Igor Pro下的程序设计机制,没有搞清楚Proc和Function的关系。在搞清楚这个问题后,我对所有的程序进行了一次彻底的升级。然后突然发现,以前几分钟的计算现在一眨眼就可以完成。很难想象我们一直在这种低效率的工作状态下使用Igor Pro很多年,仅仅是因为不了解Proc和Function所致!
诸如此类的问题非常多,如图表绘制、数据拟合、算法设计等,不胜枚举。很显然,要做好这些工作,需要很好地了解Igor Pro。
遗憾的是Igor Pro的学习资料太少了。Igor Pro其实是一款非常优秀的数据处理软件,特别是处于大数据时代的今天,它能将编程与数据可视化完美地融为一体,既具有Python、R等脚本编程语言的可扩展性,又具有Origin等数据可视化工具的方便易用性,十分难得。但由于软件的语言(英语)、需要编程的特性以及用户使用群体(主要集中在国外)等原因,Igor Pro一直未被广大用户所了解。这样造成的后果就是没有人去讨论和贡献自己对Igor Pro的心得和使用技巧。Igor Pro本身的软件文档写得非常好,但是作为一个手册,其实是不适合初学者的,只有在一定的基础上看软件文档,才有效果。初学者直接看文档,很容易感到迷茫。
由于学习资料的匮乏,很多人,特别是刚进入实验室的人员对Igor Pro望而生畏,转而去选择其他的工具。其实,Igor Pro更适合他们,更适合他们处理数据。于是,Igor Pro的潜在使用者就这么流失了。反过来,这又影响了后来的人去选择Igor Pro。
虽然有所谓酒香不怕巷子深的古训,但是,如果酒是香的,为什么不能将它放到浅一点的巷子里呢?好东西应该是被大众所共享的,而不应只属于个别“资深酒客”。
在这么多年使用Igor Pro的过程中,在帮助他人解决Igor Pro的一些问题时,我对Igor Pro的认识也越来越深刻。我发现,Igor Pro能做的其实远比我们想象的多。但是,很多人,包括在实验室里天天使用Igor Pro的人们,却没有意识到,其实他们使用Igor Pro的水平并不高(这当然是完全可以理解的,由于更专注于科学研究,他们不可能在这上面花太多精力)。
所有的这些,促使我决定编写一本关于Igor Pro使用的书籍,把我这么多年来对Igor Pro的使用心得和经验总结出来,公布于众。所谓授人以鱼,不如授人以渔。我的目的就是希望读者在使用Igor Pro遇到困惑时,能知道去哪儿找到解决问题的方法,少走一些弯路,而不是只寄希望于求助别人或者浪费很多的时间。同时,也更希望读者能利用本书中提到的知识提高数据处理效率,节约时间和精力。当然,我知道本书离这个目标还很远,但至少这是一个好的开始。
本书真正的写作始于两年前。这个过程和我学习Igor Pro的经历一样,也是艰辛的: 没有资助,缺乏参考资料只能利用业余时间创作。所有的一切都是靠兴趣、靠对Igor Pro的热爱在支撑。当然这很正常,任何一个新的领域在刚开始时都是这样的。既然还没人做这件事情,那么就从我开始吧。
本书的体例结构都是经过精心设计的,目的就是突出实用性。各章节结构具有相对的独立性,每一小节一般都对应于Igor Pro某个方面的使用。建议读者仔细阅读第1章和第5章,前者是Igor Pro工作原理的基础,后者是程序设计的基础。其他各章节可在需要的时候选择性阅读。另外,读者在阅读本书时,可结合Igor Pro自带的软件手册进行学习,这样会获得事半功倍的效果。
在完成本书的过程中,我曾与周兴江研究员、谢卓晋博士、物理所超导实验室SC7组进行过多次讨论,书中很多创作的灵感都来源于这些讨论,在此表示谢意。
清华大学出版社的盛东亮编辑在本书出版的过程中给了我很大的帮助。盛编辑对新事物的开放和支持态度,对教育科技知识推广的责任心,值得敬佩。这里表示谢意。
后,由于本书是此领域的本书,也限于我的水平,书中难免存在错误之处。在这里恳请读者在阅读过程中发现错误能及时指出,以便我及时修正。
贾小文
2018年1月于天津
第5章程序设计使用菜单以及各类对话框可以完成一般的数据处理工作,如果仅限于此,使用Igor的水平是比较低的,Igor强大的数据处理能力远远没有发挥出来。Igor是一个基于命令行的数据处理工具,通过编写程序才能彻底发挥其强大的数据分析威力。如光电子能谱数据,一次实验少则几十张图谱,多则上百张图谱,每张图谱都由好几百条曲线组成,仅靠菜单手动处理分析这些数据,几乎是不可能的。作为一款数据处理工具,程序设计应该满足几个基本准则: (1) 自由。程序设计的目的就是为了自由地设计各种新功能,这要求编程环境应该类似于普通的编程语言,具有的自由度,不仅仅能分析数据,还能访问操作计算机的系统资源,如文件、窗口、输入输出设备等。编程环境必须具有对任何对象的实时获取能力,而不是“硬编码”。(2) 简单。应和普通的程序设计语言区分开来,不能让语法的复杂性掩盖了服务数据处理需求的本质。程序设计的本质就是对各种数据对象的访问和操作,必须有一个简单、统一的获取基本数据对象的机制,好是按照人们对数据的直观认识去操作。(3) 高效。既然是编程,在速度上就不能太慢,至少和普通编程语言不能差太多。如果编程仅仅是对命令行的批量解释执行,这样的程序设计是不满足要求的,不能算作真正地支持程序设计。Igor在这3个方面都支持得非常好。Igor的图形界面提供的功能,几乎都可以通过它自身的编程环境实现。对数据对象的访问也非常简单,基本的对象都是全局对象,可以利用名字直接访问,同时提供了大量的函数和命令用于获取这些对象的基本信息。在效率方面,Igor里所有的程序都可以被编译成较底层的机器码,因此速度非常快。Igor下的程序设计具有以下特点: (1) 语法环境规范而完整。(2) 面向过程的程序设计语言,命令和函数被组织在一起形成一个功能相对独立的操作单位。(3) 有两种级别的程序模式: 脚本程序(Macro、Proc)和函数程序(Function)。前者语法规范宽松,速度慢,但使用简单; 后者语法规范严谨,编译为机器代码执行,速度极快,但使用相对复杂。(4) 有一个方便的编程环境和调试环境,可以方便地编写程序和调试代码。调试器可查看任意变量值,支持查看表达式。(5) 可以利用C/C 编程工具无缝扩展Igor功能,甚至控制硬件采集数据。扩充的功能作为动态运行库被Igor调用,和内置函数和命令没有任何区别。(6) 提供了近千个不同的函数和命令,涉及绘图、窗口、数据操作、数据分析、矩阵、信号处理、统计、文件读写、数学函数等多个方面,以供在程序设计中使用。(7) 程序必须在Igor环境下运行。不能编译脱离Igor运行的程序。虽然功能很强大,但是学习曲线并不是很陡峭。即使没有受过任何编程训练的人,通过本书的介绍,也能在很短的时间之内掌握Igor的编程方法。5.1程序设计概述5.1.1程序窗口
程序窗口是写程序的窗口。程序窗口相当于具有IDE的程序设计语言中的代码编辑器,在该窗口中可以完成输入代码、编译、运行和调试等工作。程序窗口具有语法高亮的功能,如系统关键字显示为蓝色,函数显示为深橙色,预编译指令显示为紫罗兰色,命令显示为暗青色,注释显示为红色等。除了编辑功能之外,程序窗口还可以查看帮助,查看某个函数(或程序),运行命令等。版本的Igor(Igor Pro7版本)还支持外置编辑器,如Vim。按Ctrl M键(或者利用菜单命令【Windows】|【Procedure Windows】|【Procedure Window】)就打开了一个程序窗口,这是一个内置的程序窗口,可以在该窗口中输入代码完成程序编写,如图51所示。
图51内置程序窗口
读者可以在程序窗口输入以下代码:
Function myfun()
string s=”This ia my first function in Igor\r”
s =”Please give two numbers, I’ll calculate the sum.”
doalert 0,s
Variable v1,v2
prompt v1,”Input the first number”
prompt v2,”Input the second number”
doprompt “Calculate Sum”,v1,v2
string s1=”The sum of ” num2str(v1) ” and ” num2str(v2) ” is ” num2str(v1 v2)
doalert 0,s1
End
输入完毕,单击程序窗口下方的【Compile】按钮编译程序(注意Igor下的函数必须在编译后才能运行)。【Compile】按钮在单击后会消失,对应位置显示为空白,表示程序已经编译完毕,如图52所示。如果代码里包含错误,就会无法编译,此时会显示一个编译错误对话框。
图52程序窗口的编译
在命令行窗口输入myfun(),按回车键执行(注意不能省略圆括号)。还可以在程序窗口中选择myfun(),按Ctrl Enter键执行程序。程序首先显示一个对话框,然后提供一个输入对话框,输入两个数字,后显示一个对话框,显示输出结果。Ctrl M快捷键打开的程序窗口是Igor内置的程序窗口。Igor内置的程序窗口默认是全局的,也就是说所有的程序都可以访问内置窗口里的程序和变量。可以通过菜单命令【Window】|【New】|【Procedures】打开一个新的程序编辑窗口,此时可以给窗口起一个有意义的名字,如MyProc,如图53所示。
图53创建新程序窗口
新程序窗口有一个名字,如这里的MyProc,在程序设计里可以通过这个名字来访问该程序窗口,此时程序窗口相当于一个普通的窗口。内置程序窗口是Igor的一部分,保存实验文件后内置程序窗口里的内容都将被保存。非内置程序窗口如果没有保存到文件,也会随实验文件一起保存,但是一旦被保存到文件里,Igor就不会再保存整个程序文件,而只是保存程序文件的路径信息。内置程序也可以被保存在文件里,但是其内容仍然会在保存实验文件时被保存。如果不使用内置程序编辑窗口,例如将不同的函数放在不同的文件中,可以创建新的程序编辑窗口。5.1.2程序窗口说明1. 保存程序窗口中的程序习惯于VS或者其他编程环境的读者都知道,在IDE中创建代码时,需要首先创建一个代码文件,然后在IDE中修改或者编辑该文件。在Igor中稍稍不同,当利用快捷键Ctrl M或者Windows菜单创建一个新的程序编辑窗口,在其中编写代码时,代码是实验文件的一部分,并没有创建对应的文件(很好理解,因为这个过程中没有指定任何要保存的文件)。当保存实验文件时,程序窗口连同内部的代码会和数据一起保存,这样当下次打开实验文件时,里面的程序窗口及程序代码也会被同时打开。实际情况中,程序应该是通用的,而不是依赖于某个具体的实验文件,这就需要将程序窗口中的代码以文件形式单独存放于硬盘或者其他存储介质中。保存程序文件的方法很简单,保持需要被保存的程序窗口处于激活状态,执行菜单命令【File】|【Save Procedure As】,在弹出的保存对话框中浏览到合适位置保存即可。此时窗口中所有的内容都会被保存在一个扩展名为ipf的文件中,程序窗口的标题也被替换为保存时的名字。一般将经常用到的程序文件保存到Igor安装目录/User Procedures或者Igor安装目录/Igor Procedures下。当然可以不存放在这个文件夹里,此时使用Include指令包含程序文件时需要指明完整路径。保存程序文件如图54所示。
图54保存程序文件
图54(续)
当打开一个存放于硬盘上的程序文件(或者将程序文件保存在硬盘上)时,Igor会建立该文件与Igor中对应窗口的关联,但这种关联不是实时的。如果修改代码,代码并不会实时保存到程序文件中,编程者需要通过按Ctrl Shift S键来保存程序文件的内容(或者通过菜单命令【File】|【Save Procedure】)。每次按Ctrl Shift S键只保存当前程序窗口,如果有多个程序窗口,需要选择每个窗口并执行保存命令。当程序窗口被保存为独立文件时,Igor仅会在实验文件里保存该程序文件的路径信息,而不再保存其内容。下次打开实验文件时,Igor会自动根据路径信息打开该程序文件(并列出在【Windows】|【Procedures】菜单下)。如果程序文件不存在(这种情况很普遍,比如打开从别的地方复制的实验文件,程序文件被误删或者改名,程序文件的存放路径发生改变),Igor就会报错并显示一个对话框让用户提供正确的程序文件路径。本例中,读者可以先保存实验文件(按Ctrl S键)并关闭实验文件,在电脑里将MyProc1.ipf改为MyProc2.ipf,然后再打开已经保存的实验文件,就会看到如图55所示的错误提示。
图55找不到程序文件的错误
可以单击【Skip This File】按钮忽略错误,这种情况下程序文件就不能正常打开了,但不会影响实验数据或者其他程序的打开。程序文件没有打开,自然就不能使用程序文件里的程序,可以按【Look for File】按钮提供正确的程序文件。2. 设置程序字体对于中文语言字体的计算机,程序窗口默认字体为宋体,字体尺寸也往往比较小,看起来不太舒服,可以将其修改为常见的代码字体Courier New,并设置合适字号如12pt。设置方法如下: 选择一个程序窗口为当前窗口,主菜单栏会动态出现一个【Procedure】的菜单(利用该菜单可以设置程序编辑窗口的字体、字号等信息),执行菜单命令【Procedure】|【Set Text Format】,打开字体设置对话框,进行如图56(a)所示的设置,单击【OK】按钮,可以看到程序编辑窗口的字体变为常见的代码字体,如图56(b)所示。
图56设置程序窗口字体
注意,设置好的字体需要保存,否则当下次打开Igor后仍然为默认字体。保存设置的方法是执行菜单命令【Procedure】|【Capture Procedure Prefs】,选择相应的项,这里选中【Text Format】复选框,然后单击【Capture Prefs】按钮,就可以为这台计算机永久保存字体设置。读者可以试着创建一个新的程序编辑窗口并将字体设置为合适的字体。3. 打开并查看已有程序文件打开已有程序文件的方法非常简单,可以直接将已有程序文件拖入Igor或者执行菜单命令【File】|【Open File】|【Procedure】打开一个程序对话框,定位到程序文件处打开即可。如果在处理实验数据时打开了已有的程序文件,则在保存实验数据时会自动保存该程序文件的路径信息,下次打开实验文件时会自动打开程序文件。这要求程序文件存在且位置不能发生变化,否则就会出现打开文件错误。已经打开的程序文件可以修改其内容,如果修改时出现如图57所示提示则表示程序文件处于写保护状态。观察程序文件左下角,可以发现一个上面有一条斜线的铅笔状的小图标,表示当前程序文件处于不能编辑状态,如图58所示。单击该图标,即可解除不能编辑状态,再次单击则恢复不能编辑状态。
图57程序文件写保护警示
图58程序文件写保护状态
图59程序文件被其他程序打开或者被操作系统写保护,则不能修改
如果打开的程序文件已经被别的实验文件打开,或者是操作系统设置了写保护,则“小铅笔”对应位置处是一把小锁的图标,此时程序文件不能被修改,如图59所示。单击图59左端一个类似于文档的小图标,会显示当前程序文件的基本信息,如存放路径、当前光标所处行数等。小放大镜可以按比例对字体缩放。【Templates】下拉列表可以插入函数、命令模板或者各种流程控制语句,【Procedures】下拉列表可以查看自定义Macro或者函数。所有打开的程序文件都被列入【Windows】|【Procedure Windows】菜单内,可以在这个菜单中找到并查看已经打开的程序文件。但也有例外,当程序文件中指定Pragma的hide参数为1或者指定为IndependentModule或者是操作系统设置为隐藏时,虽然程序文件已经打开,但【Windows】|【Procedures Windows】菜单内并不列出。隐藏文件提供了一种保护代码不被修改的方法,但一般不建议采用。因为对于普通的使用者,即使文件不隐藏也一般也不会被修改,而对于程序开发者而言隐藏文件除了给自己制造一些麻烦之外没有什么别的用处。IndependentModule参数很有用,它可以指定一个程序文件单独编译,即使当前程序文件出现错误无法编译,IndependentModule中的程序仍然可以正常运行,这在使用Igor进行数据采集时非常重要,请参看5.3.2节。可以按Ctrl Alt M键在已经打开的程序文件窗口中切换查看,按Ctrl Alt Shift M键隐藏当前程序编辑窗口并自动显示下一个程序文件对应的程序编辑窗口。但这两个快捷键并不好用,好的办法还是利用【Windows】|【Procedures】菜单定位到指定的程序文件,也可以右击相应的对象(如控件、函数),然后【Go To】到指定的程序文件。
图510关闭程序窗口对话框
4. 关闭程序文件单击程序编辑窗口右上角的[×]按钮或者按Ctrl W快捷键,会调出关闭程序文件对话框,如图510所示。在这里有3个选项: 【Save and then kill】表示将程序编辑窗口中的程序保存到计算机存储设备,然后从Igor中彻底关闭,此时程序文件中的所有程序都将不能再使用。 【Kill】表示彻底关闭且不保存。 【Hide】表示隐藏程序编辑窗口,但是程序文件里的内容仍然留在内存里,可以通过菜单命令【Windows】|【Procedure Windows】查看并重新打开,或者利用其他方法如通过查找对话框等重新打开。一般在关闭程序编辑窗口时选择【Hide】按钮,既不影响程序文件中的函数的使用,同时又不会被程序编辑窗口遮挡影响数据处理。若【Macro】菜单中的【AutoCompile】为打开状态(默认)时,单击【Hide】按钮会自动编译程序。当打开一个写好的程序时,应该选择【Hide】按钮编译并隐藏程序文件,这样就可以像使用Igor内置的函数和命令一样使用程序文件中提供的程序。5. Adopt程序文件Adopt是保存程序文件的逆操作——将程序文件重新保存到实验文件里。在保存实验文件时,如果实验文件里的程序文件是存放于硬盘上的ipf文件,则程序文件里的内容不会被保存在实验文件里,而是保存了该程序文件的路径信息,这样当下一次打开实验文件时,Igor会根据路径信息自动打开该程序文件。但这样存在一个问题,如果实验文件在另外一台计算机上打开(比如与别人交流实验数据时),如果该计算机上不存在对应的程序文件,就会出现找不到程序文件的错误,实验文件也不能使用该程序文件中的程序。解决这个问题的方法是Adopt程序文件。要Adopt一个程序文件,先使该程序文件(外部打开的)处于显示状态,然后选择菜单命令【File】|【Adopt Procedure】,Igor会显示一个提示信息,提示用户会先复制一份程序文件放到当前实验文件,然后断开与源文件的关系。Adopt程序文件后,程序文件的内容将会成为实验文件的一部分,随实验文件保存,这样该实验文件在任何地方都可以使用程序而无须关心程序文件是否存在。5.1.3编译程序在第5.1.1节可看到程序只有在编译后才能运行。当然不是所有的代码都需要编译后才能运行,如果只是调用内置命令和函数的命令行,或者是proc和macro,无论是在命令行窗口还是程序窗口中运行都不需要编译。但注意,如果使用自定义函数,则必须编译自定义函数所在的程序文件。Igor的程序分为编译阶段和运行阶段。编译阶段,编译器会检查代码中的语法错误并将代码生成较低层次的机器语言。运行阶段,调用前面编译好的代码。编译后的机器代码执行速度要比直接解释执行快得多。Igor下程序文件的编译非常简单,当菜单命令【Macro】|【Auto Compile】处于被选状态时,单击程序编辑窗口以外的任何地方都会自动完成编译。第5.1.2节中提到的关闭程序文件选择【Hide】按钮编译程序文件就是这个原理。也可以主动编译程序文件,保持待编译的程序文件处于激活显示状态,选择菜单命令【Macro】|【Compile】
图511编译程序按钮
进行编译,或者按程序左下角的【Compile】按钮进行编译,如图511所示。
编译以后的程序文件【Compile】按钮处对应的地方为空白,否则会显示一个虚线状态的【Compile】按钮。【Macro】|【Auto Compile】即自动编译默认为打开状态,以方便程序自动编译。但有时需要关闭这个功能,在编写程序时这样做很有必要。在程序开发的过程中经常需要查看文件夹、wave、全局变量等信息,如果设置为自动编译,则在查看这些信息的过程中,Igor会自动编译还未完成的程序并报错,这会给程序的编写带来不便,此时就可以关闭自动编译。不同于普通的程序设计,Igor并不会生成独立的可执行程序,因此每次打开程序文件都需要执行编译过程。如果程序文件较大,编译会花费较长的时间,但一般都是很快的。如果程序文件随实验文件一起打开,则编译过程是自动的,不需要手动操作。5.1.3程序代码构成程序设计就是在程序编辑窗口中输入代码。按照代码在程序中的作用不同,可以将程序文件中的内容划分以下10个类型。 (1) 编译器指令,关键字为Pragma,一般位于程序文件开头,格式为#Pragma,并紧靠程序文件左侧,用于指定编译参数。(2) 程序文件包含指令,关键字为Include,一般位于程序文件开头,格式为#Include文件名或者#include “文件名”,和C语言程序设计类似,用于打开并包含一个已有的程序文件。(3) 常量声明,关键字为Constant,用于声明常量,类似于C语言的#DEFINE指令。(4) 结构体类型定义,关键字为Structure,定义的结构体变量类型可以在函数中使用。(5) 图片资源,关键字为Picture,以代码格式保存图片,类似于编程语言中的图形资源,这些图形资源可以作为图形按钮或者窗口程序的背景图片。(6) 菜单,关键字为Menu,定义菜单,可以向系统菜单添加菜单项,也可以创建自定义菜单。(7) 函数,关键字为Function,程序设计的主要内容,完成数据处理的功能单位,需要编译以后执行。(8) 脚本,关键字为Macro和Proc,功能类似于函数,但是并不需要编译,解释执行,类似于Windows下的批处理文件或者Linux下的bash文件。(9) 预编译指令,用于条件编译。(10) 注释,格式为以“//”开头,和C语言类似。请读者对照图512所示的程序代码示例理解上面的内容,其中用Include指令包含的文件内容如图513所示。
图512程序代码构成类型
图513被Include的程序文件
Proc0.ipf存放位置如图514所示。
图514被Include程序文件存放于计算机硬盘上
注意观察在菜单栏的后一项添加了一个名为mymenu的菜单,如图515所示。
图515自定义菜单
【Macros】菜单下面也添加了一个名为macro1的菜单项。【macro1】和【mymenu】菜单项的功能是相同的,即执行后在历史命令行窗口打印信息,如图516所示。
图516历史命令行窗口打印信息
上面没有包括Picture的例子,关于Picture在后面窗口程序设计时进行详细介绍。这些代码形式是Igor下语法结构的一部分,这里列出来主要是给读者一个整体印象,让读者了解一个完整的程序文件的基本组成。关于每个部分的详细介绍请参看接下来的内容。5.1.4程序类型Igor下可执行的程序类型有3种: Macro、Proc和Function。这些程序内容基本一样: 都包含变量声明和流程控制语句,通过调用其他程序处理数据或者对数据进行拟合,或者创建新的变量数据,实现一个具体的功能。3种程序形式的格式可描述如下:
Macro macroname(parameters list)
body
End
Proc procname(parameters list)
body
End
Function functionname(parameters list)
body
End
可以看到,3种程序形式也非常类似,功能也几乎完全相同。一般而言,能用Macro和Proc实现的功能,Function都可以做,反之亦然。但是它们的执行效率是不一样的,Macro和Proc是解释执行,而Function是编译执行。Igor是基于命令行的实验数据处理软件,在设计之初,为了执行命令的方便,引入了Macro的概念和方法,将命令以脚本方式组合起来,并扩充一些功能,如变量声明和流程控制等,辅助程序设计,这有点类似于Windows下的批处理以及Linux下的shell程序,其本质是批量执行多条命令。但缺点也很明显,由于是解释执行,当数据量比较大或者计算量比较大时,十分耗时。因此Igor后来的版本引入了函数的概念,并引入了自己的编译器。不同于Macro,函数并不是解释执行,而是先编译为低层次的机器指令,然后再执行,这就大大提高了程序运行的速度。下面比较Macro和Function二者在执行效率上的差异:
Macro test1()
Variable i
Variable t0,t1
t0=ticks
i=0
silent 1
do
i =1
while(i1e5)
t1=ticks
Print/D “Time used is “,(t1-t0)/60,”s”
End
Function test2()
Variable i
Variable t0,t1
t0=ticks
i=0
silent 1
do
i =1
while(i1e5)
t1=ticks
Print/D “Time used is “,(t1-t0)/60,”s”
End
图517脚本和函数执行的时间差异
在命令行输入test1()和test2()分别执行,结果如图517所示。完全相同的代码,test1()即Macro形式,执行时间约为11s,而test2()即Function形式执行时间在计算机数据精度范围内为0s,可见二者的效率差别之巨大。因此在编写程序时尽量使用Function而不使用Macro。Proc其实是另外一种形式的Macro。一般而言,使用Macro关键字声明的程序都会被默认添加在系统【Macro】菜单下,以方便执行,如果不希望这些程序出现在【Macro】菜单下,就可以使用Proc关键字声明程序。除此之外,二者完全相同。其实还可以利用Window关键字声明一个Macro,其执行方式和普通Macro一模一样。由于Macro和Function的这种差异,一般不建议在设计程序时使用Macro,而应该使用Function。Macro更多地是被Igor用来做一些自动化的操作,如自动生成程序面板创建代码、图表创建代码、样式风格创建代码等。除了执行效率和使用场合的区别之外,Macro和Function还有一些语法上的区别,Macro在使用上更自然方便一些,可以不需要声明而直接访问全局变量或者wave,但是有些语法不能使用,如不能使用for循环,不能使用switch语句等。而Function则相对严谨,全局变量必须在声明引用以后才能使用,语法格式也更加丰富多样。5.2基本语法作为一门完全可编程的语言,Igor具有系统而完整的语法环境,体现在以下几个方面: ,高度自治,不需要引进任何外部支持,原生支持脚本级别、编译级别的程序设计,并提供了很多普通编程语言才有的高级编程技术,如预编译指令、文件包含、条件编译、名称空间等。编译器支持的功能广阔,如字符串处理、结构体变量、函数指针、对所在平台系统资源的自由访问、线程安全函数设计及多线程编程等。这使得仅仅使用Igor,不借助任何外部专门的编程工具,就能够实现各种复杂的功能。第二,显著区别于普通的程序设计语言,将程序设计的复杂性做了包装,针对数据处理程序的设计做了大大简化,如变量仅包括数值型和字符串型两类变量(当然也可以指定更具体的变量类型,这会提高效率,但一般这两类就够了),没有普通编程语言“强类型数据”所带来的复杂性,所有的内置函数和命令(包括用户自定义函数)无须声明即可自由使用,提供极为通用的方式自由访问Igor的数据对象,如wave、变量、窗口等。第三,提供了相当方便的在线帮助系统,可随时查询函数及命令的使用并能够将示例直接插入代码处,几乎所有的对话框操作都能转化为对应的命令行并醒目显示,这些命令行都可以直接作为程序的代码。第四,提供了方便易用的扩展接口,可以利用C 等编程工具任意扩展其功能。
这4个方面的特性使得Igor非常适合于处理复杂且体积较为庞大的实验数据。在需要大批量重复的操作、在多变量中任意调整某变量随时观察数据变化和需要编写某一专门类型的数据处理工具时,Igor更是极好的选择。Igor下语法结构包括编译指令、文件包含、函数定义、变量定义,流程控制语句、注释等。本节重点介绍关于这些语法结构的基本含义。另外注意,Igor下的程序设计是面向过程的,主要通过函数和各种结构来构建程序体系。5.2.1表达式和命名规则表达式是代码的基本单位。变量声明语句、赋值语句、内置命令调用、用户自定义函数调用等都属于表达式:
Variable v=sin(x)
Make/N=100 gsfun=gauss(x,50,1) gnoise(0.1)
CurveFit gauss,data/D
myfun()
表达式里可以包含任何内置函数、命令,或是用户自定义函数。每一行只包含一个表达式,表达式结尾不需要使用分号(当然加上分号编译器也不报错)。另外,Igor程序设计语言不区分大小写,这和很多程序设计语言完全不同,如make、Make、MAKE表示完全相同的命令。Igor下编程语言使用ASCII字符,Unicode字符(如中文字符)只能包含在字符串中或者注释中。Igor下的命名规则和普通编程语言类似,无论是变量、函数、wave、窗口,名字一般都由字母、数字、下画线组成,其中下画线和数字不能位于开头,不能包含空格、逗号、冒号等字符,名字不能和系统内置的关键字重名。如下面的名字都是不合法的:
Variable sin
Make cos
newpanel/N=sin//系统会自动将sin改为sin0
Variable 1v, _v
注意,wave的命名条件稍微宽松一些,可以创建包含特殊字符的wave名字,如
Make ‘a wave’
这时wave名字需要用单引号括起来,Igor里把它叫作Liberal Name。这种命名方式会给wave的访问带来很大麻烦,因此不建议使用。5.2.2变量和常量变量包括数值型变量和字符串型变量。常量也可以理解为一种特殊的变量,只不过其值不能修改。常量同样包括数值型常量和字符串型常量。1. 数值型变量数值型变量的声明方式为
Variable [/C/D/G] varName [=numExpr][, varName[=numExpr ]]…
其中,Variable是关键字,varName是变量名。可以在声明时给变量赋值。如果声明后未赋值那么系统默认赋值0。Variable有3个选项,用于限定所声明变量的类型。(1) /C,表示声明一个复数型变量。(2) /D,表示声明一个双精度型变量,由于Igor下任何数值变量都默认为双精度,因此/D参数没有实际意义,主要是为了向前兼容(即和旧版本的Igor兼容)。(3) /G,表示声明一个全局变量,此变量会出现在当前数据文件夹下,并作为实验数据的一部分随实验数据永久保存。和一些强类型语言如C语言等不同,使用Variable声明的数值型变量没类型之分,所有的变量不管是整型还是浮点型甚至是Char型,其声明全部使用Variable关键字,在计算中全部使用双精度类型。这使得Igor下的变量定义和使用非常简单。变量名的命名规则和C语言相同,包括字母、数字和下画线,其中数字不能位于首位,变量名不能有空格等特殊字符,变量名也不能为系统关键字、函数名或者命令。由于Igor编程语言不区分大小写,所以num1和NUM1表示同一个变量。数值型变量主要用于程序设计,用来存放中间变量。全局的数值型变量主要用于保存控件状态以及在不同的程序之间传值。下面举几个变量定义的例子:
Variable v1 //声明一个变量v1
Variable v1=0 //声明一个变量v1,并赋值为0
Variable pi //错误,pi是系统函数,返回圆周率值
Variable sin //错误,sin是系统数学函数
Variable/C c=cmplx(1,1) //声明一个复数型变量,并赋值1 i
Variable/G v2 //声明一个全局变量v2
Variable _v3 //错误,不能以下画线开头
2. 字符串型变量字符串是字符的集合,在一般编程语言中经常用字符数组来表示。在Igor中字符串被简化为一个String型的变量,使用起来相当方便。字符串变量的声明方式为
String [/G] strName [=strExpr] [,strName[=strExpr]…]
其中,String是关键字,strName表示字符串变量名,/G选项和Variable含义一样,表示声明一个全局字符串。字符串变量名的命名规则和数值型变量相同。字符串变量名在声明时可以初始化。如果没有初始化,则系统会初始化为null,即空字符串。空字符串是不能使用的,否则会报错。在程序中一定要在声明字符串后对字符串赋值。字符串变量主要用于处理数据对象、图形对象以及其他Igor对象的名字,以方便在程序里对这些对象访问和处理。Igor对字符串处理的支持相当完善,提供大量的函数和命令用于对字符串的操作。下面举几个字符串声明的例子:
String str1=”” //声明一个字符串str1,赋空值
String str1=”hello,world” //声明一个字符串str1,并赋值”hello,world”
String/G str1 //声明一个全局型字符串str1
3. 变量的作用域和寿命变量的作用域取决于声明变量的位置和声明时的选项。如果在函数体内声明(包括Function、Macro、Proc)且没有使用/G选项,则变量的作用域只局限于程序体内,此时变量又称为局域变量。如果在程序体外声明或者是在命令行窗口声明,或者虽然在程序体内声明但是使用了/G选项,则变量的作用域为全局的,所有的程序都可以访问该变量。局域变量的寿命在程序运行终止时即结束。全局变量的寿命则是永久的,除非被删除。4. 常量定义常量分为数值型常量和字符串型常量。数值型常量的定义方式为
Constant kName = literalNumber
字符串型的常量定义方式为
Strconstant ksName=”literal string”
常量一般在程序体外程序文件的开头定义,作为一个常量使用,类似于C语言的#DEFINE语句。如果没有用static修饰符修饰,其作用域是全局的。注意常量和变量不同,系统并不会分配内存空间储存常量,因此不能对常量修改或者赋值。5.2.3StructuresIgor支持结构体。一般在程序文件的开头、程序的外部定义结构体类型,在函数内部通过声明方式创建结构体类型的实例。结构体定义方式如下:
Structure structurename
memtype memname [arraysize] [,memtype,memname [arraysize]]
…
EndStructure
Structure是关键字,structurename是要创建的结构体类型名字,memtype是Igor下支持的变量类型,包括variable、string、wave、nvar、svar、dfrer、funcref和结构体变量类型,除此之外,memtype还包括一些其他的数据类型,这些数据类型和C语言非常类似,如表51所示。
表51结构体支持的类C数据类型
变 量 类 型C语言对应类型字节数CharSigned char1UcharUnsigned char1Int16Short int2Uint16Unsigned short int2Int32Long int4Uint32Unsigned long int4FloatFloat4DoubleDouble8在结构体里可以声明数组,利用arraysize指定数组的长度,这个长度必须是常数或者常量,不能是变量。注意在函数中不能声明数组。创建好结构体类型后,可以使用如下的语法在函数中创建结构体变量:
Struct structurename name
这里Struct是必需的,类似于C语言里的Structure关键字,structurename是结构体类型名,name是要创建的变量名。下面是一个可能的例子:
Structure mystruct
Variable var1
Variable var2[10]
EndStructure
Function myfunc()
Struct mystruct ms
ms.var1=1
ms.var2[n]=20
…
End
上面的例子中,首先创建了一个结构体类型mystruct,然后在函数内利用Struct mystruct ms声明了该结构体类型的一个实例,即创建一个mystruct类型的变量ms,然后对ms赋值。这里看到访问结构体变量成员的方法: 变量名.成员名,这和C语言完全一样。给ms.var2数组赋值时,利用一个变量指定要赋值的位置,这是允许的。下面是一个更为复杂的结构体类型,当然这个结构体类型没什么实际作用,主要是为了让读者理解结构体的概念:
Constant kCaSize= 5
Structure substruct
Variable v1
Variable v2
EndStructure
Structure mystruct
Variable var1
Variable var2[10]
String s1
Wave fred
Nvar globVar1
Svar globStr1
Funcref myDefaultFunc afunc
Struct substruct ss1[3]
char ca[kCaSize 1]
EndStructure
上面的结构体类型中几乎包含了Igor下所有的数据类型。当结构体类型中包括引用时,在创建结构体变量后必须对该引用赋值,指明引用的对象。
Struct mystruct ms
Wave ms.fred
Nvar ms.globvar1
Svar ms.globstr1
Funcref mydefaultfunc anotherfunc
这里假定当前文件夹下存在fred wave、globvar1和globstr1全局变量。注意为引用赋值时和在函数中声明引用的方法完全一样,这里不能省略wave、nvar和svar关键字。如不能写成如下形式:
ms.fred=fred
也不能写成如下形式:
Wave w=fred
Ms.fred=w
关于引用的详细介绍请参看5.3.6、5.3.7、5.3.8节内容。结构体变量在函数中传递时是按照引用传递的,因此被调用函数可以修改调用函数中结构体变量的值,这使得结构体变量既可以作为函数的输入参数,也可以作为函数的输出参数。当使用结构体变量作为参数时,语法如下:
Function subfunc(s)
Struct structname &s
…
End
这里使用了&符号获取结构体变量的引用。Igor内置了一些预定义好的结构体类型,如Button控件对应的WMButtonAction结构体类型,CheckBox控件对应的WMCheckBoxAction结构体类型等。这些结构体里包含了关于该控件的各种状态和运行信息,以方便对控件进行控制和对控件的状态作出反应。关于控件结构体类型将在第7章图形用户界面程序的程序设计中详细介绍。5.2.4流程控制语句流程控制语句包括条件判断语句、分支语句和循环语句。1. 条件判断语句条件判断语句有两种形式:
if (expression)
True Part
else
False Part
endif
和
if (expression)
True Part1
elseif
True Part2
else
False Part
endif
其中,expression是一个数值型表达式,非0表示真,0表示假。这里的数值型表达式可以是普通的数值型表达式,如a b,也可以是一个逻辑数值型表达式,如a==b。else可以省略,因此简单的条件判断语句为
if (expression)
True Part
endif
注意,不能忽略后面的Endif。Igor不使用大括号作为语句块的标识,而是使用End或者End作为前缀的关键字来作为一个语句块结束的标识,请读者注意体会这个特点。if语句可以嵌套,即if内部还可以包含if语句。下面是一个简单的条件判断的例子,其作用是比较两个数的大小:
Function example()
Variable a=2,b=1
if(a b)
Print a
else
Print b
endif
end
表52列出了常见的比较运算符。
表52常见的比较运算符
运算符含义运算符含义==相等<=小于或等于!=不等>大于<小于>=大于或等于当比较结果为真时返回1,否则返回0,如Print 2==2结果为1,Print 23结果为0。比较字符串的大小时需要使用相应的字符串函数,如CmpStr。CmpStr会根据字符串大小返回一个数字。总而言之,只要是运算结果为数字的都可以充当条件判断表达式,甚至是一个常量,如if(1)这样的语句也是可以的。表53列出了常见的逻辑运算符和位运算符。
表53逻辑运算和位运算符
运算符含义运算符含义~按位取反!逻辑取反&按位与&&逻辑与|按位或||逻辑或
逻辑运算符用于连接多个条件判断表达式。位运算符则常用于对比特变量按位进行设置。一些命令或者函数如TraceNameList使用比特变量作为参数,使用位运算符可以方便地对这些比特参数进行置位或取消置位。运算符之间存在优先级,圆括号可以改变优先级。为避免出错,建议读者在使用运算符时多使用圆括号,而不要依赖于对优先级的记忆,这样会避免很多错误。2. 分支语句分支(switch)语句也有两种形式:
switch(numerical expression)
case literalnumber constant:
code
[break]
case literalconstant:
code
[break]
[default:
code]
Endswitch
和
strswitch(string expression)
case literalstring constant:
code
[break]
case literalstring constant:
code
[break]
[default:
code]
Endswitch
不同于C语言,switch语句有两种格式,分别对应数值型和字符串型。switch根据numberical expression或者string expression的值选择执行相应case分支对应的代码,如果有break关键字,则执行完后跳出swtich语句,否则顺序执行其后的case的代码,直至碰到break或者到结束为止。default如果存在且没有任何case被对应,则执行default语句后面的代码。注意case标识符后面只能是一个具体的数字(双引号括起来的字符串)或者是一个数值型常量(字符串型常量),而不能是变量。当判断分支很多,用if语句比较麻烦时,就可以选用switch语句,如使用window hook函数侦测键盘输入,用switch语句判断键码就非常方便。下面是一个switch语句的例子,其作用是输出两个数中较大的一个:
switch(ab)
case 1:
Print a
break
case 0:
Print b
break
default:
break
endswitch
switch语句不能用于proc或者maro形式的程序中。3. 循环语句循环语句有两种形式,分别是for循环和dowhile循环,其格式为
do
loop body
while(experssion)
和
for(initialize;continue test;update)
loop body
endfor
其中,expression是一个表达式,当表达式非0时执行循环,表达式为0时停止循环。for循环先初始化条件,然后每次循环后先更新循环变量,接着计算循环条件表达式并进行判断,如果判断结果为真则执行循环,否则跳出循环。下面举一个例子:
Function example()
Variable I=0,sum=0
do
sum =i
i =1
while(i=100)
Print sum
End
上面的例子用于计算从1到100的和,如果用for循环可写为
Function example()
Variable I,sum=0
for(i=0;i=100;i =1)
sum =i
endfor
Print sum
End
循环是可以嵌套的,但是对于一些计算量非常大的数据处理,应尽量避免将大量的计算放在内层的循环,否则会大大增加运算的时间。for循环只能用于Function程序类型,而dowhile循环则可以用于Function和Macro程序类型。这也是Function和Macro的区别之一。和C语言类似,在循环中可以使用break和continue语句用于跳出循环或者满足某些条件时不执行循环体而进入下一个循环。break和continue作用于包含它的近循环。如上面的例子用break可以写为
Function example()
Variable I=0,sum=0
for(;;)
sum =i
i =1
if(i100)
Break
endif
endfor
End
求1到100之间所有奇数的和例子用continue可以写为
Function example()
Variable i=0,sum=0
do
i =1
if(mod(i,2)==0)
continue
endif
sum =i
while(i=100)
Print sum
End
mod是一个系统内置函数,用于计算一个整数被另外一个整数除时的余数,这里用来计算i被2除时的余数,当余数为0时表示i是偶数,否则为奇数。5.2.5函数按照Igor规定的语法规则,将变量、命令、语句组合在一起,形成一个具有明确功能的代码块,就构成了函数。函数是Igor程序设计的功能单位,Igor程序就是由一个个函数构成的。一些命令和操作,如数据拟合、方程求解等,也需要使用由用户指定的函数。正如任何常见的编程语言,Igor的函数概念同样涉及函数声明、修饰符、函数名、参数类型及参数表、返回类型和返回值、函数体等。请看下面的例子:
Function myFun(a,b)
Variable a,b
Variable c
C=sqrt(a^2 b^2)
Return c
End
这里Function可以理解为修饰符,定义函数必须以Function关键字开头。除了Function之外,函数修饰符还包括Static、Threadsafe,分别用于声明静态函数和线程安全函数。静态函数请参看本书第5.3.2节,线程安全函数请参看本书第7.2.1节。myFun是函数名,可以任意定义,但是必须满足Igor的命名规则,即字母 数字 下画线(数字和下画线不能位于首位)的要求。a和b是参数表,并用圆括号括起来。参数表里不指明参数的类型(Igor Pro7里可以指明),而是紧跟函数后利用相应的关键字声明。注意,可以省略参数,即定义无参数函数,但是圆括号不能省略。参数类型包括数值型、字符串型、wave型、结构体类型等。c是在函数体内定义的一个数值变量,由于没有使用/G选项,因此c是一个局部变量。return语句返回一个值,在这里将c值作为返回值返回给调用myFun的函数。注意,函数可以没有显式return语句,但函数仍有返回值,返回的值及其类型取决Function关键字的选项参数。一个函数体内可以有多个return语句。当执行到return语句时函数会立即停止执行并返回return后面的值。函数的返回类型是由Function的选项指定的,Function选项如下:
Function [ /C /D /S /DF /WAVE ]方括号表示选项是可选参数,其含义为(1) /C,返回一个复数。(2) /D,返回一个双精度浮点型数值,默认返回类型。(3) /S,返回一个字符串。(4) /DF,返回一个文件夹引用。(5) /WAVE,返回一个wave引用。当返回类型指定以后,return语句后跟的返回值必须与返回类型相对应。例如返回类型是数值时,return后面只能跟数值型结果(不可以是复数),返回类型是字符串时,return后面只能跟字符串型结果。函数在定义后就可以在命令行中直接执行或者在其他函数中调用。在命令行中调用函数的例子如图518所示。
图518在命令行中调用自定义函数
在函数中调用函数的例子如图519所示。
图519在函数中调用函数
第2个例子定义了一个新的函数example,这个函数没有参数,在函数体内调用myFun并将返回值赋给变量v,然后利用print语句将v输出到历史命令行窗口。同样需要在命令行窗口中执行example()函数。注意不要忘记圆括号。再看下面的例子:
Function/S gettracename()
String s,s1
s=tracenamelist(“”,”;”,1)
s1=stringfromlist(0,s)
Return s
End
上面的例子返回当前Graph中包含的曲线中条曲线的名字。读者可以在命令行窗口中执行下面的命令查看上面的例子,结果如图520所示。
Make data1
Display data1
Print gettracename()
图520返回字符串的函数
Igor提供了大量的内置函数,如数学函数sin()、cos()等,其功能和用户自定义函数类似,甚至完全相同,但注意用户自定义函数可以当作命令直接执行,而系统内置函数则必须位于赋值语句的右边或者是有一个变量(可能是非显式的)能接收其返回值的位置。例如直接执行sin(1)会提示错误,而c=sin(1),print sin(1)等则是正确的表达式。此外,用户自定义函数必须在编译后才能使用。5.2.6程序子类型为了方便管理和使用,Igor支持对程序进行分类,方法是在程序声明后用冒号指明程序的类型:
Function functionname(parameter list):subtype
Window macroname(parameterlist):subtyp
Proc和Macro也可以指明子类型,但常见的都是Function和Window。注意,Window本质上是一个Macro。subtype是一个关键字,用于描述一个类型。当用subtype指明程序子类型后,编译器会记录这些类型,并在需要的场合列出这些程序供用户选择,如指定一个程序的子类型为GraphStyle,则当用户在修改图表外观时,外观设置对话框会自动列出该程序以供用户选择。注意,这些子类型关键字一般都是由Igor自动创建时加上去的,使用者一般不需要专门添加。当然如果需要,添加是可以的。手动添加和系统自动创建时添加的效果完全一样。表54列出了子类型关键字及其含义。
表54程序子类型及其含义
子类型含义适用程序类型Graph在【Windows】|【Graph Macros】下显示MacroGraphStyle在【Windows】|【Graph Macros】及其样式设置时显示MacroGraphMarquee在图表选择框(Marquee)中显示Macro/FunctionTable在【Windows】|【Table Macros】下显示MacroTableStyle在【Windows】|【Table Macros】及其样式设置时显示MacroLayout在【Windows】|【Layout Macros】下显示MacroLayoutStyle在【Windows】|【Layout Macros】及其样式设置时显示MacroListBoxControl给Listbox控件指定回调函数时显示Macro/Function
续表
子类型含义适用程序类型
Panel在【Windows】|【Panel Macros】下显示MacroFitFunc在选择拟合函数时显示FunctionButtonControl给按钮控件指定回调函数时显示Macro/FunctionCheckBoxControl给复选框控件指定回调函数时显示Macro/FunctionPopupMenuControl给弹出式菜单控件指定回调函数时显示Macro/FunctionSetVariableControl给文本框控件指定回调函数时显示Macro/FunctionGraphMarquee关键字提供了一种非常好的用户交互实现方法: 只需要给函数指明graphmarquee子类型,该函数就会出现在【Graphmarquee】菜单中。看下面的例子,结果如图521所示。
Function mymarqueefun():graphmarquee
getmarquee
Print V_left,V_right,V_top,V_bottom
End
Make/O data=x
Display data
图521利用GraphMarquee关键字将函数添加到【Graphmarquee】菜单
当然还有给【Graphmarquee】菜单添加菜单项的其他方法,但是这种方法是简单的。5.2.7参数传递Igor中,函数参数传递有两种方式: 按值传递和按引用传递。按值传递表示调用函数将变量的值传递给被调用函数,按引用传递表示调用函数将变量的引用传递给被调用函数。这类似于C语言中的传递值和传递地址,前者不能修改原变量的值,后者能修改原变量的值。来看一个按值传递的例子:
Function mainFunc()
Variable v=1
String s=”hello”
subroutine(v,s)
Print v,s
End
Function subroutine(v,s)
Variable v
String s
Print v,s
v=2
s=”hello,IGOR”
End
在上面的例子中,函数mainFunc调用subroutine,将变量v和s按值传递给subroutine的两个参数v和s,subroutine打印v和s并修改了v和s,由于是按值传递,subroutine中的修改并不会影响mainFunc中的v和s。在这里,v和s是各自函数的局域变量,尽管名字相同,但是二者没有任何关系,读者可以将subroutine的参数起一个不同的名字,效果完全一样。接下来看一个按引用传递的例子。
Function mainFunc()
Variable v=1
String s=”hello”
subroutine(v,2,s)
Print s,v
End
Function subroutine(num1.num2,s)
Variable &num1,num2
String &s
num1=num1 num2
S=”The sum of the two num is”
Print s,num1
End
图522按引用传递的结果
在上面的例子中,函数subroutine有3个参数,其中第1个参数和第3个参数按照引用传递,由于subroutine获取的是被调用函数的变量的引用(地址),所以可以修改被调用函数中变量的值。在命令行中执行mainFunc(),结果如图522所示。如果是按值传递,则应该输出
Hello 1
The sum of the two num is 3
传递引用的语法格式非常简单,只需在声明函数参数时名称前面添加“&”符号即可,和C语言中引用的声明方式一样。结构体变量在作为参数传递时同样适用这种声明方式。对于使用引用变量作为参数的函数,在调用时还可以将引用变量作为参数,如
Function f1(v)
Variable &v
f2(v)//v是一个变量的引用,传递给f2
End
Function f2(v)
Variable &v
End
请注意,除了用于函数参数之外,不能直接使用“&”符号定义一个变量的引用,如下面的语句是错误的,这点和C语言不一样。另外,也不能将全局变量作为引用传递。
Variable v1
Variable &v=v1
引用的主要目的是一次返回多个函数值。一般而言,同全局变量一样,除非必要,不要使用引用传递参数,否则会增加程序之间的耦合程度,增大由于对某一函数中变量的修改而导致错误出现的概率,通常这些错误难以预测。如果函数的参数是wave,则传递方式全部为按引用传递,这和C语言完全类似。看下面的例子:
Function routine()
Make/O wave0=x
subroutine(wave0)
End
Function subroutine(w)
Wave w
W=1234
End
上面的例子中,传递给subroutine的参数是wave0的引用,而非wave0里的值。因此在subroutine里对w修改就是对wave0的修改。引用实际上也是一个变量。因此对于wave作为参数的情况,“引用变量”本身是按值传递,而wave则是按引用传递。在subroutine中对引用变量本身的修改不会影响调用函数中引用变量。如在subroutine中执行
Waveclear w
原函数中对wave0的引用(就是wave0本身)并不会被清除,请读者注意体会。5.2.8默认参数很多编程语言支持默认参数。默认参数有两个好处: 支持多态性,可以通过提供不同的参数调用同一个函数,方便记忆; 简化代码,开发者无须为相同的功能编写重复的代码。Igor支持默认参数,定义如下:
Function f(p1,p2,[p3,vout,s1,w1])
Variable p1,p2
Variable p3
Variable &vout
String s1
Wave w1
End
这里p3、vout、s1、w1都是默认参数。默认参数需要用中括号括起来,可以是任意的数据类型,包括结构体类型,还可以是变量的引用。在调用带有默认参数的函数时,使用“变量名=值”的方式提供默认参数值,变量名就是定义中使用的名字。可以只提供部分默认参数。如上面的函数可以按照下面的方式调用:
f(1,2)
f(1,2,p3=3)
f(1,2,vout=v) //v是一个数值变量
f(1,2,p3=3,s1=s1) //s1是一个字符串变量
f(1,2,w1=w) //w是一个wave引用
如果不提供默认参数值,数值型变量默认取0。字符串变量为null字符串。注意strlen函数以null字符串作为参数时返回null而非0。wave引用默认为null wave,即不指向任何wave。在程序中可以使用ParamIsDefault函数判断默认参数是否提供,如果没有提供可以在程序中提供合理的默认值。ParamIsDefault以默认参数名为参数,当默认参数没有提供时,ParamIsDefault返回非0值,否则返回0。
if(paramisdefault(p1))
p1=1
endif
看下面的例子:
Function maxvalue(a,b,[c])
Variable a,b,c
if(paramisdefault(c))
Return max(a,b)
else
return max(max(a,b),c)
endif
End
在命令行窗口输入:
Print maxvalue(1,2)
结果如图523所示。
图523默认参数函数调用示例
输入:
Print maxvalue(1,2,c=3)//注意变量名c不能省略
结果如图524所示。
图524默认参数函数调用示例
5.2.9注释和代码风格良好的注释有助于程序的可读性,有助于理解程序代码,也有助于对程序的升级和维护。注释在程序调试中也很有用。Igor下的程序注释类似于C语言,使用“//”作为注释符号,跟在“//”后面同一行的内容将视作注释内容。不同于C语言里的“/*…*/”注释符号,Igor里没有能注释多行的注释符,需要注释多行时可以在每一行行首打入“//”符号。Igor提供了一个快捷的多行注释的方法,选择要注释的多行代码,然后执行菜单命令【Edit】|【Commentize】可以自动在每一行行首添加注释符,从而实现一次注释多行代码。如果同时取消多行注释可以执行菜单命令【Edit】|【Decommentize】。注释一定要有意义,不要为了注释而注释,比如下面的注释就完全没有必要:
//计算a b
c=a b
一般应该在关键函数(如通用性非常高的函数)前面添加对函数功能的描述性注释,对函数参数的含义进行注释,如
//这个函数用来拟合×××格式的数据
Function lorf(x,cw):Fitfunc
Variable x//待拟合数据的x坐标值
Wave cw //拟合参数(初始参数和拟合结果都存放在这个wave里)
//cw[0]: 拟合参数说明
//cw[1]: 拟合参数说明
…
function body
End
良好的代码风格能使程序结构清晰,提高程序可读性,避免出错。特别是对于语句的多层嵌套,恰当的缩进显得尤为必要。在编写程序时应该有意识的使用缩进,如if语句可以缩进为如下格式:
if(ab)
Num_max=a
else
Num_max=b
endif
if里语句块的内容相对于if关键字缩进一个制表符,这样程序看起来结构清晰,层次分明。【Edit】|【Adjust Indentation】可以自动调整缩进,方法是先选择要调整缩进的内容,然后执行该菜单命令。5.3程序设计技术前面详细介绍了Igor下程序设计的基本方法,利用这些知识已经可以编写具有实用性的程序。但是要使得程序功能强大,应用范围更广,则必须利用Igor提供的其他编程特性和技术。这些技术有些为Igor所特有,有些则借鉴了其他优秀编程语言的特点。掌握这些技术是掌握Igor程序设计的必需步骤,和掌握基本语法一样重要。建议读者仔细阅读,并在实践中加以应用。5.3.1Include指令Include指令用于将另外一个程序文件包含在当前程序文件之中。通常将具有类似功能或者通用功能的一组程序存放于一个程序文件之中,并将之另存为一个后缀名为ipf的程序文件。在后续的程序开发中,当需要已经编写好的程序时,就可以通过Include指令直接将包含该程序的文件包含进来,而不需要重新编写相同的代码。通过这种方法也可以很方便地使用他人已经写好的程序。Include指令是Igor下程序设计常使用的编译器指令,必须熟练掌握。Include指令的语法格式非常简单:
#Include procedurefilename
#Include “procedurefilename”
“#”号是必需的,且必须位于行首。#Include可以位于程序文件的任何地方,但是一般都位于程序文件的开头位置。procedurefilename是要被包含的程序文件名,该程序文件必须以ipf作为后缀名,但注意procedurefilename不包括“.ipf”,如被包含的程序文件全名为somename.ipf,则procedurefilename应为somename而不是somename.ipf。编译器会自动打开Include指令所指定的文件,一般可以在【Windows】|【Procedure Windows】里查看已经打开的程序文件。如果取消了Include指令,则被打开的程序文件将会在下一次编译的时候自动关闭。尖括号、引号、procedurefilename的具体形式表示了Include指令获取程序文件位置的4种情况: 1. #Include filename这条指令用于包含一个名为filename.ipf的程序文件,该程序文件必须位于Igor Folder/WaveMetrics Procedures及其子文件夹下。这里“Igor Folder”指Igor安装目录(下同)。如没有专门强调,下面说的文件夹都是相对于Igor安装目录的。WaveMetrics Procedures文件夹位于Igor安装目录下,里面存放了大量由WaveMetrics提供的ipf程序。每个程序文件里都包含了一系列具有实用价值的的通用函数,程序文件名字描述了这些函数的大致功能,如Image Common.ipf里面包含了大量关于图像处理的通用函数,这些函数可以获取、创建和删除图像等。这些程序文件是在Igor下进行程序设计和开发的范例。当要写一个关于图像处理的程序时,就可以通过下述指令将Image Common.ipf包含进来,这样就不用再重复编写一些通用的函数代码,指令如下:
#Include Image Common
2. #lnclude “filename”和种情况相比较,这里尖括号变成了双引号,用于包含一个名为filename.ipf的程序文件,该程序文件必须位于User Procedures目录或者Igor User Files/User Procedures目录及其子目录下。Igor User Files/User Procedures位于我的文档下,是Igor在安装时自动创建的一个文件夹。可以通过菜单命令【Help】|【Show Igor User Files】打开该文件夹。User Procedures和Igor User Files/User Procedures专门用于存放由用户开发的程序文件。到底存放在哪个目录下,则看个人习惯,推荐存放在Igor User Files/User Procedures中。当然不管存放于何处,对程序文件的备份都是必需的。假如要包含的程序文件名为proc0.ipf,则包含指令为
#Include “proc0″3. Include “full file path”前面两种情况要求被包含的程序文件必须位于特定的目录下,这并不是必需的。可以用双引号指定程序文件的完整路径,此时程序文件可以位于任何地方。例如,程序文件位于E盘下的tmp文件夹中,文件名为myproc.ipf,则包含指令可以为
#Include “E:tmp:myproc”
注意,这里文件路径格式是Igor下的文件路径格式,也可以使用E:\tmp\myproc这样的格式。4. Include “patial file name”除了指定程序文件的路径,还可以指定程序文件的相对路径,此时Igor首先从User Procedures文件夹中寻找,如果失败再从Igor User Files/User Procedures文件夹中寻找,如果再失败则从当前使用#Include指令的程序文件所在的文件夹中寻找。假如当前程序文件位于E:\tmp\myproc.ipf,要包含的程序文件位于E:\tmp\categary1\proc1.ipf,则包含指令为
#Include “:categary1:proc1”
如果要包含的程序文件位于E:\tmp\proc1.ipf,即和主程序文件myproc.ipf位于同一个目录下,则包含指令为
#Include”:proc1″
这里冒号不能省略,否则会导致错误。常用的Include方法是第2种,第3种和第4种。由于包含路径信息,当程序文件的路径信息不小心被改变(如程序从一台计算机复制到另外一台计算机)时就会出现找不到文件而无法编译的错误,这种情况是非常容易出现的。因此除非必要,不建议读者采用后两种方法。使用第2种方法,只需要将程序文件放入User Procedures或Igor User Files/User Procedures文件夹中,就可以通过程序文件名方便地包含该文件。5.3.2Pragma参数在每一个程序编辑窗口前几行都有一个#Pragma的参数,如图525所示。
图525Pragma参数
这个参数的作用是设定编译器模式或者向编译器传递一些有用的信息。#Pragma参数一般位于程序文件之首,且#号不能与窗口左侧存在任何空格或者是制表符,其声明形式如下:
#Pragma keyword [ = parameters ]
目前,Igor支持以下6种Pragma参数(Igor Pro 6.37及以前):
#pragma rtGlobals = value
#pragma version = versionNumber
#pragma IgorVersion = versionNumber
#pragma ModuleName = name
#pragma hide=1
#pragma IndependentModule = name
#Pragmas参数只影响它所处的程序文件。下面介绍每一个参数的具体含义。1. rtGlobals参数rtGlobals参数用于指定编译器的行为和程序运行时对错误的处理方法。rtGlobals参数取值为数字0、1、2、3,其含义与Igor的发展有关。早期的Igor版本(早于Igor 3,本书Igor版本新于6.37),在函数中访问一个全局变量或者wave时,要求全局变量和wave必须在编译阶段就存在,否则无法编译。在Igor 3版本中引入了“运行时查找变量”的技术,即在编译阶段,编译器并不要求全局变量和wave存在,只是在运行时才将全局变量和与其引用(wave、nvar、svar)关联起来。在Igor 6.20以后又引入了对wave引用和wave长度的严格检查。rtGlobals不同取值就是反映了Igor这种版本演化的行为,其主要目的是实现不同版本之间的兼容性。
#pragma rtGlobals=0
这是Igor早期版本的编译器模式,不应该继续使用。
#pragma rtGlobals=1
打开“运行时查找变量”技术,使用此选项,编译器在编译时并不检查被引用的全局变量是否存在,一般是默认选项,本书中绝大多数例子都是在此模式下编写的。
#pragma rtGlobals=2
强制使实验文件处于“非兼容模式”,也是为了与旧版本Igor程序设计兼容的一个编译器选项,几乎很少使用,无须理会。
#pragma rtGlobals=3
打开“运行时查找变量”技术,使用严格wave引用,运行时检查wave的长度,越界访问报错。当#pragma rtGlobals=3时,对wave引用的检查非常严格,任何使用wave的场合都必须通过wave引用,换言之,要使用wave,该wave引用必须存在,否则编译器就会报错。如下面的例子:
Function test()
Display wave1
End
上面的代码在rtGlobals=3时不能通过编译,否则会出现图526所示的错误。
图526rtGlobals=3,wave必须声明引用后才能使用
这是由于wave1并没有事先声明。正确的做法是
Function test()
Wave wave1
Display wave1
End
即先声明wave1的引用,然后才能使用该wave。但是下面的情况编译时不会报错:
Function test()
Make wave1
Display wave1
End
这是因为Make命令在创建新wave时会自动创建wave引用,请读者参看本书第5.3.10节自动变量。当#pragma rtGlobals=1时,编译器对wave引用的检查是不严格的,除非是赋值语句,否则可以不声明wave的引用而直接使用wave,如下面的代码在#pragma rtGlobals=1是正确的:
Function test()
Display wave1
End
如果是赋值语句,则无论是rtGlobals=1还是rtGlobals=3,都必须声明wave的引用,如下:
Function test()
Wave wave1
Wave1=sin(x)
Display wave2// rtglobals=1时正确,rtglobals=3时错误
End
这里需要给wave1赋值,因此必须声明wave1引用,而wave2作为Display的参数,在rtGlobals=1时不需要声明wave引用,在rtGlobals=3时需要声明wave引用。rtGlobals=1和rtGlobals=3对wave越界访问的处理也不一样。rtGlobals=1时对wave的越界访问并不会报错,如
Make data1={1,2,3,4,5}
data1[6]=7
这里data1的长度为5,显然data1[6]并不存在,在C语言这会导致内存非法访问错误,产生不可预料的结果。但是在Igor下,上面的赋值语句会被自动截断为data1[4]=7,即当越界访问时,只访问wave的后一个元素。当然这不是的,如果指定了错误的Label,访问的是个元素,由于Label很少使用,这里就不做介绍了,感兴趣的读者可以查看SetDimLabel命令的帮助。如果rtGlobals=3,上述代码在运行时仍然会有截断赋值行为,但是会报一个越界访问的错误,以引起使用者关注。2. version参数version参数用于指明当前程序文件的版本号,如
#pragma version = 1.01
如果不指定则版本号默认为1.00。版本号应该在程序文件的开头指定。指明程序的版本号有助于程序的升级和维护,特别是当程序由多人开发时,版本号能方便程序的开发管理。Igor的Include指令会检查程序文件的版本号,如
#Include “a procedure file” version=1.03表示被打开的程序文件版本号应该不低于1.03,否则会显示一个警告信息,提示程序需要更新(但是程序还是会被正常包含和编译的)。3. IgorVersion参数IgorVersion参数用于指明程序在哪个版本的Igor下运行,如
#pragma IgorVersion=5.03
上述指令表示当前程序文件要求Igor的版本不低于5.03。Igor版本更新很快,每一个版本都会增加一些新的特性,其编程环境也会发生变化,如引入新的语法、函数等。旧版本Igor下的程序很有可能在新版本下无法通过编译,反之亦然。为了让程序正常运行,指定IgorVersion参数是必要的。4. ModuleName参数ModuleName参数类似于C 或是C#等编程语言中的名称空间,通过指定ModuleName参数,相当于给程序文件起了一个名字,可以通过该名字访问程序文件里的程序和变量。ModuleName的声明方式如下:
#pragma ModuleName= name
其中,name是一个合法的名字,注意不要和其他的ModuleName相冲突。当声明了一个程序文件的ModuleName参数以后,就可以通过该名字访问程序文件内的对象,方法是name#对象名,这里的#号类似于C 或者C#里的“.”运算符,如下:
#pragma ModuleName = myGreateProcedure
Static function foo(a)
Variable a
Return a 100
End
这时,可以在命令行窗口执行:
myGreateProcedure#foo(0)
使用ModuleName参数的主要作用是避免命名冲突。前面介绍函数声明时只介绍了Function关键字,其实Function还有一个修饰符static。通过static声明的函数是私有函数,只属于所处程序文件本身,只能在程序文件内使用,不能在程序文件外直接使用。如上面的例子,在命令行窗口直接输入foo(0)是无法运行的,Igor提示找不到该函数。要在程序文件外访问私有函数,必须通过ModuleName进行,格式为Modulename#函数名。不同ModuleName的程序文件中,私有函数是可以重名的,如图527所示。
图527ModuleName不同的程序窗口中,函数可以重名
这里ModuleName为a的程序窗口中有一个私有函数foo,在内置程序窗口(全局程序窗口)中有一个函数也是foo,由于Modulename为a的程序窗口中foo被声明为私有函数,所以它可以和内置程序窗口中的foo重名。在开发过程中,如果函数和已有的函数发生名字冲突时,就可以给程序文件指定一个独立的ModuleName,并将可能冲突的函数通过static声明为私有。这在多人开发或者项目比较大时非常有用。如果不使用static,则函数是公有的,此时所有程序窗口包括命令行都可以通过函数名访问该函数,这是本节以前所有例子的情况。所有的公有函数名字必须独一无二,即使是位于不同的程序文件夹中。这里的static应该借鉴OOP程序设计语言中静态成员的概念,静态表示该函数属于类本身,只能通过类(这里就是程序文件Module名字)访问。可以将不同的程序文件声明为相同的ModuleName,但是没有必要这样做,因为用不同ModuleName的目的就是为了避免命名冲突,而不是提供一个公共的名称空间。如果省略ModuleName,Igor会自动给程序指定一个ModuleName——ProcGlobal。换句话说,没有声明ModuleName的所有程序文件都位于ProcGlobal环境下。在ProcGlobal环境下,也可以使用static函数声明私有函数,但是该私有函数将无法在程序文件外部访问,只能在程序文件内部访问。读者可能想通过“ProcGlobal#私有函数名”访问,这也是不可以的。要从外部访问一个程序文件里的私有函数,必须给该程序文件声明一个ModuleName。后,请读者注意,除了函数之外,static还可以作用于constant、structure对象。一旦用static声明,这些对象即被所处程序文件私有,在程序文件外访问时只能通过ModuleName加“#”的格式访问。 5. hide参数#Pragma hide=1可以隐藏程序文件,此时在菜单【Windows】|【Procedure Windows】下看不到对应的程序文件。6. IndependentModule参数IndependentModule参数使用比较复杂,也很有用,请看下一节5.3.3。5.3.3IndependentModuleIndependentModule参数用于将程序文件设置为独立模块(IndepentModule)。被指定为IndependentModule的程序文件,里面的代码将会单独编译。此时即使其他普通程序文件里的代码没有编译(如发生语法错误),独立模块中的代码仍然处于已编译状态,可以运行。利用独立模块可以编写需要在任何时候运行的代码,如数据采集程序、高通用性程序等。将一个程序文件指定为独立模块的方式非常简单:
#pragma IndependentModule = imName
注意,内置的程序窗口不允许声明为独立模块,即内置程序窗口不能被指定为独立模块。下面看一个独立模块的简单例子: 通过菜单命令【Windows】|【New】|【Procedures】创建一个程序文件,在程序文件的开头输入独立模块预编译指令,如图528所示。
#pragma IndependentModule=myIM
图528将程序窗口声明为独立模块
在程序窗口里输入以下代码:
Function test()
Print “this function executes under an Independent Module”
End
在命令行窗口执行指令,如图529所示。
图529执行独立模块中的函数
按Ctrl M键,打开内置程序窗口,人为制造一个错误,使程序无法编译,如图530所示。
图530人为制造编译错误,独立模块仍正常编译
单击【Quit Compile】取消编译,然后在命令行窗口输入以下命令:
myIM#test()
可以发现尽管有程序没有编译成功,但是myIM中的test函数仍然可以正常运行。这说明myIM模块中的代码和其他正常模块中的代码是独立编译的。下面对独立模块程序设计进行更加详细的介绍。1. 独立模块中的程序类型独立模块中只能定义函数,不能定义Macro和Proc类型的程序,但是可以定义Window类型,也就是说可以在独立模块中放置窗口生成代码(Window Recreation Macro)。2. 函数定义和访问独立模块中的函数同样可以定义为公有的或者私有的。公有函数的定义方法为直接使用Function关键字定义函数,私有函数则在Function前面加static关键字。如下:
#pragma IndependentModule=myIM
Function test()
f1()
End
static Function f1()
print “Independent Module”
End独立模块的函数的访问层次比普通模块低一个级别。访问公有函数需要在该函数名字前面添加独立模块名作为前缀,如myIM#test()。私有函数外界完全无法访问,只能由独立模块内部的代码访问:
myIM#test() //可以执行
myIM#f1() //不能执行
3. 程序文件不可视
图531使Independent Module重新出现在
【Procedure Window】菜单里
独立模块的程序文件默认不可见,在【Windows】|【Procedure Windows】菜单里是看不到独立模块对应的程序文件的。因此独立模块的程序在编译并选择隐藏后,是无法通过【Windows】|【Procedure Windows】找到的,只能通过诸如查找、函数帮助、【Go To】命令等方式定位并重新打开程序文件。如果程序处于开发阶段,这种特性会带来不便,可以通过以下命令突破这个限制,结果如图531所示。
SetIgorOption IndependentModuleDev=1
如图531所示,myIM独立模块已经出现在【Procedure Window】列表里。注意独立模块的命名方式,在程序里访问独立模块时必须按照这个格式去访问:
文件名[独立模块名]//文件名和模块名之间的空格不能省略
独立模块程序文件被隐藏起来是合理的,这样可以防止被误修改。因此在开发阶段可以使用SetIgorOption IndependentModuleDev=1使程序文件可见,而在开发完后则使用SetIgorOption IndependentModuleDev=0隐藏独立模块程序文件。4. 文件包含在独立模块里可以使用Include指令包含其他的程序文件,被包含进来的程序文件自动转换为独立模块,Igor会在每个包含进来的程序文件头添加#pragma IndependentModule指令并自动将名字指定为该独立模块名字。如独立模块为myIM,则被包含的程序文件开头会自动添加如下的预编译指令:
#pragma IndependentModule=myIM
这样就可以自由访问被包含文件里的函数了。注意,被包含进来的程序文件为一份临时复制文件,对这些程序文件的任何修改都不会被保存。因此不要对这些程序文件做任何修改,如果做了修改,一定要以别的方式将这些内容保存到原程序文件。独立模块不能包含独立模块程序文件,除非该程序文件具有相同的独立模块名字。5. 窗口程序在独立模块里同样可以设计窗口程序,但是要注意窗口程序所有的代码都必须位于独立模块内,不要调用独立模块外的自定义程序。关于窗口程序设计请参看第6章。6. 菜单在独立模块里创建菜单时,如果菜单项对应的函数位于独立模块,好在函数前面添加独立模块名作为前缀。如果是给弹出式菜单设置菜单项,且菜单项由独立模块里的一个函数返回,则必须在该函数名前加独立模块名前缀,如图532所示。
图532独立模块中菜单的设计
完整的程序代码如下:
#pragma rtGlobals=3
#pragma IndependentModule=myIM
menu “macros”//创建两个菜单
“my menu item”, myIM#f1()//指定”my menu item”对应的函数
“create window”,myIM#f2()//指定”create window”对应的函数
End
Function f1()
Print 1
End
Function f2()
execute “panel0()”
End
Window Panel0() : Panel
PauseUpdate; Silent 1
NewPanel /W=(150,77,412,164)
PopupMenu popup0,pos={21,17},size={100,20},bodyWidth=100
PopupMenu popup0,mode=1,popvalue=”Yes”
PopupMenu popup0,value= #GetIndependentModuleName() “#popuplist()”
EndMacro
Function/S popuplist()//返回Panel0中下拉菜单popupo中的菜单项
return “items;item2”
End
上面的代码在独立模块里给【Macros】添加了两个菜单项,个菜单项打印数字1,第二个菜单项能打开一个窗口,该窗口包含一个弹出式菜单控件,控件的菜单项由value关键字指定,函数popuplist返回一个字符串列表给value关键字,以设置菜单项。注意,在popuplist前添加了GetIndependentModuleName()函数以获取独立模块名,因此后的调用是
myIM#popuplist()
5.3.4Execute命令有些命令不能在函数里执行,如函数里不能直接调用Proc或者Macro程序。如果要在函数里调用Proc和Macro,需要使用Execute命令。如下:
Function test()
Execute “f1()”
//由于f1()是一个proc,在函数中无法直接调用
End
Proc f1()
Print 1
End
上面的例子中,f1是一个Proc类型的程序,因此不能直接在函数里调用,但是可以通过Execute来执行。窗口生成脚本一般都不是Function,如果在函数里直接调用就会出错,此时可以使用Execute命令。有人会认为将脚本修改为Function就可以了,这样做没问题,但是会给以后修改窗口带来麻烦。因为生成脚本一般都是由Igor自动生成的,如果修改了生成脚本的名字,如将Window关键字改成Function,Igor在更新脚本时就会因为找不到该脚本,而将更新变成了重新生成。Execute后面还可以跟一个字符串,字符串里是待执行的命令,这个命令可以是一个函数、一个脚本,或者一个可以执行的任意合法表达式:
String cmd
sprintf cmd, “GBLoadWave/P=%s/S=%d \”%s\””, pathName, skipCount, fileName
Execute cmd
Execute有一个/P参数非常有用,它表示在没有任何程序或者函数运行时,再运行其后字符串指定的命令。一些命令在函数运行时没有任何效果,如Dowindow/R在函数运行时不能自动创建或者更新生成脚本,如果想要通过编程控制更新生成脚本,可以按照如下方式:
Execute/P “dowindow/R PanelName”
利用Execute/P还可以自动包含程序文件:
Execute/P “INSERTINCLUDE Peak Functions”
利用这个特性,可以让程序变得更加智能,如自动检查是否需要某些功能,并智能加载包含这些功能的程序文件。5.3.5条件编译Igor编译器支持条件编译,通过设定编译条件可以有选择地编译某些代码段。在编译和调试阶段利用条件编译可以将正常代码和调试代码区分开来,方便程序开发。条件编译的语法格式和说明如下: (1) 格式1:
#define symbol
#undef symbol
#define用以定义一个符号标记,#undef可取消一个符号标记定义。
#ifdef symbol
code1
#else
code2
#endif
如果前面利用#define定义了symbol,则编译代码段code1,否则编译代码段code2。#else可以省略。(2) 格式2:
#if expression
code1
#else
code2
#endifexpression是一个合法的表达式,但不能包括用户自定义的任何函数或者变量(因为这些函数和变量还没有编译),当expression计算结果值大于0.5(小于0.5,四舍五入为0,相当于假)则编译代码段code1,否则编译代码段code2。#else可以省略。(3) 格式3:
#if expression1
code1
#elif expresion2
code2
#else
code3
#endif
可以利用#elif关键字引入多种条件的判断,注意这里的关键字是#elif而不是#elseif。
(4) 格式4:
#ifndef symbol
code1
#else
code2
#endif
和#ifdef刚好相反。注意,#define关键字的作用仅仅是用来声明一个用于条件编译的符号,除此之外没有任何含义,#define并不是宏定义,Igor里也没有宏展开的概念。这和C语言完全不一样,在C语言中利用#define预定义的符号可以当作一个常量使用,Igor里是不能的。一般一个程序文件里的符号标记仅在这个程序文件里有效,但是在主程序文件里声明的符号标记所有程序文件都可见。如果一个程序文件通过#include指令包含了一个新的程序文件,则这个程序文件对于被包含的文件就是主程序文件。不同于#pragma关键字,用于条件编译的符号“#”可以有缩进而无须紧靠程序窗口左侧。在Igor中预定了一些全局符号,这些符号不需要使用#define定义就可以使用,如表55所示。
表55Igor预定义的全局符号(#define)
符 号 标 记含义MACINTOSHIgor运行于苹果系统环境下WINDOWSIgor运行于Windows环境下IGOR64Igor是64位版本来看下面的例子:
#ifdef WINDOWS
Function f()
Print “I am running on windows.”
End
#endif
#ifdef MACINTOSH
Function f()
Print “I am running on Mac OS.”
End
#endif
如果在Windows操作系统下运行Igor,并执行函数f(),则会输出
I am running on windows.
5.3.6函数引用在C语言中,可以定义指向函数的指针,通过将函数名赋值给指针变量就可以利用指针变量访问函数了。函数指针的概念使得用户在设计程序时不需要知道要执行的程序是什么,到运行时再将实际的程序指针传递给程序。如Windows程序设计里窗口过程函数就是典型的例子: 窗口类维护一个指向窗口过程的函数指针,用户在创建窗口时将自定义的窗口过程指定给该指针。这给程序的设计带来了极大的灵活性。Igor同样支持这种程序设计特性。在Igor中可以定义函数的引用,函数引用就类似于C语言中的函数指针。可以将一个函数名赋值给函数引用,之后就可以通过函数引用调用被引用的函数。声明一个函数引用的语法如下:
Funcref myprotofunc f=functionname
Funcref是关键字,f是函数引用的名字。myprotofunc是一个用户自定义的模板函数,有两个含义: 限定函数所能引用的函数类型,被引用函数的返回类型和参数必须与模板函数匹配,这和C语言类似,C语言里声明函数指针时同样需要指定返回类型和参数表; 当f没有指向一个合法的函数时系统默认调用的函数,如f引用的函数不存在时系统就会调用myprotofunc函数。functionname是所要引用的函数名字。看下面的例子:
Function f0()
Print “Naive!”
End
Function f1()
Print “You get it!”
End
Function test(fref)
Funcref f0 fref
Function f0 fref1=f1
Function f0 fref2=$”somefunc”
Function f0 fref3=fref
Fref1()
Fref2()
Fref3()
End
上面的例子中,f0就是myprotofunc,它表示被引用的函数必须没有参数且返回一个数值。对函数引用赋值有3种方式: 将一个函数名赋值给函数引用; 利用“$”运算符解析一个字符串变量,将其内容作为函数名赋值给函数引用; 将另外一个函数引用赋值给函数引用。fref1引用了f1,因此fref1()就相当于调用f1()。 fref2引用了一个名为somefunc的函数,这个函数是不存在的,因此系统会自动调用f0。fref3就是test函数参数传过来的函数引用,fref3()将执行该参数所引用的函数。在这里可看到函数引用也可作为函数的参数。在命令行里执行:
Test(f1)
输出结果如图533所示。再执行
Test($”aa”)
输出结果如图534所示。
图533Test(f1)输出结果
图534Test($”aa”)输出结果
请读者对照上面对函数引用的介绍分析输出结果。在上面的程序文件里再增加一个函数f3:
Function f3(v)
Variable v
Print “I am not the right one”
End
然后在函数test里添加:
Function test(fref)
Funcref f0 fref
Funcref f0 fref1=f1
Funcref f0 fref2=$”somefunc”
Funcref f0 fref3=fref
Funcref f0 fref4=f3//f3参数类型与模板函数不匹配,因此不能指定给f0类型的函数引用
Fref1()
Fref2()
Fref3()
End
当编译时,会出现如下错误,如图535所示。
图535函数引用要求被引用的函数和模板函数必须匹配
由于f3有一个参数,f0没有参数,因此不能使用f0作为模板声明f3的函数引用。注意,声明函数引用并赋值时必须采用下面的格式:
Funcref myprotofunc f=functionname
不能先声明后赋值,如下面的方法是错误的:
Funcref myprotofunc f
f=functionname
函数引用在程序设计中是非常有用的。在编写数据拟合程序时,如果将拟合公式和过程硬编码在程序里,程序的通用性是很低的,以后扩展和维护起来也很不方便,如果使用函数引用,只有在运行时,才将真正的拟合函数传递给程序会大大提高程序的灵活性。这样,只需要按照指定格式编写拟合函数,原程序不需要任何修改即可利用新的公式拟合。拟合函数可以通过诸如下拉菜单等选择,利用$符号将字符串转化为函数引用名。利用ProcedureText等函数,还可以自动获取拟合参数的信息(当然这需要拟合函数按照一定格式编写)。如此设计,程序会更加通用和简洁。在编写数据变换程序(如坐标变换)时,变换的公式往往随实验条件改变而改变,但是数据本身格式却相对固定,如源数据是两个坐标,目标数据亦是两个坐标,则可以在程序中使用函数引用,在具体的场合由用户选择或者按照指定格式编写相应的变换程序,同样可大大简化程序的结构,提高程序的通用性。函数引用程序设计可以使用这样的技巧: 如果模板函数的参数在编写程序时无法确定则可使用wave作为参数(当然也可以使用默认参数)。5.3.7访问全局对象Igor下能在函数中访问的全局对象包括全局数值型变量、全局字符串型变量、wave、函数、数据文件夹、符号路径、Graph窗口、Panel窗口、Notebook、程序窗口、坐标轴、控件等。除了全局变量和wave这些传统的数据对象之外,其他的对象都可以通过名字直接访问,也可以通过$符号访问(这种方式更灵活,请读者参看本书第5.3.9节)。对于wave和全局变量,Proc和Macro程序类型中可以通过名字直接访问,Function程序类型中不能通过名字直接访问。在Function中访问wave和全局变量,如果这些全局对象是在函数体内定义的,则可以直接访问; 如果是在函数体外定义的(如访问事先已有的数据),则需要先定义wave和变量的引用,然后才能访问。先看一个例子。在命令行窗口中定义一个全局数值型变量并赋值为1,如图536所示。
图536在命令行窗口创建一个全局变量
在程序编辑窗口输入以下函数:
Function myFun()
Print v
End
这里试图在函数里直接使用全局变量,但在编译程序时,Igor提示错误,如图537所示。
图537不能在函数里利用名字直接访问全局变量
这个错误提示要使用wave、nvar和svar关键字去访问全局wave、变量或是字符串。 因此对于本例,需要使用nvar创建对v的引用,如图538所示。此时程序就能正常编译运行,且能正常访问全局变量v。
图538创建对全局变量的引用
Igor使用下面3个关键字创建wave和全局变量的引用: (1) wave创建wave的引用。(2) nvar创建数值型变量的引用。(3) svar创建字符串型变量的引用。这3个关键字用于创建一个引用,编译器会将全局变量和该引用关联起来,在运行时函数就可以使用该引用去访问全局变量。注意,Igor下使用引用访问全局变量(wave),而不是通过名字去访问,不是为了使问题复杂化,恰恰相反,是为了使问题简单化。在程序设计时,并不知道有哪些数据,更不知道数据名字叫什么,因此根本无法通过名字访问数据。使用引用的技术可以先在程序里使用数据,而只有在运行时才将引用与真正的数据联接起来。预编译参数#Pragma rtGlobals就是用来控制编译器的这种行为的。在早的Igor版本中,创建全局变量(wave)的引用时,要求该变量(wave)必须存在,否则程序不能编译。#Pragma rtGlobals=1或者3表示在创建引用时,Igor并不检查与引用关联的变量是否存在,只有在运行时才将变量和引用真正联接起来(runtime lookup of globals)。1. nvar数值型变量引用nvar用于创建一个数值型变量引用,用于引用数值型全局变量,其语法格式为
NVAR [/C][/Z]localName[=pathToVar][,localName1 [=pathToVar1 ]]…
图539处于不同文件夹的
2个全局变量
其中,/C和/Z是可选参数,前者表示引用复数型全局变量,后者表示当被引用的全局变量不存在时不报错。localName表示引用名字,在函数中将使用该名字来访问全局变量。pathToVar是包含了路径的全局变量。nvar创建非当前数据文件夹下的全局变量的引用时,需要指明全局变量的路径。假定有以下2个全局变量,如图539所示,其中,v位于root文件夹,v2位于root: Folder1文件夹。当前文件夹是root,则创建v和v2引用的方法如下:
nvar v //正确,此时引用名就是v,能直接访问v
nvar num=v//正确,引用名为num,能直接访问v
nvar num=root:v //正确,引用名为num,指明v的完整路径,能直接访问v
nvar v2 //错误,由于当前文件是root,root下不存在v2这样的全局变量,不能访问v2
nvar num=root:Folder1:v2//正确,引用名为num,且指明了路径,可以访问v2
nvar num=:Folder1:v2 //正确,引用名为num,且指明了相对路径,可以访问v2
nvar v=v //正确,引用名和全局变量名相同,可以访问v
请读者注意,访问当前文件夹下的全局变量时,可以在nvar后直接跟全局变量名,就可以通过变量名访问该全局变量了,如上面行例子。创建复数类型全局变量的引用与创建普通数值型变量引用的方法完全相同,只需要使用/C标记即可。当编译选项#Pragma rtGlobals=1或者3时,编译器在编译时不会检查全局变量是否存在,只有在运行时才会检查全局变量是否存在并建立关联。2. svar字符串型变量引用svar用于创建一个字符串型引用,引用一个全局字符串变量,用法和nvar完全相同,本书不再赘述。3. wave引用wave关键字用于创建一个wave的引用,其语法格式为
WAVE [/C][/T][/Z] localName [=pathToWave][,localName1 [=pathToWave1 ]]…
(1) /C,创建一个复数型wave引用,如果被引用wave是复数类型时需要使用此选项。(2) /T,创建一个文本型wave引用,如果被引用wave是文本类型时需要使用此选项。(3) /Z,当被引用wave不存在时不报错(但使用时会报错)。
图540wave访问的示例
在函数中各种能产生wave的命令,除非使用/FREE选项,都会自动在当前或者指定的文件夹下创建一个真实的wave。这不同于普通编程语言(如C语言),数组在函数结束时即被释放。函数中创建的wave和命令行窗口中创建的wave一样,将永久存在。因此wave几乎总是全局型的,在访问wave时,必须用wave关键字创建一个引用。请看下面的例子,用到的wave如图540所示。
在函数中访问root下面的wave,必须按照如下方式声明引用:
wave w=wave1 //引用名为w
wave wave1 //引用名为wave1
wave w=root:wave1 //引用名为w,指明路径
wave w=:wave1 //引用名为w,指明相对路径
和nvar一样,如果wave位于当前文件夹,可以直接在wave关键字后跟wave的名字创建该wave的引用,此时可以通过wave名字访问wave。需要注意的是,一些命令在执行时会自动创建wave的引用,这时就不需要专门创建引用了。如Make wave1指令在创建wave1的同时自动创建了一个wave1的引用,该引用名就是wave1本身,其他的命令如Duplicate等也是如此。使用Proc和Macro关键字声明的代码段,可以直接访问全局变量而无须使用nvar、svar和wave声明引用。考虑下面的例子,假设当前文件夹为root,并存在一个名为wave1的wave,一个字符串变量str和一个数值型变量v,则下面的程序可以直接访问这些全局变量而无须创建引用(执行之前编译器仍然不会去检查变量是否存在)。
Make/O wave1
Variable v
string str
proc myProc()
wave1=sin(x)
v=1
str=”Hello,Igor”
End
Macro myMacro()
wave1=sin(x)
v=1
str=”Hello,Igor”
End
全局变量会增加函数之间的相互关联度,降低程序的通用性,应该在使用过程中加以避免。但是在有些场合全局变量也是必要的,如记忆程序的执行状态,记录实验数据信息,存放ListBox内容等都需要使用全局变量(或wave)。5.3.8wave引用wave是Igor下基本的概念,在Igor下编程必须熟练地掌握获取wave对象的各种技巧。第5.3.7介绍了如何通过wave关键字创建一个wave的引用,这是在函数中使用wave的一般方法。本节介绍一些特殊情况。1. 命令自动生成wave引用当执行Make、Duplicate或者是FFT等命令时,Igor编译器会自动创建wave引用,如
Make wave1
Duplicate wave1 wave2
FFT/dest=w wave1
第1个命令在当前文件夹下创建wave1,同时创建wave1的引用,这个引用名就是wave1,可以直接使用wave1访问刚创建的wave或者是做任意运算,如wave1=x*x。第2个命令中wave1的创建与引用与第1个命令中含义完全相同。第3个命令对wave1执行傅里叶变换,并创建一个新的名为w的wave,将变换后的结果保存在w中,同时使用w作为新创建wave的引用名。Igor下绝大多数能创建输出wave的命令都具有这个特性,新生成的wave可以直接使用。上面的情况也有例外,考虑下面的例子:
Function CreateaWave(namestrforwave)
String namestrforwave
Make $namestrforwave
wave w=$namestrforwave
w=x^2
End
本例中,使用Make命令创建一个wave,该wave的名字保存在一个字符串变量namestrforwave中,使用$运算符从该字符串解析出名字并作为Make参数创建该wave。此时并没有同时创建该wave的引用,因此需要利用wave关键字创建其引用。对于上面的例子还有一种更方便的方法:
Make $namestrforwave/wave=w
在Make命令中使用/wave选项,就可以直接利用引用名w访问刚创建的wave了。很多命令支持这种声明wave的方法,如下面的命令计算微分后,可以直接用wave1访问微分以后的结果:
Differentiate wave0 /D=$name/wave=wave1
上面的命令生成的结果wave名保存在name字符串变量中,在这里使用了$运算符创建目标wave。在创建wave引用时需要指明被引用wave的数值类型,如果不指明则默认为被引用的wave是一个双精度数值型wave。2. 返回wave引用的函数为了方便wave的访问,Igor提供了能直接返回wave引用的函数,这些函数功能都特别强大,用好这些函数访问数据会变得很简单。表56列出了部分返回wave引用的函数。
表56返回wave引用的函数
函数功能CsrWaveRef返回Graph中当前Cursor所处的Y wave引用CsrXWaveRef返回Graph中当前Cursor所处的X wave引用(XY型wave)WaveRefIndexed返回Graph或是Table或是当前文件夹下的wave引用XWaveRefFromTrace返回Graph中的X wave引用。需要指明对应曲线(trace)的名字,该名字通常可以通过TraceNameList函数获取ContourNameToWaveRef返回Graph中被显示为Contour模式的wave引用。需要提供Contour对应wave的名字,该名字可以通过ContourNameList函数获取
续表
函数功能
ImageNameToWaveRef返回Graph中被显示为Image模式的wave引用。需要提供Image对应wave的名字,该名字可以通过ImageNameList函数获取。TraceNameToWaveRef返回Graph中被显示为曲线模式的wave引用。需要提供Trace对应wave的名字,该名字可以通过TraceNameList获取TagWaveRef返回Graph中当前Tag所处的Y wave引用WaveRefsEqual比较两个wave的引用是否相同,是则返回1,否则返回0这些函数会返回一个wave的引用,可以赋值给由wave关键字创建的wave引用变量。这些函数绝大多数都以Graph作为操作对象,要被获取引用的wave显示在Graph窗口中。这样只需要通过Graph就能获取Graph中数据对象的引用,而无须知道名字和存放的路径,这会大大降低编程的复杂性并提高数据处理的效率。常用的返回wave引用的函数是CsrWaveRef、WaveRefIndexed、TraceNameToWaveRef和ImageNameToWaveRef。(1) CsrWaveRef函数的使用方法为
CsrWaveRef(cursorName[,graphNameStr])
courseName表示光标(按Ctrl I键设置),可以是A或者B。graphNameStr是图窗口的名字,为可选参数,如果提供则从graphNameStr指定的图窗口中获取引用,否则默认从当前顶层图窗口获取引用,如
wave w=Csrwaveref(A)
wave w=CsrWaveRef(A,”graph0″)
wave w=csrwaveref(A,namestr)
Print Csrwaveref(A)[0]
第1行返回当前顶层窗口Cursor A所在的wave引用,并赋值给w。第2行返回一个名为graph0的窗口中Cursor A所在的wave引用,并赋值给w。第3行返回一个名字存放在namestr中的Graph中Cursor A所在的wave引用,并赋值给w。第4行直接打印当前顶层窗口Cursor A所在的wave的个数据。在对曲线进行拟合时,当窗口中曲线较多,且需要使用Cursor指定拟合范围时,利用CsrWaveRef函数可以获取要被拟合的曲线的引用,十分方便。(2) WaveRefIndexed函数的调用方法为
WaveRefIndexed(windowNameStr, index, type)
windowNameStr是一个字符串变量,用于存放Graph或者Table的名字,如果是空字符串则表示当前顶层Graph或者顶层Table或者当前文件夹。index表示要返回wave所处的次序。type可取1~4,如果取值为4,windowNamestr应为空字符串,表示获取当前数据文件夹下的wave引用。当前窗口为Graph时,1表示获取Y wave引用,2表示获取X wave引用,3表示两者都获取,这也是常见的情况,如
wave w=WaveRefIndexed(“”,0,1)
上述命令返回当前Graph窗口中的条曲线的引用。一般一个窗口只显示一条曲线,当需要对该曲线进行拟合、运算等操作时,这条命令提供一个非常方便地获取该曲线引用的方法: 既不需要知道该曲线的名字,也不需要知道该曲线的存放路径。(3) TraceNametoWaveRef函数的调用方法为
TraceNameToWaveRef(graphNameStr, traceNameStr)
graphNameStr表示Graph窗口的名字,如果为空字符串表示当前顶层窗口,traceNameStr表示曲线wave的名字。可以利用TraceNameList获取Graph中所有曲线的名字列表,并将它作为TraceNameToWaveRef的参数,如
string list=TraceNameList(“”,”;”,1)
wave w=TraceNameToWaveRef(“”,StringFromList(0,list))
行通过TraceNameList函数获取当前顶层Graph中所有曲线的名字列表,以分号作为分隔符存放在字符串变量list中,第二行利用StringFromList取出list中的个字符串,然后将它作为TraceNametoWaveRef的参数,就可以获取该字符串对应曲线的wave引用。ImageNameToWaveRef函数和TraceNameToWaveRef函数的使用方法完全一样,表示获取Graph中一个Image的引用,不同的是其名字列表需要用ImageNameList获取。在Igor下,要访问wave,仅仅使用名字是不够的,还需要同时知道wave所处的数据文件夹,因此一般的方法是要么设置当前文件夹为wave所处文件夹,要么使用完整路径访问wave。利用上面介绍的返回wave引用的函数的优点是不需要知道目标wave的名字及其存放路径。在实际中经常还有这样的需求: 需要对Graph中显示的曲线或者Image进行操作。普通方法需要预先知道Graph或者Image中数据存放的位置,然后将这些信息硬编码到程序中。显然这样的程序是没有通用性的。利用上面的函数,就可以解决这个问题,只需要通过Graph就能直接得到目标曲线的引用。其实这也是很好理解的,Graph既然能显示一个wave,那么它必然包含这个wave的全部信息。细心的读者可能会问,有了wave的引用,能否获取wave的名字和它所在的路径呢?答案是可以的,只需要使用下面两个函数就能达到这个目的:
NameOfWave(wave)
GetWavesDataFolder(wave,kind)
个函数使用非常简单,直接返回wave的名字,注意其参数是一个wave引用,如下列命令打印wave w的名字:
Print NameOfWave(w)
第二个函数返回一个wave所处的文件夹,第1个参数是一个wave引用,第2个参数含义如表57所示。
表57GetWavesDataFolder函数的kind参数含义
kind含义
0返回包含wave的文件夹名1返回包含wave的完整路径,但不包括wave名2返回包含wave的完整路径,包括wave名看下面的例子,结果如图541所示。
Function myFunc()
Make/O data1
Setscale/I x,0,2*pi,data1
Display data1
wave w=waverefindexed(“”,0,1)
Print nameofwave(w)
Print getwavesdatafolder(w,2)
End
图541通过引用获取wave的名字和存放路径
一个被引用的wave不存在时,访问wave就会出错。当使用#Pragma rtGlobals=1时,编译器在编译一个类似于wave w=wave1的表达式时并不会检查wave1是否真正存在,只有在运行时才会将w和wave1真正联结起来。因此在使用wave引用之前应该检查wave是否存在,方法为
if(!waveexists(w))//wave w存在
do somethings
else //wave w不存在
coping with errors
endif
5.3.9$运算符引用是Igor中广泛应用的一个概念,前面看到可以利用nvar、svar和wave声明变量或者wave的引用。实际上除了变量和wave之外,还可以声明或者创建窗口(Window)、路径符号(Symbolic Path,这里指操作系统文件路径,非Igor下文件路径)和函数的引用。利用前面介绍的方法声明引用时,变量已经存在,如
nvar num=v
v是一个已经存在的变量,在编写代码时就知道。但在实际中,要访问的对象经常是变化的,或者说无法预知访问的对象是谁,比如需要将一批wave显示出来,而在编写程序时,并不知道这些wave的名字,只有在运行时才能获取它们的名字,这种情况下就无法按照上面的办法来创建引用。解决这个困难的方法是使用$运算符。利用$运算符可以将一个字符串或者字符串变量转化为一个引用,引用该字符串所代表的对象。使用$运算符无须担心引用的类型,Igor会自动根据上下文环境判断被引用的对象类型,这个对象可以是变量、wave、窗口、文件夹、路径符号或者函数。首先来看利用$运算符创建wave引用的例子。1. $创建wave引用
wave w=$”wave1″
假定当前文件夹下存在一个名为wave1的wave,则上面的命令就可以创建wave1的引用。使用$运算符作用于“wave1”字符串,并将它赋值给引用变量w,这样就创建了wave1的引用。可以简单地理解为$运算符会解析字符串里的内容,并按照字符串内容(这里就是wave的名字)创建该wave的引用。在这里也可以使用wave w=wave1来声明wave1的引用,请读者体会两者的区别。下面来看较为复杂一些的例子:
Function displaywaves()
String s,s1
s=WaveList(“*”,”;”,”DIMS:1″)
Variable i,n
N=ItemsInList(s)
Display
for(i=0;in;i =1)
s1=StringFromList(i,s)
AppendToGraph $s1
endfor
End
这个函数用来显示当前文件夹下所有的一维wave。首先使用WaveList获取所有一维wave的名字,以分号分隔并存放于变量s中,然后利用for循环取出每一个wave的名字存放在s1中,接着利用$运算符将s1转化为其内容对应wave的引用。显然,本例中事先无法得知要显示的wave有哪些,不能直接创建这些wave的引用,而只能在程序运行时通过$运算符动态生成。注意下面使用$的方法是错误的:
String s=”somewave”
$s=sin(x)
$运算符并不会直接创建引用,这是因为仅仅使用$运算符,编译器无法确定$运算符后面的字符串里内容所代表的对象类型。前面,通过wave关键字告诉编译器,$运算符解析出来的字符串代表一个wave。对于第二个例子,在AppendToGraph $s1命令中,编译器能够通过AppendToGraph确定这里的$s1代表一个wave的引用。类似的情况还有Display、CurveFit等需要使用一个wave作为参数的情况。对于上面的错误只要进行如下修改就可以正常使用:
String s=”somewave”
wave w=$s
w=sin(x)
2. $创建变量引用通过$创建变量引用和创建wave引用的例子完全类似,只需要将wave关键字替换为nvar和svar即可,但注意被创建引用的变量必须是全局变量。看下面的例子:
Variable var1=1
String str1=”Global string”
Function myFun()
String vref,sref
vref=”var1″
sref=”str1″
nvar v=$vref
svar sG=$sref
Print v,sg
End
执行上面的函数,结果如图542所示。
图542利用$运算符创建全局变量的引用
3. $运算符创建窗口引用可以利用$运算符创建窗口的引用。从前面的介绍知道可以通过名字直接访问窗口,但有时窗口的名字并不知道,而是动态生成的,并且保存在一个字符串里,此时就可以通过$运算符解析该字符串获取对窗口的引用。例如下面的函数判断指定名字的窗口是否存在,就使用了窗口引用的技巧。
Function GraphWindowExsts(s)
String s
Dowindow $s
if(V_Flag==0)
Return 0
else
Return 1
endif
End
在这个例子中,将Window的名字存放在字符串变量s中,然后利用$运算符解析出Window名字,由于$s作为Dowindow的参数,编译器能够确认$s代表一个Window引用,并根据s里存放的名字获取该Window。其他的命令如创建、修改或是需要指定Window名字的命令,都可以通过$运算符来指定Window的名字。如
Display/N=$graphNameStr $ywavename vs $xwavename
Cursor/W=$graphNameStr A,$ynamestr,0
个例子,选项/N指定了新创建的Graph的名字为graphNameStr字符串内容。第二个例子/W选项指明了Cursor命令执行时的目标Graph窗口,窗口名称包含在graphNameStr内,如果不用$运算符,则表示窗口名称为graphNameStr本身。请读者注意体会。4. $运算符的对其他全局对象的引用在程序设计中,其他的全局对象有记事本(Notebook)、控件(Controls)、坐标轴(Axis),函数(Function)、程序窗口(Procedure Window)、程序面板(Panel)、符号路径(Symbolic Path)等。Igor下有很多的命令和函数用于操作这些全局对象,一般通过名字来访问这些对象。和上面的窗口引用完全一样,如果这些名字事先无法确定,则可以利用一个字符串变量保存动态生成的对象名字,然后在对应的命令后利用$运算符 字符串变量的方式来通过引用方式访问对象。如一个设置图中坐标轴的程序,由于Graph的坐标轴不仅仅是left和bottom,还可能包括各种自定义坐标轴,所以程序里无法事先知道有哪些坐标轴,只能在程序运行时利用Igor提供的函数获取Graph里所有的坐标轴名字,然后通过$运算符转化为对坐标轴的引用:
String s1= AxisList(“”)//获取当前窗口所有坐标轴名字列表,保存在s1中
String s2=StringFromList(0,s1) //获取个坐标轴名字,保存在s2中
ModifyGraph log($s2)=1 //将s2对应的坐标轴设置为对数刻度
通过$引用函数的方法请参看第5.3.6节。通过$引用符号路径请参看第7.8节。5.3.10自动创建变量Igor中有很多命令在执行时会自动创建一些变量。如果是在命令行窗口或是Macro(Proc)中执行,则在当前文件夹下创建全局变量; 如果是在函数中执行,则创建局域变量。在函数中使用自动变量时不需要声明,可以直接访问。Dowindow命令用于检查一个窗口是否存在,存在则将一个变量V_flag设置为1,否则将V_flag设置为0。在命令行窗口执行下列命令:
Display
Dowindow/C myGraph
Dowindow myGraph
Print V_flag
图543执行命令前当前root文件夹
中没有V_flag变量
注意观察在执行命令之前当前文件夹中并没有V_flag这样的命令,如图543所示。执行完命令后可以看到Dowindow自动创建了一个变量V_flag,并且将其值设置为1。这里Display命令产生一个Graph窗口,Dowindow/C命令将其名字设置为myGraph,由于myGraph窗口处于显示状态,故Dowindow作用于myGraph窗口时会自动创建V_flag变量并设置为1。在程序中经常使用这个方法来判断一个窗口是否存在。执行下面的命令:
Dowindow somegraph
Print V_flag
输出结果为0。这是由于不存在名为somegraph这样的窗口,故V_flag被设置为0(注意,如果V_flag没有会自动创建)。函数中使用Dowindow的例子如下:
Function GraphWindowExsts(s)
String s
Dowindow $s
if(V_Flag==0)
Return 0
else
Return 1
endif
End
上面的函数用于测试一个窗口是否存在。窗口名字存放于字符串变量中作为参数传递给函数,通过$运算符获取窗口名字并由Dowindow判断是否存在。程序随后检查V_flag变量,如果V_flag等于0,则返回0,表示窗口不存在; 否则返回1,表示窗口存在。这里可以看到在使用V_flag之前并没有创建它,也没有在其他地方声明,也不是通过参数由调用环境传递而来,那么V_flag到底是从哪儿来的?答案是由编译器自动创建。编译器在编译上述代码时,当检测到存在Dowindow这样的命令时,就会自动创建V_Flag这样的变量,换句话说,是编译器创建了这个变量。其他的命令如ControlInfo、WaveStats、FindRoots等都会创建自动变量,这些变量一般都统一以“V_”这样的前缀开头,命令执行后可以直接使用。Igor对每个命令能创建的自动变量都有详细的说明,读者只需要在在线帮助系统中查询该命令就可以获知这些信息。除了创建自动变量,一些命令还会创建wave,如CurveFit命令会自动创建名为w_sigma的wave,如果是在函数中,其引用名亦为w_sigma。这个wave保存了每个拟合参数的标准偏差,可以无须声明就可直接使用。注意,无论是自动创建的变量还是wave,对其访问必须在对应的命令之后。如下面对自动创建变量的访问是错误的:
Function test()
Print V_flag
Dowindow aa
End
5.3.11调试程序编写程序重要,调试程序更重要。Igor下有两种调试程序的方法: (1) 使用Print命令。(2) 使用自带的调试器。 Print类似于C语言中的printf语句,通过向历史命令行窗口输出信息来调试程序中可能的错误。调试器则可以设置断点、实时观测变量值,甚至计算表达式的值,功能更加复杂、完善。使用Print方法进行调试非常简单,在可能出错的地方输出一些关键变量的值,通过审查这些变量值是否符合预期从而确定程序出错的原因。这里不再对Print调试方法做过多介绍,而是重点介绍调试器的使用方法。当需要调试程序时,首先需要使程序处于可调试状态,方法是右击程序编辑窗口,在弹出的快捷菜单中选择【Enable Debugger】或者执行菜单命令【Procedure】|【Enable Debugger】(注意只有程序编辑窗口处于选中状态【Procedure】菜单才会出现)。这类似于一些集成开发环境中以Debug模式运行程序。打开程序调试功能的菜单如图544所示。
图544打开程序调试功能
图545设置断点
Igor程序调试器支持单步执行,可以在任意一行代码插入断点。程序运行到断点会自动停止,同时弹出调试对话框,在对话框中可实时查看变量、wave的值,也可以计算任意合法表达式的值。当程序处于可调试状态时,在程序编辑窗口的左侧会出现一个空白的竖条,单击竖条会在单击位置出现一个红点,表示在红点对应的行设置了一个断点,再次单击红点可清除该断点,如图545所示。当程序运行到断点位置时停止执行,并弹出调试对话框,如图546所示。设置断点有点类似于Print方法,只是Print方法输出关键变量的值后继续执行,而设置断点之后,程序会在断点处停下来,此时就可以通过调试器观察当前函数所有变量的值,然后选择继续执行或者是终止执行,是单步执行还是正常执行等。
图546调试对话框
设置断点是程序调试基本的技能,但有时仅靠设置断点是不够的。设置断点要求对程序可能出错的地方有所估计,否则断点的设置具有一定的盲目性,有时难以找到程序的故障点或者出错的真正原因。例如,一些脆弱的程序经常在执行完后才显示某个命令出错,而这个命令什么时候出错,出错时其运行环境如何都不知道,这就很难准确地设置断点。当这个出错的命令位于一个循环之中或者是位于一个被多次调用的函数之中时,直接设置断点几乎不可能,因为无法确定在第几次循环或者在第几次被调用时出现错误。还有一种情况是如果函数中引用的对象不存在,如一个wave引用变量所引用的真实wave不存在,会跳出一个wave引用的错误,但是它并不提示到底是哪个wave引用错误,如果程序中引用的wave很多,就很难确定到底是哪个wave引用出错,从而也无法准确设置断点。针对这种情况Igor提供了另外两种会自动调出调试器的方法: 运行时错误调试; nvar、svar和wave引用错误调试。前者当程序运行出错时立即调出调试器,后者当引用的全局对象不存在时立即调出调试器。这两种方法结合断点几乎可以立即确定程序出错的位置,顺利完成程序调试。启动运行时错误调试的方法是右击程序编辑窗口,在弹出的快捷菜单中选择【Debug on Error】,也可以执行菜单命令【Procedure】|【Debug on Error】。启动nvar、svar和wave引用错误调试的方法是右击程序编辑窗口,选择【NVAR SVAR WAVE Checking】,或者执行菜单命令【Procedure】|【NVAR SVAR WAVE Checking】,如图547所示。
图547启动运行时错误调试和引用错误调试
看下面的例子:
Function testf(v1)
Variable v1
Make/O/N=(3,4) data
Variable i
for(i=0;iv1;i =1)//v1大于4会出错
MatrixOP/O w=col(data,i)
endfor
wave w=w1//w1可能不存在,此时引用会出错
Display w1
Print v1
End
上面的例子中存在一个明显的错误,一个潜在的错误。明显的错误是w引用了一个不存在的wave w1,这时Display命令就会出错; 潜在的错误是循环中的MatrixOP命令,当参数v1大于二维wave的列数(本例中列数为4)时程序就会出错。首先不启用【Debug On Error】和【NVAR SVAR Wave Checking】菜单命令,执行testf(1),这时程序出现如图548所示的错误提示。
图548执行testf(1)后报错
同时历史命令行窗口输出1,表示程序仍然正常执行完毕,但是里面有一些错误。如果执行test(5)则会出现如图549所示的错误提示。
图549执行test(5)后报错
虽然上面两个错误提示了可能出错的原因,但是并没有给出到底是哪个wave引用出现了错误(实际的例子远比这个复杂,wave引用可能是几十个),特别是第二个错误,只有在v1大于4时才出错,而什么时候v1大于4,是什么原因导致v1大于4,单靠这个提示是无法确定的。此时,如果启用了【Debug On Error】和【NVAR SVAR Wave Checking】选项,则程序会在出错的地方立即停下来,这样就可以看到到底是哪个wave引用出错或者是到底v1等于多少导致MatrixOP出错。启用Debug On Error和NVAR SVAR WAVE Checking,执行testf(1),程序会弹出调试对话框如图550所示。再执行testf(5),弹出调试对话框,如图551所示。当被调试的程序类型是Proc或者Macro时,如果存在错误,则无论是否设置Debug On Error,都会弹出一个类似于图552所示形式的对话框,单击【Debug】按钮,也可以调出调试器。当然如果设置了断点,则当程序执行到断点时也会自动调出调试器,这和Function没有区别。图552所示是Macro程序出现错误时的提示信息对话框。下面介绍调试器(调试对话框)的含义和使用方法。调试器由程序运行控制按钮、出错信息、当前函数列表(Stack)、变量列表、wave和表达式及结构体变量、程序窗口区域这6个区域组成,除了程序运行控制按钮和出错信息区域外,每个区域的大小都可以调节。调试对话框如图553所示。
图550wave引用错误
图551程序在出错的地方立即停止执行并弹出调试对话框
图552Macro出错时的提示信息对话框
图553Igor程序调试器
程序控制按钮含义如下(Igor Pro7按钮图标变了,但含义一样): : 立即终止程序执行。: 单步执行。: 进入调用函数。当表达式中含有函数时,按此按钮会进入被调用函数。: 跳出被调用函数,返回到原函数。: 正常执行程序。
函数列表列出当前正在执行的函数以及调用该函数的函数列表,例如图553所示的调试器就是对图554的例子进行调试时的结果。在函数f4中设置断点,然后执行f1(),由于f1调用了f2,f2调用了f3,f3调用了f4,这4个函数都会显示在函数列表中。变量列表可以显示当前函数中所有变量的值,变量列表有4个选项和1个控制选项,如图555所示。
图554程序代码调试示例
图555调试器变量列表
userdefined variables表示只显示局部变量。 Igorcreated variables表示只显示由Igor创建的变量,如V_flag等。 userand Igorcreated表示显示局部变量和Igor创建的变量。 local and global variables表示显示局部变量和和当前函数引用的全局变量。 show variable types表示显示变量的类型。变量列表显示方式如图556所示。其中列为变量名,第二列为变量的值,如果选择了【Show variable types】选项,则还会显示变量的类型,如图557所示。
图556调试器变量列表
图557显示变量类型
每一列的宽度都可以调节。双击每一个变量值所在区域,可以修改变量的值。当左边函数列表中被选择的函数发生变化时,变量列表也相应调整为对应函数中的变量。
图558在调试器中查看wave、结构体和表达式
wave和表达式及结构体区域列出当前函数列表中所选择函数中所有的wave、结构体变量,还可以输入表达式并计算输出结果。和变量列表类似,这里也有不同的选项,如图558所示。Expressions表示输入表达式并计算表达式的值,如当前函数中有一个v4的局部变量,输入下面的表达式,然后按回车键,调试器自动计算出结果,如图559所示。
图559调试器中使用表达式
Waves in current data folder,查看当前文件夹下的所有wave。Waves in root data folder,查看root文件夹下的所有wave。WAVEs and STRUCTs,查看当前函数中所有引用的wave和结构体变量。Show wave scaling,显示wave的x(y)坐标。如果不选择此选项,wave显示的方式为【index】: value,选择此选项,则显示方式为(坐标): value,如图560所示。
图560调试器可以查看wave内容
Show wave in table,设置wave的显示方式为表格方式,如图560所示。Show wave in graph,设置wave的显示方式为图形式,如图561所示。调试器程序窗口显示当前函数所处程序文件中的所有代码,由一个黄色的小箭头指向当前断点处,另外还有一个灰色的小箭头指向调用设置了断点的函数的表达式。程序窗口区域也可以设置断点,但无法编辑程序。通过程序窗口还可以实时地查看变量的值,方法是将鼠标指针置于一个变量之上等待一小会儿,就会自动显示该变量的值,如图562所示。
图561在调试器中以图的方式查看wave
图562调试器程序窗口中查看变量实时值
除了显示变量的值以外,程序窗口还能显示表达式的值,方法是选择一个表达式,然后将鼠标指针置于表达式之上等待一小会儿,如图563所示。
图563调试器查看表达式的值
通过调试找到程序出错的位置后,就可以按下终止程序执行按钮并返回程序文件进行修改。通过下面的方法可以迅速从调试器程序窗口中定位到程序文件出错代码处: 在调试器程序窗口中右击出错函数名,在弹出的快捷菜单中选择【Go To 函数名】; 按Esc键或者终止执行程序或者选择正常执行程序退出调试器,也可定位到程序文件出错代码处。还有一个简便的方法是直接终止程序执行(按下调试器终止程序执行按钮),则程序文件终止执行时对应的代码处会被高亮选择。当程序经过充分调试确认没有任何错误时,应该消除所有断点,以免干扰程序执行,这时可以右击程序文件,在弹出的快捷菜单中选择【Clear All Breakpoints】。程序的调试是程序开发的重要组成部分,可能有20%时间是开发程序,剩下的80%时间都是在调试程序。因此,读者一定要充分掌握程序的调试技巧,善于发现并消除程序中隐藏的错误,提高程序的健壮性。
评论
还没有评论。