文档视界 最新最全的文档下载
当前位置:文档视界 › (完整版)IDL入门教程八

(完整版)IDL入门教程八

(完整版)IDL入门教程八
(完整版)IDL入门教程八

第八章IDL编程基础

本章概述

本章的目的是学习IDL基本编程技巧,具体的来说,可以学到以下方面内容:

1.IDL批处理文件、主程序、过程和函数之间的区别;

2.如何在IDL程序中输入和输出信息;

3.如何在IDL程序中使用定位参数和关键字;

4.如何编译和运行IDL程序;

5.程序中的常用控制语句语法

如果把IDL程序看作一系列IDL命令的话,那么IDL程序——或称为IDL程序模块——可以分为四类:(1)批处理文件(2)主程序(3)过程(4)函数。

编写IDL批处理文件

最简单的程序是一个IDL批处理文件。一个批处理文件由一系列命令组成,这与在IDL 命令行敲入的命令完全一样。大多数人用批处理文件是为了自动执行自己在IDL命令行一次又一次敲入的命令。

例如,假设要在IDL中打开并显示一组图像,如果已经将图像数据读入到变量image 中,那么用来显示图像的命令可以如下所示:

IDL> thisImage = BytScl (image, Top = 199)

IDL>LoadCT, 5, NColors = 200

IDL> s = Size (image)

IDL> Window, /Free, XSize = s[1], ysize = s[2]

IDL> TV, thisImage

这五行代码并不多,但键入三、四次之后,可能已决定把他们放在一个名为ImageOut.pro 文本文件中。这文件就是所谓的批处理文件。

要执行该文件中的命令,必须把@放在IDL命令行的开头,其后再加上文件名即可。(.pro 为默认扩展名。如果加了其它扩展名,那么应该把文件名称写完整)如下所示:IDL> @ImageOut

注意,文件名没有在引号中,这与IDL文件名的一般规则是不一致的。

IDL会严格执行批处理文件中的命令,就像在命令行上键入一样。这意味着有必要用行续字符($)和其他命令行语言来让IDL确认键入的命令。如果在文件中的命令输入错误,那么出现的错误结果和在命令行键入命令出现的错误结果是一样的。

假设要打开8到10个图像文件,因而不得不分别打开每个图像文件,读取数据,然后运行批处理命令将每一个图像显示在窗口中。具体地说,可以这样自动进行读取数据和显示图像过程:

theseFiles = FindF ile (‘*.img’, count = num Files)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

但是这个文件不适合于作批处理文件,因为FOR循环里面有多行语句。如果没有续行符($)和命令连接符(&)的话,这种程序是很难写在命令行中的。为了自动执行由多行控制语句组成的命令,最好使用IDL主程序。

编写IDL主程序

IDL主程序和批处理文件在很多方面很相似,但也存在着很大的区别。像批处理文件一样,一个主程序也包含一系列命令。但与之不同的是,这些命令必须以END语句结束。例如,上面自动读取数据和显示图像的程序可以写成一个IDL主程序:

