4.3 循环结构

在实际中,常遇到有些过程是重复进行的。例如,要连续采集一个物理量;对若干个数求和,等等。对具有这类特征的问题,可以利用循环结构来实现其VI的编写。

几乎所有实用的程序中都使用到了循环结构。LabVIEW提供有两种循环结构,分别是While循环和For循环。为了使用这两种循环结构,必须了解循环结构内外的数据是如何进行交换的,以及循环结构的自动索引、移位寄存器和反馈节点等功能的使用方法。

4.3.1 While循环

在LabVIEW中如何创建一个While循环呢?方法很简单。如图4.14所示,首先,在“函数”选板→“编程”→“结构”子选板上,选中While循环,将它拖曳到程序框图面板上,并按下鼠标左键且拖曳虚线框至合适的大小,如此,就创建了一个While循环。

创建好的While循环如图4.15所示。其中,灰色边框里的空白区域用于放置循环体内的程序代码;灰线框内左下角的i是重复端子,可输出已经执行循环的次数;灰线框内右下角的是条件端子,用于控制是否要退出循环。可见,While循环的循环次数是不确定的,具体要由右下角的条件端子的当前控制条件来判定。

图4.14 在程序框图上创建一个While循环

在LabVIEW中,While循环会先执行一遍循环体代码,然后再由循环条件端子判断是否要继续下一次循环。所以,LabVIEW中的While循环功能,就相当于C语言中的Do While循环。

图4.15 在程序框图面板上建好的While循环

【例4.5】 构建一个可显示随机信号波形的VI,其显示随机信号的速度应可调节。

一个编写好的例4.5 VI的前面板如图4.16所示,其程序框图如图4.17所示。此例中,每执行一次While循环,会产生一个随机数,并将其送入显示控件波形图表中显示出来。在While循环里,有一个定时函数,旨在控制两次循环之间的时间间隔。运行此VI,在前面板的波形图表控件上,可以显示出一段随机信号的波形,调节“循环延时”按钮,可以看到随机信号波形的显示速度会随之做相应改变。

在此VI中,前面板上为显示随机信号使用了一个波形图表控件,找到它的路径是“控件”选板→“新式”→“图形”子选板;而为了控制循环的定时,调用了一个旋钮控件,找到它的路径则是“控件”选板→“新式”→“数值”子选板;再有,控制开关选用了一个开关控件,找到它的路径是“控件”选板→“新式”→“布尔”子选板。在程序框图中,调用了一个“随机数”函数,找到它的路径是“函数”选板→“编程”→“数值”子选板;而且,还调用了一个“等待下一个整数倍毫秒”函数,找到它的路径是“函数”选板→“编程”→“定时”子选板。

图4.16 例4.5 VI的前面板

图4.17 例4.5 VI的程序框图

在LabVIEW中,While循环的条件端子常接布尔量控制开关。在例4.5所示的VI中,开关的初始状态是False,如此,单击LabVIEW的运行按钮,该VI一直会执行While循环里的代码;再单击While循环条件端子连接的控制开关,当其状态变为True时,While循环才会退出,如此,该VI也就停止了。例4.5很有实际意义,学习者今后自己制作虚拟仪器时,经常会用到这样的结构。

4.3.2 For循环

C语言中的for语句使用最灵活,不仅可用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况[7]。LabVIEW中的For循环结构也具有这样的特点。

这里,首先学习如何创建一个For循环。For循环的创建方法与While循环的类似,即在LabVIEW环境下,在函数选板上选中For循环,将其拖曳到程序框图面板上,按住鼠标左键、拖曳出合适的尺寸大小,然后放开鼠标,就创建了一个For循环。创建好的For循环如图4.18所示。其中,灰色边框内的空白区域放置循环体代码;灰色边框内左上角的N为循环总数端子;灰色边框内左下角的“i”是重复端子,由它可输出已执行循环的次数。

图4.18 For循环

图4.18所示的For循环是LabVIEW中的默认状态,在这种模式下,For循环的执行机理是先判断、后执行,亦即先判断i是否在0到N-1之间,如果是,再执行循环。通常,For循环的执行次数由左上角的循环总数N的具体数值来确定。

