



循环可用来控制程序中要进行的重复性操作。LabVIEW中包括For和While两种形式的循环。
    For循环按设定的总次数执行子程序框图,循环总数可以通过将循环外部的数值连接到For循环的总数接线端(Count Terminal)
     来手动设定或使用自动索引来自动设定,已经执行完成的循环次数由循环计数器接线端(Iteration Terminal)
    来手动设定或使用自动索引来自动设定,已经执行完成的循环次数由循环计数器接线端(Iteration Terminal)
     实时更新输出(图4-14(a))。
    实时更新输出(图4-14(a))。
   
循环总数和循环计数器接线端都是32位有符号整数。如将一个浮点数或定点数连接到总数接线端,LabVIEW将对其进行取整,并将其强制转换到32位有符号整数的范围内。如果将0或负数连接到总数接线端,该循环将无法执行并在输出中显示该数据类型的默认值。第一次执行循环时,循环计数器接线端会返回0,随后每次按1递增。如果循环计数器的次数超过了2 31 次(计数器的值超过2147483647),循环计数器接线端将在此后的循环中保持在2147483647。如果需要使用大于2147483647的循环计数器,则需使用表示能力更大的移位寄存器。
 
    图4-14 For循环的几种形式
    通常情况下,For循环运行的总次数通过连接到总数接线端的值来控制,但是有时也可以为For循环添加一个条件接线端,来控制For循环在满足某种条件时结束循环执行。循环的条件接线端,可以是布尔数据(如一个布尔输入控件或一个比较函数的输出),也可是一个错误簇。如果For循环同时含有多个结束条件,将以最先实现的条件为准来控制循环结束。右击循环边框并从弹出的菜单中选择“条件接线端”选项,可为For循环添加或删除一个条件接线端。含有条件接线端的For循环右下角会包含一个条件接线端子图标(Conditional Terminal)
     ,并且计数接线端的外观也会改变,如图4-14(b)所示。带条件接线端的For循环,其条件接线端必须连接,同时循环的总数必须通过总数接线端或自动索引指定。
    ,并且计数接线端的外观也会改变,如图4-14(b)所示。带条件接线端的For循环,其条件接线端必须连接,同时循环的总数必须通过总数接线端或自动索引指定。
   
如果设定条件接线端为“真时停止”(Stop If True),For循环将在收到TRUE值、循环达到总循环数或自动索引的最大次数时停止执行。右击条件接线端并从弹出的菜单中选择“真时继续”(Continue If True)选项,条件接线端的动作和外观均改变(图4-14(d))。For循环将执行其子程序框图,直到条件接线端接收到一个FALSE值。
包含条件接线端的For循环,只有在连接到条件接线端的条件从未出现时,循环的总次数才是循环的最大次数。当使用的循环没有最大循环次数,并且要求它在某个条件出现时停止执行,就可以使用While循环。
While循环类似于文本编程语言中的Do-While循环或Repeat-Until循环,它持续执行子程序框图直到满足某个指定的条件为止。While循环与For循环都可以包含条件接线端,但由于For循环同时还包括一个固定的循环总数,因此即使条件未发生,循环也不会无限运行下去;相反,对While循环却不能设定循环执行的总数,因此如果条件未发生,循环会无限地运行下去。另外,与For循环不同,任何While循环都至少要执行一次。如图4-15所示,当Stop控件的初始值为False时,While循环将执行一次,循环结束后Counter的值为1。
与For循环类似,While循环也含有“循环计数器接线端”和“循环条件接线端”,而且它的条件接线端也可被设置为“真时停止”(Stop If True)或“真时继续”(Continue If True)。此外,使用While循环的条件接线端也可进行基本的错误处理。将错误簇连接到条件接线端时,仅有错误簇中状态参数的TRUE或FALSE值被传递到该接线端,并且“真时停止”和“真时继续”快捷菜单选项也相应地分别变为“错误时停止”和“错误时继续”。
 
    图4-15 While循环将执行一次