theseFiles = FindF ile (‘*.img’, cou nt = numFiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

End

批处理文件和IDL主程序最大的区别就是主程序的命令语句先由IDL编译器编译成程序模块,然后才执行代码。这就是为什么在主程序中可以有多行控制语句的原因。

如果将上面的代码保存在文件ImageOut.pro中,键入如下命令就可以编译并运行这个程序模块:

IDL> .RUN ImageOut

现在,主程序就已经驻留在IDL内存中了。同一时间只能有一个主程序驻留在IDL内存中。如果没有重新编译该代码而要重新运行这个程序,可以使用可执行命令.GO来实现,如下:

IDL> .Go

可执行命令(.Go,Compile,Run,等等)只能应用在IDL开发环境中,而不能用在IDL过程和函数中(尽管有很多方法可以在过程和函数中编译程序。详细情况请参阅P230“特殊编译命令”)

编写主程序的最大优点是在程序中定义的变量可以在IDL开发环境中使用,IDL的命令都是在那里键入并被解释的。换句话说,在主程序中定义的变量的作用范围是IDL整个开发环境,因而它可以被IDL其它命令使用。

更为通常的是,往往希望限定变量的作用范围,以使它们不致于占用大量的内存。在编程过程中,使用大量的全局变量会使内存的使用效率大大降低。为此,大部分应用程序都用过程和函数来编写。

编写IDL过程

IDL过程和IDL主程序很相似,同时也存在很大的区别。首先,在编写一个过程时,实际上所做的是创造另外一个IDL命令,一个构造在IDL系统语言之上的新命令。在IDL 命令行和程序中,可以使用所创造的命令,就像使用IDL系统内置的命令一样。

过程看起来和主程序非常相似,不过过程是以过程定义语句开始的。定义语句的目的就是为过程命名(如果喜欢,也可以称之为命令名称)和定义过程参数。过程的参数既可以是定位参数也可以是关键字。待一会儿将学到更多有关过程参数如何定义方面的知识。

例如,想将上面的主程序写成一个名为ImageOut的IDL命令,可以这样编写这个过程:PRO Imageout

theseFiles = FindF ile (‘*.img’, count = num Files)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

End

如果这些代码被保存在一个名为ImageOut.pro文本文件中,在命令行上键入此程序的名字,它就会自动编译和执行。如下所示:

IDL> Imageout

有时候想在运行之前清楚显式地编译一个过程或函数。(例如,在代码中有一个错误,在修改完毕之后,运行之前希望将程序重新编译。)如果想要显式地编译和运行上面的代码,可以这样键入:

IDL>.Compile ImageOut

IDL> Imageout

注意,在上述编译语句中的ImageOut是想要编译的文件的文件名(IDL默认.pro为文

件扩展名)。文件名并没有加引号,但是它可能会区分大小写,这取决于当前的操作系统。.Compile命令将编译该文件中所有的程序模块,但不会运行其中的任何一个。(在这个例子中只有一个程序模块。编译方面的详细信息请参阅P228“编译和运行IDL程序模块”。)文件名不一定必须和过程名称相同,但通常情况是这样的。上面的第二行语句是想要运行的程序或程序模块的名称。

过程和与函数中变量的作用范围

关于过程和函数很重要的一点是在过程和函数中创建的变量是局部变量。也就是说,只有过程和函数内部的命令才能调用在他们内部创建的变量。

例如,在上面的过程中,在IDL命令行中是不可能识别变量theseFiles,thisimage或者s的,因为这些变量的作用范围只限于过程。而且,当IDL退出该过程时(在这种情况即为执行到end语句),这些变量被清除,它们所占用的内存也被释放。

如果所有的变量的作用范围都只是局部的,这就变得极其不方便了。因为那样就不可能在过程、函数之间,或命令行与过程及函数之间进行信息交流和数据传递了。因此,可以采用多种办法来扩展变量的作用范围,或在过程、函数之间传入或传出信息通常情况下,信息(例如变量等),是以过程和函数的参数形式来传递的。

创建定位参数

定位参数是在过程的定义语句中被定义的。一般来说,定位参数的数量没有限制的,但对它们的顺序有严格的要求。第一个定义的定位参数(“定义”的意思是把它放在过程名称的右边)是参数1,紧接着定义的是参数2,以此类推。

例如,希望指定ImageOut过程通常在哪一个子目录下查找图像文件。可以通过字符串参数lookHere把信息传入到ImageOut过程中去,修改后的ImageOut过程如下所示,增加部分已用黑体字标出。

PRO ImageOut, lookHere

Cd, lookhere

theseFiles = FindF ile (‘*.img’, count = num Files)

Print, ‘number of files found: ‘, numfiles

For j = 0, numFiles-1 Do Begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

thisImage = BytScl (image, top =199)

LoadCT, 5, Ncolors =200

S = size (image)

Window, /Free, XSize = s[1], YSize = s[2]

TV, thisImage

Endfor

End

若要调用修改以后的过程,应该重新编译它。之后,就可以这样来调用它:(假设在

Windows操作系统中。下面的代码仅起示范使用)

IDL>. Compile ImageOut

IDL> ImageO ut, ’c:\rsi\mydatafiledir’

字符串c:\rsi\mydatafiledir被拷到参数(有时也叫形参或局部变量)lookhere后,命令CD根据这个将工作路径改变到与之同名的指定路径中。

这个字符串也可以作为变量传递到过程ImageOut中。如下面所示:

IDL> myimagedirecto ry = ‘c:\rsi\mydatafiledir’

IDL> ImageOut, myimagedirectory

在这里,变量myimagedirectory通过引用方式来传递的。它的意思以后将会更详细讨论到。但其中一点就是,这个变量现在可以在主程序和过程ImageOut中被使用。这意味着如果在过程内部改变它的值,这个变化将会反应在IDL命令行上来。换句话说,IDL命令行上的myimagedirectory变量和ImageOut程序中的变量lookhere是相同的。

换句话说,这个变量有两个名字,这两个名字都指向同一个数据或IDL中的物理地址。(就好比说,一个人在美国时人们叫他为乔,而当他回到家乡智利时是人们叫他乔丝。两个名字所指的是同一个人,但是一个地方的人只知道他的一个名字。)

但是如果在调用ImageOut时忘记了传递参数会怎样呢?如果键入如下所示会发生什么事?

IDL> ImageOut

在这种情况下,什么也不会传递到变量lookhere中去,所以在过程中变量就没有定义(但是,没有定义也是变量的一个有效类型)。遗憾的是,这会带来很大的麻烦,因为在过程中,CD命令语句把lookhere变量作为它的参数。而在cd命令中使用一个没有定义的变量会导致错误的。因此,清楚地知道过程如何被调用是非常有必要的。

定义可选的或必须的定位参数

IDL提供的N_Params命令可以返回过程在被调用时定位参数的调用个数(不包括关键字)。这个命令如下所示:

Numparams = N_Params ()

知道了过程在调用时输入了多少个定位参数,就有机会对所传入的参数是可选择的还是必须的进行判断。例如,想把参数lookHere作为一个必须的参数,在过程的前几行应增加如下几行:

Pro ImageOut, lookHere

Numparams = N_Params ()

If numparams EQ 0 Then $

Message, ‘must supply one parameter to this procedure.’

Cd, lookHere

这里,message命令会产生一个错误,导致IDL停止执行过程中的命令,同时错误信息被送到命令记录窗口或输出窗口。这个结果和在没有数据参数的情况下调用plot命令是一样的。

也许在ImageOut过程中把参数lookHere作为一项可选参数更合情合理。

如果没有提供参数,那么lookHere变量的值就会设置为当前子目录。实现这部分功能的代码如下所示:

Pro ImageOut, lookhere

Numparams = N_Params ()

If numparams EQ 0 Then CD, current = lookhere

Cd, lookHere

在这里,如果在调用ImageOut时没有提供lookhere参数,那么cd命令就和关键字Current 一起使用,并将当前工作目录放在lookhere变量中(关键字current在这里是一个输出性质的关键字,关于这个,读者等下将会了解更多。)

在IDL过程和函数中,定义参数的习惯性规则是,参数是指必须的参数,关键字是指可选参数。这种规则常常因为参数而破例,而几乎不会因为关键字而破例。也就是说,经常可能会出现参数是可选的,却很少有关键字是必选的情况。

定义关键字

和定位参数一样,关键字也是在过程的定义语句中加以定义的。只要愿意,关键字可以和混杂在参数中定义,(这对参数的相对位置没有任何影响),但一般地,关键字在参数定义之后再定义。

如果读者看了过程ImageOut的代码,就会发现程序中硬性地载入了颜色表5。但是,如果允许用户指定颜色表,并且只有在用户没有指定的情况下才将颜色表5作为默认的颜色表,那样就更符合情理了。这便是关键字所起的作用。关键字定义语法如下:

Keywordname = keywordsymbol

等号左边的是关键字名字,这是关键字使用时的名字;等号右边是在过程和函数中关键字所赋的值。例如,下面是为Imageout过程定义关键字colortable的例子:

Pro ImageOut, lookHere, colortable = thiscolortable

关键字colortable是连同颜色表值一起调用ImageOut过程时即将使用到的名称。例如,想用Red-Temperature色表,即色表3,来浏览这些图片,可以这样调用过程:IDL> ImageO ut, ‘c:\data’, colortable = 3

使用缩写关键字

关键字名称,colortable,在使用过程中不必全部拼写,只要有足够多的字母将它和其它关键字区别开来就可以了。因为colortable 是ImageOut中唯一的关键字,所以用c已足够来定义这个关键字。过程ImageOut也可以这样调用。

IDL> ImageOut,’c:\data’, c = 3

或者这样:

IDL> ImageOut, ‘c:\data’, color = 3

上述三种调用方法的作用是相同的。

关键字右边的变量,thiscolortable,是用来给程序中关键字赋值的。在这里值赋为3。

现在可以重新编写ImageOut的代码了(修改部分已用粗体字标出):

Pro ImageOut, lookHere, colortable = thiscolortale

Numparams = N_Params ()

If numparams EQ 0 Then CD, current = lookHere

CD, lookHere

t heseFiles = FindFile (‘*.img’, count = numFiles)

Print, ‘number of files found:’ numfiles

For j = 0, numFiles-1 do begin

Openr, lun, theseFiles[j], /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

Thisimage = BytScl (image, top = 199)

LoadCT, thiscolortablel, ncolors = 200

S = size (image)

Window, /Free, XSize = s (1), YSize = s (2)

TV, thisimage

Endfor

End

但是如果变量thiscolortable没有定义呢?换句话说,如果用户这样调用ImageOut过程会怎么样呢?

IDL> ImageOut

由于关键字colortable没有被使用,所以在变量thiscolrotable里面什么也没有。这话也可以这样理解:变量thiscolortable在过程中没有定义。如果这样,那么在上述代码中LoadCT 命令以下的三分之二的编码都会无效,因为它不能接受一个没有定义的变量作为参数。

这使读者想起上次曾经遇到的情况,即在调用ImageOut时应该知道参数是否被使用。在这里读者应该知道,在调用ImageOut时关键字是否被使用的情况。

定义可选择的关键字

关键字是可选择的参数。这意味着,在调用含有关键字的过程和函数时,关键字很可能不会被使用。但是赋有关键字的值的变量还是会在程序中被使用的。这意味着要对每一个已经定义的关键字赋予默认值。实际上如果不这样做的话,IDL应用程序迟早会失败。

但是怎样才知道需要定义默认值呢?当然,如果用户已经赋值那就没有必要来定义默认值。

但是怎样知道用户是否已赋值了呢?如果是参数,可以用N_Params命令来提供这方面的信息。遗憾的是,N_Params只对参数才起作用,而不能提供关于关键字的任何信息。

很多人把这个问题想象为“我想知道关键字使用了还是没有?”。如果是这样的话,要查明关键字是否使用比其最初出现的时候要难多了。我们经常期待好事出现,但是现实却未必如此。我们问,“这个变量已经赋予了关键字的值还是没有?”

关键字定义了吗?

对一个关键字,比如colortable,很可能被赋予很多可能的值,用N_elements这个IDL 命令可以知道在过程和功能程序中,变量thiscolortable是否已经定义。尽管N_emements在IDL中还有其他作用,但是在这个情况下,使用这个函数的功能是:如果N_emements的参数没有定义,则函数返回值为零。否则将返回参数的元素个数。该参数可以是任意数据类型。

在这种情况下,过程ImageOut在用户没有提供色表的情况下,将会定义默认的色表值,它的前几行如下所示:

Pro ImageOut, lookhere, colortable = thiscolortable

Numparams = N_Params ()

If numparams eq 0 hen cd, current = lookhere

Cd, lookhere

If N_Elements (thiscolortable) EQ 0 Then thiscolortable = 5

注意,一定要把关键字的名字(比如,它在IDL命令行中是如何使用的)和赋有关键字的值的变量区分开来。关键字的名字在关键字定义的左边,赋值的变量在右边。N_elements 的参数必须是关键字的变量,而不是关键字的名字。

这点经常被错误理解,因为一些IDL程序员(包括作者在内)喜欢把关键字名字和变量拼写成一样。换句话说,如果作者正在为自己编写以下程序,作者会这样写:Pro ImageOut, lookHere, colortable = colortable

Numparams = N_Params ()

If numparams EQ 0 Then CD, current = lookHere

Cd, lookHere

If N_Elements (colortable) EQ 0 Then colortable = 5

这里不存在正确与否,但还是存在区别。IDL会清楚地区别这些。建议读者也这样做。记住,检查的应是关键字变量,不是关键字名字。作者喜欢把关键字变量和名字拼成一样,因为这样可以减少拼写错误。我可以认为我是在检查关键字名字,事实上我在检查关键字变量。

处理具有双重属性的关键字

如果仔细检查ImageOut的代码,将会发现图像数据在显示之前已经被拉伸了。这个特殊代码行是:

Thisimage = BytScl (image, top = 199)

图像数据文件很可能已被拉伸过,因此这个步骤并不是必须的。如果这样的话,就可以定义一个名为scale的关键字,当过程带该关键字调用时,则拉伸图像,如果不带该关键字调用,则忽略图像的拉伸。这样的关键字具有双重属性,它可以设置也可以不设置。其它关键字同样具有双重属性,也就是说它们可以是真或假,是或者否,1或者0。

IDL提供了一个特殊的命令来处理类似的关键字,即Keyword_Set命令。和N_elements 一样,Keyword_Set的参数是关键字变量而不是关键字名字。但Keyword_Set有一点不一样就是,如果Keyword_Set的参数没有定义或者值为0,那么它将会返回0。否则返回1。所以,Keyword_Set的返回值只有两种:0或者1。

许多程序员错误运用了Keyword_Set,用它来检测关键字是否使用。其实不是这样的,用它来验证一个关键字是否已经使用最终会导致IDL应用程序的错误。只能在关键字具有双重属性时才能使用它。

在确定只有设置了关键字scale情况下才调用图像拉伸步骤,这时的ImageOut代码可以这样来写:

Pro ImageOut, lookhere, colortable = thiscolortable, Scale = scaleIt

Numparams = N_Params ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Th esefiles = findfile (‘*. img’, count = numfiles)

Print, ‘number of files found: ‘, numfiles

For j = 0, numfiles-1 do begin

Openr, UN, thesefiles (j), /get_lun

Image = BytArr (512,512)

Readu, lun, image

Free_lun, lun

If Keyword_Set (scaleIt) then $

Thisimage = BytScl (image, top = 199) else $

Thisimage = image

LoadCT, thiscolortable, ncolors = 200

S = size (image)

Window, /Free, XSize = s (1), YSize = s (2)

Tv, thisimage

Endfor

End

现在,希望对图像进行拉伸,可以这样调用过程ImageOut:

IDL> ImageOut, /scale

记住/keyword只是表示keyword=1。这种设置关键字的简单表示方法在使用具有双重属性的关键字时经常碰到。

创建输出型参数

到目前为止,在过程ImageOut中创建的关键字或参数都是输入型参数。换句话说,信息是从程序以外传入进来的。但是,还经常需要把信息输出到此程序外部。

比如说,ImageOut的用户想要知道被打开的文件名。在过程内部这些文件名被储存在变量thesefiles中。对ImageOut来说,这是个局部变量。也就是说,该变量的作用范围仅限于过程本身。一个用户怎样才能从过程外部获得这些信息呢?

有许多方法可以采用,比如说,一个公用模块可以扩大变量的作用范围,但在通常情况下,不需要公用模块。一个简单的方法是使用输出型参数。

用引用和传值的方法传递信息

209页中,在“创建参数”中讨论了关于用引用传递参数的问题。通过引用的方式,参数的作用范围比局部变量作用范围要大。也就是说,通过引用来传递参数事实上是——在低级意义上——指向IDL内存中的地址,这从而使得在所有具有引用权限的程序中该参数都可以被引用。在程序之间通过引用方式进行传递数据的实际上是,任何程序对该数据的修改都将影响其他程序中该数据的结果。

另一个传递信息的方法是传值。就是说,传递到程序中的不再是指向数据地址的指针,而是数据值的拷贝。在程序模块中对数据的修改不会对原有数据有任何改变。

在IDL中,所有的变量都是以引用的方式传递的。其他的,比如下标变量、系统变量、表达式、结构,常量,都是通过传值的方式进行的。

这里举一个简单的例子来说明这一点。这是一个用来调整图片的大小并将它显示在一个150*150的窗口中的程序。在文本编辑器中键入如下所示,并保存为resizeit.pro。

Pro resizeit, image

Image = congrid (image, 150,150)

Window, 0, xsize = 150, ysize = 150

Tvscl, image

End

现在用loadata来载入世界海拔高度数据,如下:

IDL> image = loaddata (7)

用help命令检查变量:

IDL> help, image

Image byte = array (360,360)

现在用resezeit程序调入该数据,再次检验图片变量。这时,变量image已经变成了一个150*150的字节型数组。

IDL> resizeit, image

IDL> help, image

Image byte = array (150,150)

变量image已经发生变化,这是由于在传入过程resizeit中时,它是以引用方式被传递的。换句话说,它在resizeit程序中具有使用权限。在这里,变量image既是输入型变量(即传入到程序中的信息),又是输出型变量(输出到程序外的信息)。变量可以是输入型变量、输出型变量,或者两者都是。这完全取决于在IDL程序中怎样编写它的代码。

假如想把image数据传入到程序,但不想在程序内部对它有所改变,也可以选择下面两种方法之一:(1)重新编写resizeit程序,不改变image变量;(2)用传值的方式将变量image 传入到当前程序中。如果是第一种方法,可以这样重新编写程序:

Pro resizeit, image

Window, 0, xsize = 150, ysize = 150

Tvscl, congrid (image, 150,150)

End

在第二种情况下,也可以用表达式作为参数,如下所示:

IDL> rsizeit, image + 0b

有时候也会遇到相反的问题,比如,希望在过程或函数中改变某个值而实际上却没有。这通常是因为输入的参数不是变量,而可能是变量的一部分。例如,它可能是结构中的一个字段。因为结构、系统变量,任何一种表达是都是用传值方式传递,而不是引用。只有变量(并且是完整的变量)才是通过引用方式来传递的。

例如,假设上面的变量是一个自定义的系统变量:

IDL> image = loaddata (7)

IDL> defsysv,’image’, image

如果这样调用resizeit程序:

IDL> resizeit, !image

这时,就不可能将新尺寸的图片输出到程序外和传递到系统变量中,因为这是系统变量!image总是以传值方式传递的而不是以引用的方式。正确的命令如下:

IDL> thisimage = !image

IDL> resizeit, thisimage

IDL> !image = thisimage

为了解决从ImageOut过程中获得文件名的问题,选择输出型关键字是比较恰当的。关键字,和参数一样,可以通过传值或引用方式来传递。带有关键字filename的新程序代码如下,变化已经用粗体标出:

Pro ImageOut, lookhere, colortable = thiscolortable, Scale = scaleit, $

Filenames = theseFiles

Numparams = N_Params ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = fi ndfile (‘*.img’, count = numfiles)

Print ‘number of files found:’, numfiles

For j = 0, numfiles-1 do begin

Openr, lun, thesefiles (j), /get_lun

Image = bytarr (512,512)

Readu, lun, image

Free_lun, lun

If Keyword_Set (scaleit) then $

Thisimage = bytscl (image, top = 199) else $

Thisimage = image

Loadct, thiscolortable, ncolors = 200

S = size (image)

Window, /free, xsize = s(1), ysize = s(2)

Tv, thisimage

Endfor

End

如果用户希望返回文件名,只需简单使用关键字名字,并将它赋值给某个变量。如下所示:

IDL> ImageOut, filenames = myfiles

尽管在过程ImageOut被调用时变量myfiles可能还没有定义,但当过程返回时它会被定义为一个字符数组。如果变量myfiles在调用ImageOut时已经定义,它会被ImageOut再定义一次,而它的初始值将会丢失。

参数存在吗

使用输出型参数,常常希望知道该参数或是关键字是否存在。例如,某个程序在计算数据时很费时间,但计算结果并不需要。因此希望,只有在用户需要输出参数时才会进行计算。

为了更生动的说明这个问题,怎样才知道当用户这样调用程序ImageOut时,参数是否存在?

IDL> ImageOut

或这样:

IDL> ImageOut, filenames = myfiles

答案是无法确定。在过程ImageOut内部,可以用N_emements或Keyword_Set来检查为与关键字filenames相对应的的变量(即变量thesefiles)的赋值情况,遗憾的是,这些命令最多只能说明这些变量是否已经定义。但这并不同于变量是否存在。如果变量myfiles没有定义,那么N_elements,Keyword_Set都无法区别上述两种调用程序方法的区别。

为了帮助人们知道一个关键字是否已经使用,IDL5.0引入了新的函数arg_present。arg_present将返回1,如果参数(变量)是以关键字和或参数传递的(换句话说,关键字和参数被使用了),并且变量以引用的方式传递的。后面一点极其重要。否则,arg_present将返回0。如果关键字或参数已经使用了,但参数以传值方式传递,这时arg_present将返回0,

就好像参数没有被使用或者并不存在一样。在使用这个新命令时应该倍加小心。

编写IDL函数

函数的定义和过程非常相似。也就是说,函数的参数和关键字的定义与过程中的一样。但也两点不同:(1)函数定义以Function而不是以pro开头;(2)函数通常返回一个单一的、特定的IDL变量给函数调用者。被返回的变量被称函数的返回值,它可以是任何一种有效的变量类型或结构。例如,它可以是一个很大的结构变量。实际上,这意味着在所有函数的return语句中必须有一个参数来保存函数的返回值(一个没有Return语句的函数将隐性地返回零)。

例如,为了把过程ImageOut改成一个函数,只需做一点改动,如下所示。变化部分已经用粗体字标出。

Function ImageOut, lookhere, colortable = thiscolortable, Scale = scaleit, $

filename = thesefiles

Numparams = n_paras ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = findfile (‘*.img’, count = n umfiles)

Print,’ number of file found:’, numfiles

For j = 0, numfiles-1 do begin

Openr, un, thesefiles[j], /get_lun

Image = bytarr (152,512)

Readu, lun,image

Free_lun, lun

If Keyword_Set (scaleit) then $

Thisimage = bytxl (image, top = 199) else $

Thisimage = image

Loadct, thiscolortable,l ncolors = 200

S = size (image)

Window, /free, xsize = s[1], ysize = s[2]

Tv, thisimage

Endfor

End

在IDL中,函数的调用语法不一样。函数总是会返回一个值,因此把用来接收返回值的变量放在等号左边,函数调用名放在等号右边,而函数的所有参数和关键字都放在括号内,如下所示:

IDL> thisvalue = ImageOut (‘c:\data’, filenames = mydatafiles)

由于函数ImageOut没有Return语句,所隐性地返回为0。

但是如果希望ImageOut返回打开和显示的文件的数目,可以这样编写程序。更改的地方已经用粗体字标出:

Function image, lookhere, colortable = thiscolortable, Scale = scaleit, $

filenames = thesefiles

Numparams = N_Params ()

If numparams eq 0 then cd, current = lookhere

Cd, lookhere

Thesefiles = findfile (‘*.img’, count = numfiles)

Print, ‘number of files found:’, numfiles

For j = 0, numfiles-1 do begin

Openr, lun, thesefiles[j], /get_lun

Image = bytarr (512,512)

Readu, lun, image

Free_lun, lun

If Keyword_Set (scaleIt) then $

Thisimage = bytscl (image,top = 199) else $

Thisimage = image

Loadct, thiscolortable, ncolor = 200

S = size (image)

Window, /free, xsize = s[1], ysize = s[2]

Tv, thisimage

Endfor

Return, numFiles

End

如果想要显示和打印出某个特定子目录下的所有图片文件的文件名,可以键入如下代码:

IDL> numfiles = ImageOut (‘c:\data’, filenames = mydatafiles)

IDL> for j = 0, numfiles-1 do print, mydatafiles[1]

不管有10个还是100个文件,这段代码照样运行得很好。

更多的函数是用来获得对变量操作的结果。例如,想得到一个矢量或一组数组的平均值,可以这样在文本编辑器中编写average函数:

Function average, data

Averageavalue = total (data)/n_elements (data)

Return, averagevalue

End

调用形式如下:

IDL> thisdata = [3,5,6,2,9,5,4]

IDL> avg = average (thisdata)

IDL> print, avg

4.85714

函数的返回值可以作为另一个过程或函数的参数,所以也可以把上面的三行代码写为一行:

IDL> print, average ([3,5,6,2,9,5,4])

4.85714

方括号和函数的调用

当调用一个IDL函数时,可能会有些迷惑。要分辨是函数调用,还是引用数组的下标,仅仅是通过检查IDL代码是很困难的。比如, 上述代码就显示出这方面的问题。如果没有其他信息,Average也可以认为是一个IDL变量。(一个函数和一个变量是可能同时名取为

average的,但这是一种不明智的做法。)

为了增加代码的可读性,Research System公司在IDL5中引入了方括号来指定下标变量。从而就可以这样定义名为average的变量了,如下所示:

IDL>average=[4,6, 3,8,2,1]

它还可以这样引用:

IDL>number=average [3]

这种语法就可以将变量average和函数average区分开来了,因为函数average要求其参数用括号括起来。

用Forward_Function命令保留函数名

为了确保IDL编译器能区别函数调用而不是带下标的数组,可以用Forward Function 命令来预先声明函数名称并为之保留。例如,如果想保留上述函数Average,可以键入:IDL>Forward_Function Average

注意:保留的函数名不必用引号括起。

现在,如果在编译程序模块中,IDL编译器碰到average,并且跟有参数,那么它就认为这是函数调用而不是对数组元素的下标引用。

使用程序控制语句

与其他程序语言一样,IDL程序可以通过控制语句来控制程序语句的执行顺序。大多数情况下,一个程序控制语句包括布尔测试(通常是程序变量的一种表达式)、当测试为真时执行的命令和为假时的执行命令。或者,它包括一个计数器和反复执行某个语句的方式。重复执行某个语句的情况,取决于计数器的值。

IDL中表达式的真和假

在IDL中,一个表达式的真或者假取决于该表达式中的数据类型。真的情况可以概括如下:

奇数,非零的字节型、整型和长整型;

非零的浮点型、双精度型和复数类型(包括单精度和双精度);

非空的字符串类型;

在IDL中,通过布尔逻辑运算为非真的表达式,都为假。

注意,指针和对象不是通过真或假来衡量,而是分别由Ptr_valid或Obj_Valid命令来检查是有效还是无效。

第一中类型的例子是典型的IF…THEN… ELSE 控制语句。在IDL中是这样表述的:IF test THEN statement1

其中,test 通常是一个布尔表达式,stattement1是IDL命令。

注意,test必须总是数值并且必须是在表达式调用前已经定义。例如,下面的表达式就会出错,因为变量coyote没有定义。

IDL> IF coyote EQ ‘tricky’ THEN Print, ‘Missed him!’

第二种类型的例子是典型的FOR循环控制语句。在一个FOR循环语句中,语句反复地

被执行,直到计数器达到某个预设定的值。如:

FOR j=0,10, DO statement1

这里,j是计数器,statement1是将要执行的IDL命令.

例如下面的例子中,Print命令将执行11次,打印数值0到10.

IDL> For j=0,10, Do Print, j

将多个语句处理成单个语句

大多数控制语句(见上面)在语法中,要求是单个语句,因为对IDL解释器而言,所有的命令看上去都是一个命令。但这常常不是所希望的。例如,在某中测试的基础上,如果这种测试是真,就执行4个语句,如果为假就执行15个语句。

在IDL中,通过使用BEGIN 和END语句块,可以清楚地让IDL解释器将多个语句看作是单个语句。语句块以BEGIN始,以END结束。

例如,若在FOR循环中执行多个语句,上面的循环应写作:

FOR j=0, 10 DO BEGIN

Statement1

Statement2

Sratement3

END

当与BEGIN相匹配的多个END语句结束时,程序变得难于阅读和交互操作。这是因为在IDL中,使用控制语句的地方都可以以END语句结束。例如,在上述控制语句中,通常用ENDFOR命令代替END命令,因为可以用ENDFOR命令结束FOR循环。

FOR j=0,10 DO BEGIN

Statement1

Statement2

Statement3

...

ENDFOR

有效的END语句可以是ENDIF、ENDELSE、ENDFOR、ENDWHILE、ENDCAS和ENDREPEAT,下面将更详细地讲述这方面的知识。

If…Then…Else控制语句

IF…THEN…ELSE控制语句是应用最广泛的控制语句之一。它是以布尔测试或可以用“真”和“假”来衡量的表达式为基础的。(详细参见220页中的“IDL中表达式的真和假”)下例是一个简单的例子。

If (num GET 10) THEN index =2 ELSE index=4

在IF…THEN…ELSE中,ELSE是可有可无的,上述的语句也可以表示为:If (num GET 10) THEN index =2

在使用多行语句时,IF…THEN…ELSE控制语句的语法就比较灵活了。注意,这多行IDL 语句对IDL编译器来说必须是个简单IDL命令。如果在IDL命令行中,想要编写一个多行命令,就必须用行连续符和行连接符。例如,IF…THEN…ELSE控制语句可以写作:

IDL>IF (num GT 10) THEN BEGIN $

Index=2&$

Num=0&$

ENDIF ELSE THEN BEGIN $

Index=4&$

Num=-10&$

ENDELSE

如果代码是文件中,那么就完全没有必要使用行连续符和行连接符了。例如,在IDL 主程序的过程或函数中,代码可以写成:

IF (num GT 10) THEN BEGIN

Index=2

Num=0

ENDIF ELSE BEGIN

Index=4

Num=-10

ENDELSE

与在IDL解释器中相反,对IDL编译器而言,BEGIN和END语句已经暗示行连续符和行连接符。但是,在IDL编译器中,IF…THEN…ELSE的语法也不是任意的。

例如,一些程序员喜欢将BEGIN语句和END语句对齐,这样可以一目了然地知道程序所指的什么,比如:

IF (num GT 10) THEN $

BEGIN

Index=2

Num=0

ENDIF ELSE

BEGIN

Index=4

Num=-10

ENDELSE

但是在IDL中,代码不能那样写,因为在编译中不能恰当地分开IF…THEN…ELSE控制语句,从而不能将其作为一个独立语句。如果想把用上述格式,就必须在代码中应用行连续符来连接,如下:

IF (num GT 10) THEN $

BEGIN

Index=2

Num=0

ENDIF ELSE $

BEGIN

Index=4

Num=-10

ENDELSE

条件表达式

在IDL5.1中,引入了一种新的条件表达式(?:)。用它可以代替IF…THEN…ELSE控制语句。如变量num大于或等于10, 设变量index=2,否则设变量index=4,可表述为: Index=(num GE 10)? 2:4

在这个语句中,先进行条件判断(num GE 10),如果判断结果为真,变量index就设为问号右边和冒号左边的变量(或表达式)的值;如果为假,变量index就设为冒号右边的变量(或表达式) 的值。

FOR循环控制语句

FOR循环运用计数器来多次执行一个或多个语句,如:

A =1

FOR j=1, 10 DO BEGIN

Print, j

A = a * j * 2

ENDFOR

在这种情况下,计数器j从1 开始直到10,这个语句将执行10次。如果希望计数器J 的步长不是1,就应当明确指定步长。例如,让j的步长为2,可参见下例:

A =1

FOR j=1, 10 DO BEGIN

Print, j

A = a * j * 2

ENDFOR

WHILE循环控制语句

WHILE循环控制语句循环不断到执行一条或多条语句,只要判断条件为真。如: WHILE (number LT 100) DOES BEGIN

Index =amount * 10

Number =number +index

ENDWHILE

在进入WHILE循环之前,首先进行条件判断。

REPEAT...UNTIL 循环控制语句

REPEAT...UNTIL循环语句与WHILE 循环相似,不同之处在于,REPEAT...UNTIL循环语句在循环末尾进行条件判断,而不是在循环的开始。下面是一个简单的例子: REPEAT BEGIN

Index =amount * 10

Number =number +index

ENDPEP UNTIL number GT 100

CASE控制语句

有时会遇到一个判断条件,其判断结果可能有一些不同的值,无法用真和假来表示,这时就应该使用case语句。

case语句常用于响应程序的不同的事件从而执行相应的代码。下面的代码是一个响应按钮事件的例子:

; What button caused the event?

Widget Control, eventide, Get Value=button Value

; Branch based on button value

CASE button Value OF

‘Sober’: TVScl, sobel (image)

‘Roberts’: TVscl, Roberts (image)

‘Boxcar’: TVScl, Smooth (image, 7)

‘Median’: TVScl. Median (image, 7)

‘Original Image’ :TVScl, image

‘Quit’: WIDGET Control, event. Top, / Destroy

Elae: ; Do nothing at all

ENDCASE

注意,当判断条件与所列的条件不匹配时,ELSE语句定义了默认操作,在这里ELSE语句让事件进入事件处理代码中。在使用ELSE时,后面必须有一个冒号。实际上,ELSE语句不一定得出现在case语句中,但是如果判断条件与所有列出的条件都不匹配时,程序就会产生错误。

如果在case语句中使用BEGIN和END语句,一定要小心,END语句既可以是块的结束语句,也可以是case的结束语句。有时多个CASE语句可以写成如下所示:

CASE this TEST OF

0:x=5

1: BEGIN

X =5

Y =x *2

END

ELSE: x=0

ENDCASE

注意,当有一个case语句满足条件时,case总是跳出来。换句话说,一旦一个case语句为真,程序将跳转到case语句的下一条程序语句。这和在C程序不一样。

GOTO控制语句

和所有的计算机高级语言一样,IDL也有GOTO语句。IDL程序员只有在的确需要时,才使用GOTO语句。任意使用GOTO语句会使简单的程序变得复杂。

GOTO语句指定一个程序标识符,当程序执行到GOTO语句时,程序就跳转到程序标识符所在的程序行。比如,可以用GOTO语句来打断一个FOR循环,见下:

FOR j =0, n DO BEGIN

Index= j * ! PI * 5.165 * thisTESTValue

IF index GT 3000.0 THEN GOTO, jump out

ENDFOR

Jump out: Print, index

程序标识符后面要有一个冒号。在程序标识符所在行应当是可执行语句,如这个例子所示,但也可以不是可执行语句。如果该语句无效,IDL程序将跳转到标识符下面的一个可执行语句。

错误处理控制语句

在IDL中,有多个错误处理语句,通常使用到的有:ON_IOError,ON_Error和Catch。

ON__IOError 控制语句

ON__IOError语句与IDL中的GOTO语句相似,它可指定一个标识符,当出现任何输入输出错误时,程序就跳转到标识符所在的行。

例如,ON__IOError语句可以在读取数据时进行相应的处理,如下所示:ON_IOError, Problem

OpenR, lun, filename, / Get_Run

Data =Bytarr 9256,256)