选中For循环,右击,如图4.19(a)所示,选中“条件接线端”,具有条件接线端的For循环如图4.19(b)所示。此条件下,For循环的循环次数便改由其灰色边框内右下角的条件端子与左上角的循环总数端子共同决定,取两者中的最小值。下面通过例4.6练习对它进行使用。

【例4.6】 带条件端子的For循环。

此例VI的程序框图如图4.20所示。其中,循环总数端子接入数值100,VI要对计数端子i中的数值进行判断,当其大于10时,就退出循环。如此,运行该VI,当i=11时,循环退出。此实例表明,带条件端子的For循环的循环次数,是由条件端子的判断结果和循环总数端子共同决定的。学习者可以将此VI中循环总数端子的赋值改为10,而将循环体内的判断改为100,再运行程序,并观察VI的运行结果。

图4.19 添加有条件端子的For循环

图4.20 例4.6程序框图

4.3.3 循环结构内外的数据交换

按实际需求,利用LabVIEW编写VI时经常会使用循环结构。VI中循环结构的数据是通过隧道进出循环的,按LabVIEW的规则,循环外的数据会在循环运行开始前进入循环;而循环结构内的数据,则是在循环运行结束后才输出到循环体外。即循环结构内外进行数据交换依据的准则是:执行循环之前,读入一次数据;循环结束后,输出一次数据。下面通过例4.7对上述准则进行解释。

【例4.7】 循环结构内外数据交换举例。

图4.21 例4.7 VI的程序框图

例4.7 VI的程序框图如图4.21所示,运行此VI,可体会循环结构内外的数据是如何进行交换的。

在LabVIEW环境下,首先在前面板上对控件“x”和控件“y”进行赋值,比如给它们都输入数值1,然后运行此VI,在前面板上观察“乘积1”和“乘积2”的值。

在前面板上改变控件“x”的值,会发现“乘积1”的值并未改变。这是因为控件“x”在循环外,该VI运行时,只会在刚开始读取一次控件“x”的值;而进入到循环体后,就不会再去读取控件“x”中的值了。所以,控件“x”值的改变,对后面的乘法计算结果不会产生任何影响。但改变控件“y”的值会发现,“乘积1”的值也会跟着改变,而“乘积2”的值并未改变。这是因为,控件“y”位于循环体内,每次执行循环,都会读取控件“y”中的值,而“乘积1”也位于循环内,所以“乘积1”的值会随着控件“y”值的改变而改变;但由于“乘积2”位于循环体外,只有在前面板按下VI的停止按钮,VI才会将循环边框上隧道输出的值赋给“乘积2”控件。

在刚接触图形化语言时,有一个思维方式转换的过程。对于“循环内外数据如何交换”这一点,可以形象地这样来想,即可以想象成有一位搬运工将控件“x”中的值,先搬到循环的边框上,如此,这位搬运工的任务就完成了。那么,While循环每次读的数,就都是搬运工放在循环边框上的数。搬运工不会再将控件“x”的值搬到循环边框上去,所以控件“x”的值改变,不会影响到循环边框上隧道的值。也就是上面所介绍的,LabVIEW只会在循环开始前读取数据一次。那么相同的道理,对于输出,只有当循环结束后,才会将循环边框上隧道内的数值通过连线送给循环外的显示控件。

有读者可能还会提出以下问题,如果想在循环内也能及时更新循环外控件的值,那该如何实现呢?学习者可以自己先进行思考,本教材会在第4.5和4.6节中做出解答。

4.3.4 自动索引

在LabVIEW中,当把一个数组接到循环结构上时,循环结构可以对该数组进行自动索引。例如,当把一个数组接到For循环时,自动索引被默认打开。隧道小方格呈空即“[ ]”状态。而当将一个数组接到While循环时,其自动索引被默认关闭,隧道小方格呈实心状态。自动索引的状态(打开或关闭),可以根据实际需求加以设置,实现方法是:将鼠标点在隧道边框上,单击右键,弹出快捷菜单,改变自动索引状态,设置自动索引的关闭或开启。

按照上述规则,当For循环(无条件端子)接上数组时,其循环次数由数组元素的个数和自己本身的循环总数端子的值共同确定,实际执行的循环次数取两者的最小值。而当While循环接上数组时,其循环的执行次数,仍然受自身的条件端子决定。下面通过例4.8和例4.9加深对循环的自动索引相关规则的理解。

【例4.8】 自动索引例1。