默认状态下,LabVIEW尽可能迅速地执行循环和程序框图,但是,有些情况下可能需要控制进程的执行速度,如数据的刷新速度等。此时,在循环中使用“等待”(Wait)函数可指定循环在重新执行之前等待的时间,时间的单位为ms(毫秒)。当然也可使用定时循环在限定时间和延迟的条件下执行代码。在应用程序中有时候可能包含多个循环,例如使用多个While循环创建多通道数据处理程序结构(后续章节详细介绍)来并行处理数据,这时,使用“等待”(Wait)函数为每个While循环设置一个时间间隔尤为重要。因为这个简单的举动会取消某个循环对CPU的独占,使CPU能有机会来处理其他循环的操作请求。
LabVIEW中的For循环和While循环具有自动索引(Indexing)的功能。在设计时,可以为传递至循环中的输入数组或循环的输出数组启用自动索引功能。当为输入数组启用数据索引功能时,无须使用其他数组索引函数,仅通过循环就可以自动识别数组总长度,并可访问和处理数组中的各个元素。循环每执行一次,就会有一个数组元素按顺序进入循环。如果输入数据是一维数组,则循环会从中提取标量,如果是二维数组,则提取一维数组件,以此类推。当为输入数组禁用自动索引时,整个数组将一次性全部传递到循环中。启用数组输出通道的自动索引功能时,该输出数组从每次循环中接收一个新元素,输出一个大小与重复的次数相等的新数组。例如,如循环执行了10次,那么输出数组就含有10个元素。与输入通道的情况相反,循环中的标量元素会按顺序累积形成一维数组输出,一维数组会累积形成二维数组输出,以此类推。如果禁用输出通道上的自动索引,仅有最后一次循环执行时的值被传递到程序框图上的下一个节点。
For循环的自动索引功能默认为启用,While循环的自动索引功能默认为禁用。右击循环外框上的通道,从弹出的菜单中选择“启用索引”(Enable Indexing)或“禁用索引”(Disable Indexing)选项,可以启用或禁用循环的索引功能。已启用了循环的自动索引的边框上将出现方括号。另外,输出通道和下一个节点间连线的粗细也表示循环是否正在使用自动索引。使用自动索引时,连线较粗,因为此时连线上包含一个数组而不是一个标量。
对For循环来说,使用自动索引功能可以设置循环的总次数。如果启用自动索引功能,无须设计人员指定,LabVIEW会自动将循环执行的总次数设置成与数组大小一致。如果有多个通道启用自动索引,或对总数接线端进行连线,实际的循环次数将取其中较小的值。例如,如果两个启用自动索引的数组进入循环,分别含有10个和20个元素,同时将值15连接到总数接线端,这时该循环仍将只执行10次,并且会索引第一个数组的所有元素,索引第二个数组中的前10个元素。再如,在一个图形上绘制两个数据源,并只需绘制前100个元素,这时可将值100连接到总数接线端。然而,如果较小的数据源只含有50个元素,那么循环将执行50次,并且只索引每个数据源的前50个元素。
对While循环来说,虽然它可以对进入循环的数组采用与For循环相同的方式进行索引,但是由于它只有在满足特定条件时才会停止执行,因此它的执行次数并不会受到输入数组大小的限制。当While循环的次数超过输入数组的大小时,LabVIEW会将输入数组元素类型的默认值输入到循环。通过使用“数组大小”函数可以防止将数组默认值传递到While循环中。为了避免这种情况发生,可以使用“数组大小”(Array Size)函数设置While循环的总次数,使得循环次数等于数组大小时停止执行。
对比For循环和While循环的自动索引功能可以看出,在不能提前确定输出数组大小的情况下,启用For循环的自动索引比启用While循环的自动索引更有效。另外,如将0或负数连接到For循环的总数接线端,或将空数组作为输入连接到For循环并且启用自动索引,For循环将不会执行。
输出通道的自动索引还具有条件输出(Conditionally Writing Values)功能。该功能可以基于某一条件是否被满足,来确定数据值是否被添加到输出通道输出。虽然条件输出功能可以通过LabVIEW程序代码以其他方式实现,但是使用它却可以有效简化LabVIEW程序代码。例如图4-16(a)中程序代码仅限定当Data in的值大于0时,才将其值添加到数组中并输出到Data out,该程序代码可以很方便地通过图4-16(b)中的条件输出索引来实现。
 
    图4-16 使用条件输出自动索引简化程序代码