ReadU, lun, data

Free_Lun , iun

...

RETURN

Problem: Print, ‘Problem reading data. Returning...’

IF N_Elments (lun) NE 0 THEN Free_Lun ,lun

RETURN

END

在GOTO语句中一样,注意程序标识符后的冒号,程序标识符所在行不一定必须是有效的IDL语句。

ON_Error 控制语句

在程序运行过程中出现错误时,ON_Error 控制语句指明了IDL 应该怎样做。与其他的控制语句不一样,ON_Error 控制语句并不执行一个新的IDL语句,而是指出错误出现时应采取的措施。ON_Error命令的有效参数为0,1,2,3。表11表明了程序出错时采取的措施。

当程序出现错误时,IDL新手通常不之所措。因为默认的行为(ON_Error设为0)是在程序出错的地方停止下来。由于受IDL提示的影响,许多用户认为错误出现在IDL主程序中。当用“HELP”命令时,他们非常失望,因为所有的变量已经消失,不存在了。

用户实际看到是位于已经破坏了的程序中的变量。使用命令RETALL(返回IDL主程序)将会恢复消失的变量,从而获得它们的实际值。

RetAll是用户最重要的工具。在程序不能正常退出时,有经验的用户常常自动用RetAll 命令来恢复。特别是在编写模块程序时。如果忘记了这个重要的命令,程序将会常常变得让摸不着头脑,尤其是在编写IDL程序的时候。