图4.22 例4.8 VI的程序框图和前面板

为例4.8编写的VI程序框图和前面板如图4.22所示。在例4.8的VI中,循环总数端子接入4,输入到For循环的一维数组有3个元素,分别是(1,2,3),且该数组接到For循环的自动索引是打开的,如此,运行此VI后,For循环会执行3次。而由于For循环输出侧的自动索引是关闭的,如此,该VI只会将最后一次循环的值输出,即运行此VI,最终输出的是数组里的最后一个元素的值3。

基于例4.8的VI,再介绍一下自动索引功能的打开与关闭是如何设置的。选中例4.8 VI中的For循环左边框上的隧道,右击,弹出快捷菜单,如图4.23所示,然后,就可以进行左边框隧道即输入隧道的自动索引状态的选择了。之后,选中For循环右边框上的隧道,右击,弹出快捷菜单,如图4.24所示,选择隧道模式,如果不需要每次循环都输出结果,而只要输出循环的最终结果,那就不选择索引,而单击选取最终值,如此,VI就会将最后一次循环的结果值输出到循环体外。

图4.23 For循环左边框上的(输入)隧道

图4.24 For循环右边框上的(输出)隧道

【例4.9】 自动索引例2。

为例4.9编写的VI程序框图和前面板如图4.25所示。在此VI中,For循环的循环总数端子设为2,一维数组(1,2,3)接到For循环上,输入隧道的自动索引功能处在关闭状态。如此,运行此VI,其中的For循环会执行两次,每次进入For循环体的代码都是同一个一维数组;而由于循环输出侧边框上隧道的自动索引是打开的,于是输出了一个二维数组,即输出的数组的行数为2,其中每一行都为输入的那个一维数组。

图4.25 例4.9 VI的程序框图和前面板

4.3.5 移位寄存器

在实际中,经常会碰到这样的编程需求:某个变量的当前值要依靠上次循环的结果做进一步运算才可得到,即需要进行迭代运算。利用LabVIEW如何实现这样的功能呢?

在LabVIEW中,可以利用循环结构的移位寄存器来实现迭代运算。移位寄存器的具体功能是:在当前的循环完成后,将其中程序代码执行的某个结果作为该循环下一次执行的输入。为循环结构添加移位寄存器的方法是:在循环结构的边框上右击鼠标,在弹出的快捷菜单中选择“添加移位寄存器”。

下面以While循环为例,说明移位寄存器的工作流程。如图4.26所示,在循环开始执行前,首先要对移位寄存器进行初始化,即要为循环左边框上的移位寄存器赋一个初始值;然后,初始值进入第一次循环,按其中的程序代码进行运算,之后,将运算结果赋给循环右边框上的移位寄存器;接着,循环右边框上的移位寄存器的值会回传到左边框上的移位寄存器,随即,再进入第二次循环,进行相应的运算;依次类推,循环会按照这样的机理运行下去,直到循环退出,然后,循环右边框上移位寄存器的值会输出到循环体外。

图4.26 移位寄存器的工作流程

有时,不仅需要保存前一次循环的值,还需要保存前面几次循环的值。为此,可以选中循环结构左边框上的移位寄存器,右击,选择添加元素来实现,具体如图4.27所示。在LabVIEW中,移位寄存器的左端子可以添加多个,但右端子只能有一个。

图4.27 为移位寄存器添加更多的左端子

常见问题5:移位寄存器有哪些特点?使用移位寄存器时需要注意什么?

移位寄存器是成对出现的(仅限于第一次生成),即在循环结构的边框上创建移位寄存器,会在其左边框和右边框同时生成。使用移位寄存器,一定要对其进行初始化,即在循环开始之前,一定给移位寄存器赋予初始值。刚生成的移位寄存器是未指定数据类型的,其应具有的数据类型,要由连入到移位寄存器左端子或右端子的数据来决定。

【例4.10】 编写一个求和(n从1到100)VI。

例4.10这个VI,要实现数值的简单累加运算。为此,利用文本式语言(C语言)实现的代码如图4.28所示;而利用LabVIEW实现的代码如图4.29所示。

图4.28 累加计算的C语言实现

图4.29 累加计算的LabVIEW实现