在循环中使用移位寄存器(Shift Register)可以将上一次循环的值传递至下一次或后续循环中。移位寄存器以一对接线端的形式出现,分别对称于循环两侧的边框上。LabVIEW将左侧接线端的数据作为下一次循环的初始值,循环右侧接线端含有一个向上的箭头,用于存储每次循环完成进入下次循环时的数据值。每次循环执行后,数据将从移位寄存器右侧接线端传递到左侧接线端供下次循环使用,该过程在所有循环执行完毕后结束。右击循环的左侧或右侧边框,并从弹出菜单中选择“添加移位寄存器”选项,可以创建一个移位寄存器。移位寄存器可以传递任何数据类型,并自动与其连接的第一个数据对象的类型保持一致。如循环中的多个操作都需使用上一次循环的值,则可以添加多个移位寄存器保存结构中不同操作的数据值。例如在图4-17(a)所示的程序框图中,右上角的移位寄存器将第一次循环中0与2之和传递到左上角的移位寄存器接线端,作为加运算第二次循环的初始值。右下角的移位寄存器接线端将第一次循环中1与2之积传递到左下角的移位寄存器接线端,作为乘运算第二次循环的初始值。第二次循环将2和2相加,并将结果4传递到左上角的移位寄存器接线端,将2和2相加的结果与1与2之积相乘传递到左下角的移位寄存器接线端用于第三次循环。整个10次循环结束后,右上角的接线端将加运算的最终结果传递到上方的显示控件,右下角的接线端将乘运算的最终结果传递到下方的显示控件。
 
    图4-17 循环中使用移位寄存器
通过连接输入控件或常数至循环左侧的移位寄存器接线端,可设置第一次传递给循环的值,这称为移位寄存器的初始化。如果未对移位寄存器初始化,在循环未执行过的情况下将使用数据类型的默认值,如果循环之前已经执行过,则使用最后一次执行时写入寄存器的值。如图4-17(b)所示,移位寄存器的初始值设置为0,For循环将执行5次,每次循环后,移位寄存器的值都增加1,5次循环结束后,移位寄存器会将最终值(5)传递给显示控件并结束VI运行。
使用未初始化的移位寄存器可以保留VI多次执行之间的状态信息。如图4-17(c)所示。For循环将执行5次,每次循环后,移位寄存器的值都增加1。第一次运行VI时,移位寄存器的初始值为0,即32位整型数据的默认值。For循环完成5次循环后,移位寄存器会将最终值(5)传递给显示控件并结束VI运行。而第二次运行该VI时,移位寄存器的初始值是上一次循环所保存的最终值(5)。For循环执行5次后,移位寄存器会将最终值(10)传递给显示控件。如果再次执行该VI,移位寄存器的初始值则是10,以此类推。关闭VI之前,未初始化的移位寄存器将保留上一次循环的值。
有时需要用到连续几次循环的数据,例如将连续三个采样数据进行平均,这时可以使用层叠式移位寄存器(Stacked Register)。右击循环上的移位寄存器的左侧接线端,从弹出的菜单中选择“添加元素”选项,可以创建层叠式移位寄存器或改变它的个数。层叠式移位寄存器可以保存之前多次循环的值,并将值传递到下一次循环中。它只位于循环左侧,与之对应的右侧接线端仅用于把当前循环的数据传递给下一次循环。如图4-18(a)所示,层叠式移位寄存器在功能上相当于一个小型的队列缓冲区,寄存器的数量决定了缓冲区的大小,每执行一次循环,缓冲区中的数据自动按照先进先出(FIFO)的原则进行一次更新。在图4-18(b)中,左侧接线端上添加了数量为5的层叠式移位寄存器,则连续5次循环的值将传递至接下来的循环中,其中最近一次循环的值保存在最上面的寄存器中,而上一次循环传递给寄存器的值则保存在与之相接的下一个接线端中,以此类推。
 
    图4-18 循环中使用移位寄存器保留多个状态信息
除了使用寄存器外,还可以在循环中使用数据通道,就像在顺序和分支结构中一样传递数据。使用数据通道时,循环中的值不再传递到下一次循环中,而是在循环结束时直接将循环体中对数据的更新结果传递到循环体外。
循环中的移位寄存器节点和数据通道节点可以互相转换。当不再需要将循环中的值传递到下一次循环时,右击移位寄存器,从弹出的菜单中选择“替换为通道”选项,可将移位寄存器替换为数据通道。由于默认情况下For循环会为数据通道启用“自动索引功能”,所以连接到循环外部任何节点的连线都将断开。右击隧道并从弹出的菜单中选择“在源处禁用索引”选项,禁用自动索引后会自动纠正断线。如需启用自动索引,必须删除断线和显示控件接线端,重新创建输出类型匹配的显示控件;相反地,如需将循环中的值传递到下一个循环中,可以右击数据通道并从弹出的菜单中选择“替换为移位寄存器”选项,将通道替换为移位寄存器。
反馈节点(Feedback Node)与移位寄存器从实现的功能角度来看相同,但是由于其类似反馈控制理论和数字信号处理中的z-1块,而且它能在某些情况下代替移位寄存器简化程序框图,因此LabVIEW从7.0版本开始引入了反馈节点。与移位寄存器类似,反馈节点在每次循环结束后存储数据,再将数据传递到下一次循环。下一次循环将读取数据进行运算,并将新的数据再次传递到循环中,该过程直到循环完成后中止。但是反馈节点的连线方式与移位寄存器不同,使用时用户不需要将连线从循环的一个边框连接到另外一端。另外,反馈节点不能像移位寄存器那样被扩展为层叠式移位寄存器来保存之前多次循环中的数据。
图4-19给出了分别使用反馈节点和移位寄存器实现4个1相加的例子。两个框图最终实现的功能相同,但是使用移位寄存器时可以将其扩展到层叠形式,保存之前多次循环中的值,而使用反馈节点时仅能保存之前一次循环的值。另外,使用反馈节点不必像移位寄存器那样将连线横跨循环左、右边框。在比较复杂的程序结构中,这可以有效减少贯穿于循环结构上的连线数量,从而有效地增加程序的可读性。然而,需要注意的是,反馈节点与LabVIEW数据流从左到右的规则有冲突,而且反馈节点在目前LabVIEW中的运行效率不及移位寄存器。
 
    图4-19 反馈节点和移位寄存器