为了避免用户在使用这个程序时感到迷惑,最好把On_Error,1加到正在调试的程序中。如果程序出错了,IDL将隐性地执行RetAll命令,从而返回到IDL主程序上。

Catch 控制语句

第三种控制语句,即Catch 控制语句,可能是最有效的,它与GOTO语句相似,但也不完全一样。Catch 控制语句调用如下:

Catch,error

这里的error是一个变量名。(当然,变量名称可以取自己喜欢的)。当执行到这条语句时,IDL为该特定程序模块记录了一个Catch错误处理语句,同时,将该变量(即Error)设为0。

若在程序运行过程中出错时,且在该程序中有相应的Catch错误处理语句,则该变量被赋予相应的错误值(每个错误都有与之相对应的数值),然后程序跳转到Catch语句后的第一条语句。

在实际操作中,CATCH语句行包括一块错误处理代码。例如,要读一个文件,这不可避免会出现许多错误,就可以用Catch语句俩来进行错误保护。

Catch ,error

IF error NE 0 THEN BEGIN

Catch, / Cancel

Print, ‘Problem reading data file .Returning…’

IF N_Elements (lun) NE 0 THEN Free_Lun ,lun

RETTURN

ENDIF

OpenR, lun, filename, /Get_Lun

Data=BytArr (256,256)

ReadU, lun, data

Free_Lun, lun

Catch, /Cancel

注意,Catch错误在任何时候都可以忽略。上个特殊的例子中,在错误语句代码中的第一行就忽略了所出现的错误。如果将在错误保护程序中也有错误,这绝对是不允许的。后面,程序可能会产生新的错误,并且可能会有任意多个catch错误,但是在任何时候,只有一个错误处理语句。注意:catch语句也可以用来捕获输入输出错误的。那和On_INError有什么

相关文档