从图4.28和图4.29可以看出,利用C语言进行编程时,首先要定义变量。而在LabVIEW中,则无须定义变量,就直接用连线代表变量,且数据类型是由连线的颜色和形状表征的。

例4.10中,为实现累加算法,通常应利用循环结构。在具体的编程实现上,需要利用到上一次循环里的值。针对于此,C语言中利用了中间变量sum;而若使用LabVIEW编写此VI,一般就会用到移位寄存器(见图4.29中的VI,在While循环的边框上生成)。移位寄存器的功能类似于C语言中的中间变量sum,都可以实现保存上一次循环的值。

上面完成的累加计算的VI,是利用While循环实现的。由于累加计算编程中的循环次数是确定的,故改用For循环实现的代码要简单些,具体如图4.30所示。其中,由于循环计数端子i从0开始计数,所以循环总数端子赋值101。图4.31中还给出了以C语言的For循环实现的累加计算的代码。

图4.30 利用For循环实现的累加计算VI(LabVIEW)

图4.31 利用For循环实现的累加计算(C语言)

4.3.6 反馈节点

图4.32 利用反馈节点实现累加计算的VI

在LabVIEW中,还提供有反馈节点,其功能与移位寄存器相类似,利用它也可以实现迭代运算。找到反馈节点的路径是“函数”选板→“编程”→“结构”子选板。对于例4.10的功能,利用反馈节点编写出VI的程序框图如图4.32所示。其中,将“加”函数的输出结果连至“反馈节点”的右端子上,将“加”函数的一个输入端连至“反馈节点”的左端子上,“反馈节点”的下方为初始化端子,将一个数值常量0赋给它。

对图4.32所示的VI,选中反馈节点,右击,在弹出的快捷菜单中选择“将初始化器移出一层循环”(见图4.33),可以在循环体外对反馈节点进行初始化赋值,如此,修改后的VI的程序框图如图4.34所示。

图4.33 将反馈节点的初始化移出到循环外

图4.34 反馈节点的初始化在循环体外实施

常见问题6:反馈节点与移位寄存器有什么异同?

将反馈节点与移位寄存器进行比较,有如下几点值得关注:①可以将反馈节点转换为移位寄存器。具体实现方法是选中反馈节点,弹出图4.33所示的快捷菜单,选择“替换为移位寄存器”,就可实现反馈节点向移位寄存器的转换;②反馈节点只能保存上一次循环的值,如果要利用到上两次或上几次循环里的值,那就必须利用移位寄存器;③移位寄存器只能在循环结构中使用,而反馈节点不仅可以在循环结构中使用,在循环结构外也可使用;④同移位寄存器一样,使用反馈节点也要进行初始化操作。

4.3.7 综合示例及补充

本章到此,已经学习了三种基本的程序结构,分别是顺序结构、条件结构和循环结构。接下来,运用上述知识做两个综合练习,以加深对它们的理解和认识。

【例4.11】 已知一个有5个元素的一维数组(2,0,-1,-2,3),要求编写一个VI,将该数组中小于0的元素去除掉,用剩下的元素组成新的数组,并在前面板上显示出来。

看到这个题目,产生的一个解决思路,就是对数组中的每个元素进行逐一判断,如果该元素大于等于0,则保存下来;否则,不进行处理。在这个算法中,有重复过程,应该利用循环结构;而且数组元素的个数是确定的,所以使用For循环即可。而由于还需要对每个元素进行判断,所以还应该利用到条件结构。另外,要对符合条件(大于或等于0)的元素进行保存,故还要用到移位寄存器。

根据上述思路,编写出的VI的程序框图如图4.35和图4.36所示。其中,利用到了For循环结构与条件结构的配合。具体地,在For循环结构外,向For循环输入了一个数组常量,共有5个元素,分别是2、0、-1、-2和3,将其连接到For循环结构上,并且打开了For循环输入隧道的自动索引。在For循环的边框上创建了移位寄存器,用以保存上次循环的值。还利用条件结构,进行元素是否大于或等于0的判断,当为真时,利用“创建数组”函数,将当前元素与移位寄存器里的历史元素连接起来,形成新的数组并赋给移位寄存器的右边端子。