可以通过图4-20所示的两个简单例子来验证移位寄存器和反馈节点的执行效率,在LabVIEW 8.61版本中分别运行两个程序后发现,使用反馈节点的程序耗时12796ms,而使用移位寄存器的程序耗时仅9728ms。鉴于此原因,除非必要,在设计时笔者通常尽量避免使用反馈节点。
反馈节点的初始化根据初始化接线端位置的不同可分为以下两种情况:
(1)反馈节点的初始化接线端位于循环的左侧。此时在程序中必须为初始化接线端连接一个初始值,同时反馈节点将在循环第一次执行前被初始化。
(2)初始化接线端并不在循环的左侧,而是位于反馈节点下端。此时,反馈节点会被全局初始化(Globally Initialize)。在反馈节点被全局初始化的情况下,反馈节点会按照以下原则运行:
● 如果反馈节点已经设置了初始值,反馈节点将在VI的第一次执行中初始化为该值。
● 如果反馈节点没有设置初始值,反馈节点第一次执行的初始输入为适于其数据类型的默认值。第一次运行之后,VI每次运行时会将上次VI运行结束时反馈节点的值作为反馈节点的初始值。
 
    图4-20 反馈节点和移位寄存器的执行效率
右击反馈节点的初始化接线端,从弹出的菜单中选择“将初始化器移出一个循环”(Move initializer one Loop Out)或“将初始化器移入一个循环”(Move initializer one Loop In)选项,可以移动初始化接线端至循环的左侧(若有多个循环嵌套,可在循环之间移动)。此时,必须为其连接一个初始值,它将在循环第一次执行前初始化。如果初始化接线端在最外层或最内层循环的边框上,如图4-21所示,则LabVIEW将禁用右键菜单中的“将初始化器移出一个循环”或“将初始化器移入一个循环”选项。
图4-21中,尽管反馈节点位于For循环内部,但初始化接线端在最外层While循环的边框上,于是反馈节点在每次While循环执行时被初始化为输入值2,而加1函数的结果在每次For循环执行时逐次递增。图4-22显示了一个初始化接线端已连线的反馈节点以及一个初始化接线端未连线的反馈节点。在经过数次执行后它们得到的结果完全不同。图4-22(a)中的反馈节点在第一次执行后被初始化,故每次循环后值不改变。图4-22(b)初始化接线端未连接初始值,则反馈节点初始化接线端使用了数据类型的默认值0作为第一次循环的初始值,且在此后循环中不对反馈节点进行初始化。两个循环的运行结果分别如图4-22中所示。
 
    图4-21 初始化接线端在最外层循环的边框上
 
    图4-22 初始化接线端已连线和未连线的处理结果
虽然在循环和嵌套循环中可将节点和初始化接线端隔开,但不可将初始化接线端移到含有节点的嵌套结构的外部。创建子VI时也不能将节点与初始化接线端隔开。
实际开发中,使用顺序、分支和循环三种基本程序控制结构足以对付各种简单的问题。然而现实世界中的问题复杂多变,往往会对设计人员提出更高的要求。为了快速解决更为复杂的问题,LabVIEW额外提供了事件结构、定时结构和禁用结构。将这些扩展的程序结构与基本的程序结构相结合,再配合上有效的数据传递手段(如消息队列)就可以构建高级的应用程序框架,这些内容将在本书的第8章、第9章详细介绍。