Octave 中的敘述(statements)可以是簡單的常數或是複雜的運算式與判斷式。控制敘述(control statements)可以控制 Octave 程式的執行,例如:if 或 while 等敘述。許多控制敘述中還會包含了其他的敘述,例如 if 敘述就常常包含了一些可執行或是不可執行的敘述。
每一個控制敘述都會包含一個對應的結束敘述,例如 endif 就是表示 if 敘述的結束,endwhile 則表示 while 敘述的結束,所有的結束敘述都可以簡寫成 end,但建議還是以完整的結束敘述為主,因為使用完整的結束敘述可以提供 Octave 更多資訊以偵測程式上可能發生的錯誤(例如漏寫了結束敘述等)。
介於控制敘述(例如 if)與其對應的結束敘述(例如 endif)之間的敘述,稱為此控制敘述的主體(body)。
if 敘述(The if Statement)
if 敘述會根據使用者指定的條件判斷式而決定是否要執行某一段程式,此敘述有三種用法,最簡單的就像這樣:
if (condition)
# then-body
endif
if 敘述會根據 condition 的結果來決定是否要接下去執行,若 condition 的結果為 true,則會執行 then-body 部分的程式碼。而 condition 的計算結果若為0,則會被視為 false,若不是0 則視為 true;若 condition 的結果是一個向量,則只有在向量中所有的元素都是0 的情況下才會被視為 false,其他的情形都會被視為 true。
if 敘述的第二種使用方式是多加入一個 else 敘述:
if (condition)
# then-body
else
# else-body
endif
若 condition 的結果為 true 則執行 then-body,否則執行 else-body。例如:
if (rem (x, 2) == 0)
printf ("x is even\n");
else
printf ("x is odd\n");
endif
在這個列子中,若是 rem (x, 2) == 0 的結果為 true(即 x 被 2 整除),則第一個 printf 會被執行,否則執行第二個 printf。
if 敘述的第三種使用方式(也是最常見的方式)是再加入一個 elseif 敘述,這樣可以允許加入多個不同的判斷式:
if (condition)
# then-body
elseif (condition)
# elseif-body
else
# else-body
endif
elseif 敘述的個數沒有限制,這些判斷式會依照順序執行,當其中一個 condition 的結果為 true 時,則會執行其對應的程式碼,之後剩餘的 elseif 則會直接跳過,不會繼續檢查;若每一個判斷式皆為 false,則會執行 else-body。else 敘述必須放在最後一個位置,並且一組 if 敘述中最多只能有一個 else 敘述。
下面的範例中,當 rem (x, 2) == 0 的結果為 true 時(即 x 被 2 整除),則會執行第一個 printf,若其結果為 false,則會繼續判斷第二的 elseif 中的 rem (x, 3) == 0,若其結果為 true(即 x 被 3 整除),則會執行第二個 printf;若兩個判斷式的結果都是 false,則會執行最後一個 printf。
if (rem (x, 2) == 0)
printf ("x is even\n");
elseif (rem (x, 3) == 0)
printf ("x is odd and divisible by 3\n");
else
printf ("x is odd\n");
endif
elseif 不能像 Fortran 語言一樣拆成 else if,如果拆成這樣的寫法,Octave 視為在一個 else 敘述中包含另外一個 if 敘述,例如:
if (c1)
# body-1
else if (c2)
# body-2
endif
若是這樣寫 Octave 會將最後一行的 endif 視為第二個 if 敘述的結束,若是使用互動是的命列列輸入,則 Octave 會繼續等待使用者輸入程式碼,直到輸入了第一個 if 敘述的 endif 為止;若從檔案中載入程式碼,則 Octave 可能會出現警告訊息或是直接產生錯誤的結。將此程式碼重新排版後,更可以看出問題所在:
if (c1)
# body-1
else
if (c2)
# body-2
endif
這樣就可以很明顯的看出最後少了一個 endif 敘述。
switch 敘述(The switch Statement)
在撰寫程式時常常會需要依照某一個變數的值來決定執行哪一段程式碼,這種情況可以使用 if 敘述,例如:
if (X == 1)
do_something ();
elseif (X == 2)
do_something_else ();
else
do_something_completely_different ();
endif
不過這樣的寫法非常累贅,Octave 亦支援專門用於此情況的 switch 敘述,上面的程式碼可以使用此敘述改寫為:
switch (X)
case 1
do_something ();
case 2
do_something_else ();
otherwise
do_something_completely_different ();
endswitch
使用 switch 敘述可以讓程式碼看起來更簡潔也更容易閱讀,在維護上也更方便,例如若要將變數 X 換成另一個變數名稱,只需要更改第一行,若是使用 if 敘述則需要更改每一個判斷式。
switch 敘述的標準使用方式為:
switch expression
case label
command_list
case label
command_list
# ...
otherwise
command_list
endswitch
其中 label 可以是任何算式,若是有重複的 label,則只有對應到第一個符合的 label 的 command_list 會被執行,剩餘的會被忽略。在 switch 敘述中至少要有一個 case 敘述(否則這個 switch 敘述就沒有意義了),而最後的 otherwise 敘述則是可有可無。
若 label 為一個巢狀陣列(cell array),則當此巢狀陣列中的任何一個元素符合 expression 時,就會執行其對應的 command_list,例如:
A = 7;
switch A
case { 6, 7 }
printf ("variable is either 6 or 7\n");
otherwise
printf ("variable is neither 6 nor 7\n");
endswitch
其輸出為
variable is either 6 or 7
endswitch 敘述與其他敘述一樣可以用 end 取代,但建議還是使用 endswitch 以避免錯誤發生。
使用 switch 敘述而不使用 if 敘述的另外一個好處是 label 可以為字串,if 無法直接判斷字串是否相等,例如:
x = "test";
if(x == "another")
y = 1
endif
因為 x == "another" 會執行字元對字元的比對,所以這樣會產生錯誤:
error: mx_el_eq: nonconformant arguments (op1 is 1x4, op2 is 1x7)
而 switch 可以判斷字串是否相等:
x = "test";
switch (x)
case "another"
y = 1
case "test"
y = 2
endswitch
輸出為
y = 2
在 C 語言中也有 switch 敘述,但其用法與 Octave 的 switch 有些差異:第一個差異是 Octave 中的每個 case 預設就是獨立的,執行完一個 case 就會跳開,不需要像 C 語言使用 break。另一個差異是 Octave 中每個 case 的 command_list 是不可省略的,例如 C 語言可以這樣寫:
switch (foo) {
case 1:
case 2:
doit ();
}
但在 Octave 中必須改寫成這樣:
switch (foo)
case {1, 2}
doit();
endswitch
while 敘述(The while Statement)
程式中的迴圈(loop)可以在符合指定條件的情況下,重複執行某一段程式碼。Octave 中最簡單的迴圈結構就是 while 敘述,它會在指定的條件為 true 時重複執行其中的程式碼,其判斷方式與 if 敘述相同,當值為0 時則視為 false,否則視為 true,若為向量或矩陣,則只有當向量或矩陣不是空矩陣且所有的元素值都不是0 時,才視為 true,否則視為 false。
Octave 中 while 敘述的使用方式為:
while (condition)
body
endwhile
其中 body 可以是任何的敘述,當 condition 為 true 時,就會執行 body 一次,執行完 body 再重新檢查 condition,若為 true 則繼續執行 body,不斷重複直到 condition 為 false 為止。若第一次檢查 condition 就是 false,則 body 就不會被執行。
下面的範例會產生一個含有 Fibonacci 數列的變數 fib:
fib = ones (1, 10);
i = 3;
while (i <= 10)
fib (i) = fib (i-1) + fib (i-2);
i++;
endwhile
這裡的 body 含有兩個敘述。這個迴圈一開始的 i 是設為 3,然後 while 會檢查 i 是否小於或等於 10,若 i 小於或等於 10 則會將 fib 的第 i 個元素設為其第 i-1 與第 i-2 個元素的和,然後再將 i 的值加 1,重複這個動作直到 i 增加到 11 為止。
while 敘述與 body 之間可以不需要換行,不過除了很簡單的迴圈之外,使用換行可以使程式碼更容易閱讀。
do-until 敘述(The do-until Statement)
do-until 敘述與 while 敘述類似,但他是重複執行指定程式碼直到指定的條件變為 true,另外他測試 condition 的地方是放在結尾,所以 body 的程式碼至少會被執行一次。do-until 敘述中 condition 的測試方式與 if 敘述相同,若其值為0 則視為 false,否則視為 true,若為向量或矩陣,則只有當向量或矩陣不是空矩陣且所有的元素值都不是0 時,才視為 true,否則視為 false。
do-until 的使用方式為:
do
body
until (condition)
body 可以是一個或多個敘述,而 condition 則用來控制要執行幾次 body。
下面的範例會產生一個含有 Fibonacci 數列的變數 fib:
fib = ones (1, 10);
i = 2;
do
i++;
fib (i) = fib (i-1) + fib (i-2);
until (i == 10)
在 do 與 body 之間可以不需要換行,不過除了很簡單的迴圈之外,使用換行可以使程式碼更容易閱讀。
for 敘述(The for Statement)
for 敘述基本用法
for 敘述可以明確的指定迴圈的執行次數,其使用方式為:
for var = expression
body
endfor
其中 body 可以是一個或多個敘述,expression 可以是任何的算式,而 var 則可以是很多形式,最常見的就是一個變數,但若 expression 傳回的是資料結構(structure),則 var 就必須要指定為一個含有兩個元素的向量。
在 for 敘述中的指定運算子與 Octave 中一般的指定運算子有些不同,它不會一次將 expression 的值整個指定給 var,而是依序將 expression 的每一個行(column)指定給 var。若 expression 是範圍(range)、列向量(row vector)或純量(scalar),則每一次指定給 var 的值會是一個純量;若 expression 是行向量(column vector)或矩陣,則每一次指定給 var 的值會是一個行向量。
下面的範例示範如何使用 for 迴圈建立 Fibonacci 數列:
fib = ones (1, 10);
for i = 3:10
fib (i) = fib (i-1) + fib (i-2);
endfor
這個 for 迴圈首先會執行 3:10 而得到一個範圍,然後將此範圍的第一個值(也就是 3)指定給 i,接著執行 body 部分的程式碼,執行完之後又將此範圍中的下一個值指定給 i,然後再繼續執行 body,這樣重複執行到範圍中所有的元素都指定完為止。
在 Octave 中可以使用 for 迴圈存取矩陣中的元素,例如:
for i = [1,3;2,4]
i
endfor
這個範例中 i 會依序被指定為矩陣的行向量,輸出為
i = 1 2 i = 3 4
巢狀陣列也可以這樣使用:
for i = {1,"two";"three",4}
i
endfor
輸出為
i =
{
[1,1] = 1
[2,1] = three
}
i =
{
[1,1] = two
[2,1] = 4
}這樣的用法可以拓展至更高維度的陣列上:
a = [1,3;2,4];
b = cat(3, a, 2*a);
for i = b
i
endfor
這個範例中,矩陣 b 會以 reshape (b, rows(b), prod(size(b)(2:end))) 的方式被轉換成二維的矩陣,然後再以一般矩陣的方式進行迴圈的運算,其輸出為
i = 1 2 i = 3 4 i = 2 4 i = 6 8
雖然所有的 for 迴圈都可以改寫成 while 迴圈,但由於這兩種迴圈各適用於不同的情況,因此 Octave 對這兩個迴圈都支援。
資料結構使用迴圈(Looping Over Structure Elements)
資料結構(structure)可以使用特殊的 for 迴圈來存取:
for [ val, key ] = expression
body
endfor
在這種 for 迴圈中,expression 必須指定為一個資料結構,而其中的欄位名稱與對應的值會依序被指定為 val 與 key,例如:
x.a = 1;
x.b = [1, 2; 3, 4];
x.c = "string";
for [val, key] = x
key
val
endfor
輸出為
key = a val = 1 key = b val = 1 2 3 4 key = c val = string
使用 for 存取資料結構時,其元素的順序是不固定的,若需要以指定的順序存取其中的元素,則必須以 fieldnames() 函數取得欄位名稱後自行排序。
break 敘述(The break Statement)
break 敘述可以跳出包含此 break 敘述且最內層的迴圈,此敘述只能用於迴圈的主體(body)之中,下面的例子會找出指定整數的最小因數,並且標明質數:
num = 103;
div = 2;
while (div*div <= num)
if (rem (num, div) == 0)
break;
endif
div++;
endwhile
if (rem (num, div) == 0)
printf ("Smallest divisor of %d is %d\n", num, div)
else
printf ("%d is prime\n", num);
endif
在 while 迴圈中的第一行,若判斷 num 除以 div 的餘數等於0,則直接跳出 while 迴圈,並繼續執行 while 迴圈之後的程式碼,這與 exit 敘述直接跳出整個程式是不一樣的。
下面的程式碼其功能與上面的相同,但這裡將 while 的判斷式以其中的 if 配合 break 敘述來取代:
num = 103;
div = 2;
while (1)
if (rem (num, div) == 0)
printf ("Smallest divisor of %d is %d\n", num, div);
break;
endif
div++;
if (div*div > num)
printf ("%d is prime\n", num);
break;
endif
endwhile
continue 敘述(The continue Statement)
continue 敘述跟 break 敘述一樣只能用於迴圈的主體之中,但 break 是直接跳出迴圈,而 continue 敘述會跳過目前的迭代,並從下一次的迭代開始執行,並不是跳出整個迴圈,例如:
num = 1:10;
for x = num
if(rem(x, 3) != 0)
continue;
endif
printf("%d\n", x);
endfor
這個例子會將 1 到 10 之中所有被 3 整除的數字列出來,在 for 迴圈中的第一行,若是判斷 x 除以 3 的餘數不是0,則使用 continue 跳過以下的程式碼(即 printf),進行下一個數字的檢查,其輸出為
3 6 9
這個範例只是要示範如何使用 continue 敘述,在實際的程式中,上面的程式碼可以改寫為更簡潔的形式:
num = 1:10;
for x = num
if(rem(x, 3) == 0)
printf("%d\n", x);
endif
endfor
unwind_protect 敘述(The unwind_protect Statement)
Octave 中提供了一個用於例外處理(exception handling)的 unwind_protect 敘述,其作用類似 Lisp 語言的 unwind-protect,用法為:
unwind_protect
body
unwind_protect_cleanup
cleanup
end_unwind_protect
body 與 cleanup 可以是任何的 Octave 敘述,並且都是選擇性的(可以省略),不管 body 的執行結果如何(即使有錯誤發生),最後 cleanup 的程式碼一定會被執行。
unwind_protect 敘述可以避免由於執行錯誤而導致更動到一些全域變數的情況,下面的範例中不管 body 執行的情況如何,全域變數 frobnosticate 都會保持原來的值:
save_frobnosticate = frobnosticate;
unwind_protect
frobnosticate = true;
% ...
unwind_protect_cleanup
frobnosticate = save_frobnosticate;
end_unwind_protect
若沒有使用 unwind_protect 敘述,則當前面的程式碼(body 部份)執行出現錯誤時,會直接離開此程式,位於後面的程式碼(cleanup 部份)將不會被執行。
try 敘述(The try Statement)
除了 unwind_protect 敘述之外,Octave 也支援另一種例外處理的 try 敘述,其用法為:
try
body
catch
cleanup
end_try_catch
其中 body 與 cleanup 可以是任何的 Octave 敘述,並且都是選擇性的(可以省略),cleanup 的程式碼只有在 body 執行出現錯誤時才會執行。
在 try 敘述中的 body 程式碼出現錯誤時,並不會輸出錯誤訊息,在 cleanup 中可以使用 lasterr() 函數來取得最後一次的錯誤訊息(關於 lasterr() 函數可以參考錯誤與警告),例如:
try
error("test message.")
catch
errmsg = lasterr();
printf("Error Message: %s\n", errmsg);
end_try_catch
輸出為
Error Message: test message.
try 與 catch 敘述的功能與 eval(try, catch) 相同,但其在執行的效率較高,因為 Octave 不需要在每一次執行前解析程式碼。
多行指令(Continuation Lines)
在 Octave 中的敘述都是以換行符號作為一個指令的結束,若要將一個指令分成多行來輸入,則可以在行尾加上多行連續符號 ... 或 \ 以接下一行,例如:
x = 1 + 2 + 3 ...
+ 4 + 5 \
+ 6 + 7
這樣會輸入一個跨越三行的敘述,反斜線若位於一行的最後一個字元會被當成連接行與行的符號,而不會被視為除法運算子。
只要不是使用在字串內,多行連續符號之後還可以再加上空白或註解,例如上面的敘述也可以這樣寫:
x = 1 + 2 + 3 ... # comment one
+ 4 + 5 # comment two
+ 6 + 7 # last comment
若是在字串當中使用多行連續符號,則必須放在一行的結尾,其後方緊接著換行字元。
若是將括號中的敘述分成多行,則可以不需要加入多行連續符號,例如:
if (fine_dining_destination == on_a_boat
|| fine_dining_destination == on_a_train)
seuss (i, will, not, eat, them, sam, i, am, i,
will, not, eat, green, eggs, and, ham);
endif
這樣可以不需要輸入讓人感覺難以閱讀的多行連續符號。