根据以往经验,不少初学者在编写条件结构的“假”分支程序段时,会出现困惑。对于条件判断,当为“假”时,应该不对相关元素做任何处理。如果利用C语言进行编程,用一个不带else的if语句就可以实现。而利用LabVIEW中的条件结构,却要求每一个分支都要为输出隧道赋值,也就是无须做任何处理的“假”分支,也要为输出隧道赋值,但对这个题目而言,判断为“假”时,应该不做任何处理。那么,在LabVIEW中该如何实现上述功能呢?

对这类问题,一个解决思路是,当为“假”时,直接将移位寄存器左边端子的值赋给条件结构的输出隧道。如图4.36所示,即当条件为“假”时,将移位寄存器的历史数据再输出一遍,如此,既可实现不对当前小于0的元素进行保存,又能保证条件结构的输出隧道有确定的输入值。

图4.35 例4.11中VI的程序框图(For循环结构+“真”分支条件结构)

图4.36 例4.11中VI的程序框图(For循环结构+“假”分支条件结构)

细心的读者会发现,这个例子有自己特殊的地方,即与循环结构搭配用到了移位寄存器,如此,就可以将移位寄存器的值赋给“假”分支的输出隧道。而如果有的问题不需要移位寄存器,即仅需要实现图4.37所示的代码的功能,那么利用LabVIEW进行编程,又该如何实现呢?

图4.37 C语言中无else的if语句示例

此外,在LabVIEW 2012以后的版本中,对循环结构的隧道模式又增加了新的功能,即利用LabVIEW 2012以后的版本,对例4.11所希望实现功能VI的编写更为简单了。具体地,如图4.38所示,无须再调用条件结构,而是将数组常量连至For循环结构的右边框上,选中生成的隧道(自动索引默认打开),右击,在弹出的快捷菜单中选择“隧道模式”→“条件”,可以看到,在隧道下端就出现了一个“问号”的条件端子,将大于或等于0的输出端连至此条件端子上,如此编写的VI的程序框图如图4.39所示。运行此VI,在前面板上观察结果,会发现也得到了正确的结果,即数组中小于0的元素被去除掉了。

图4.38 For循环结构的隧道模式设置

图4.39 例4.11中VI的程序框图(For循环结构)

例4.11是一个综合应用前述相关知识的例子。同时,通过例4.11,也对循环结构中的隧道模式做了补充介绍。

在第2章中,已经学习了如何在LabVIEW中创建一个子VI,然后再在新的VI中调用此子VI。第2章示例中的子VI实现的功能较为简单(求两个数的平均数),目的是为了让学习者掌握创建子VI的方法。在实际应用中,有时会碰到如下需求,在调用子VI时,需要弹出它的界面,在它的界面中设置一些输入参数,然后,再将计算结果返回到主VI中。那么,利用LabVIEW该如何编程实现这样的功能呢?

【例4.12】 创建一个可以弹出界面的子VI。

首先,可以对求两个数平均数的子VI做一些修改,即应该将图2.11所示的VI放入一个While循环内,并且为While循环灰色线框内右下角的条件端子添加一个输入控件。如此改造成的VI的程序框图如图4.40所示,为了区别于前面所见的求平均数的子VI,可将此子VI的标签设为“平均数While”。

图4.40 加上While循环的求平均数子VI

然后,在一个新的VI中调用“平均数While”子VI,其程序框图如图4.41所示。为此VI创建一个显示控件Result,选中此VI,右击,选择“设置子VI节点”,在弹出的界面中选中“调用时显示前面板”和“如之前未打开则在运行后关闭”两个选项,并单击“确定”按钮。为了使VI的运行功能更完整,最后调用子VI的VI程序框图如图4.42所示。运行此VI,单击“求平均数”,会弹出“平均数While”子VI的界面,如图4.43所示,然后,在弹出的界面上设置输入参数,单击“确定”按钮,退出子VI界面,并将计算结果输出到主VI界面中,如图4.44所示。

图4.41 子VI节点设置

图4.42 主VI的程序框图和前面板

图4.43 调用子VI的界面

图4.44 主VI的运行结果

此例中,在子VI中调用了一个While循环,其条件端子上连的是一个按钮。在这种程序结构下,调用子VI时,一定要通过选中“调用时显示前面板”和“如之前未打开则在运行后关闭”,以确保调用子VI时会弹出子VI界面,然后,在此界面上单击“确定”按钮,以使得While循环退出。否则,因无法使子VI中的While循环停止,程序会陷入死循环中。