複雜的 Octave 程式可以經由定義函數的方式讓其架構更精簡,函數可以直接在命令列中定義或是寫在外部的檔案中,而其使用起來就像內建的函數一樣。
定義函數(Defining Functions)
要定義一個函數最簡單的方式就是
function name
body
endfunction
這樣會定義一個名稱為 name 的函數。函數的命名規則與一般的變數相同,可以使用英文字母、數字或下底線,但開頭不可以是數字,函數的命名空間與變數是共用的。body 部份包含了一個或多個敘述,其指定了此函數所要執行的程式碼,這是定義函數時最重要的部份。例如:
function hello
printf("Hello.\n");
endfunction
這樣會定義一個名稱為 hello 函數,呼叫此函數會使用 printf() 函數輸出字串 "Hello.\n"(關於 printf() 函數可以參考輸入與輸出)。當定義完一個函數之後,可以直接使用函數的名稱來呼叫此函數,例如:
hello
輸出為
Hello.
若要將變數傳入函數中,可以使用參數,使用方法為:
function name (arg-list)
body
endfunction
其中 arg-list 是一個包含傳入參數的逗點分個序列,當函數被呼叫時,這些參數的名稱就會用來當作變數名稱,儲存函數呼叫時所傳入的值。arg-list 可以是空的,這個情況就跟上面沒有使用小括號的定義方式相同。
將上面的例子加入傳入參數:
function hello(msg)
printf("Hello, %s.\n", msg);
endfunction
呼叫此函數
hello("world")
輸出為
Hello, world.
若要將函數中的運算結果傳回,則可使用指定運算子加入函數的傳回值:
function ret-var = name (arg-list)
body
endfunction
ret-var 是一個變數名稱,其儲存的值會作為整個函數的傳回值,這個變數必須在函數結束之前定義,以便可以讓函數作為傳回值。
在函數中所使用的變數都是此函數的區域變數,包含 arg-list 與 ret-var 也都是區域變數,若要在函數中存取全域變數,可以參考全域變數。
下面的例子會計算一個向量中每個元素的平均值:
function retval = avg (v)
retval = sum (v) / length (v);
endfunction
執行此函數:
avg([1, 3, 5, 7])
輸出為
ans = 4
若將上面的 avg() 函數改寫成這樣
function retval = avg (v)
if (isvector (v))
retval = sum (v) / length (v);
endif
endfunction
此時若是呼叫此函數而傳入矩陣
val = avg([1, 2; 4, 6])
則會出現警告訊息:
warning: avg: some elements in list of return values are undefined val = [](0x0)
這是因為當傳入矩陣時,if 判斷式中的程式碼將不會執行,因此導致 retval 沒有被定義,為了避免出現這種含糊不清的訊息,建議在定義函數時,將每一種可能發生的情況都考慮進去,並且確定傳回值都有被定義,在必要時也能輸出較詳細的錯誤訊息,例如這個 avg() 函數就可以改寫成這樣:
function retval = avg (v)
retval = 0;
if (isvector (v))
retval = sum (v) / length (v);
else
error ("avg: expecting vector argument");
endif
endfunction
這樣可以解決傳入參數型態不符合的問題,但是若在呼叫此函數時沒有傳入任何參數,則還是會出現錯誤。若沒有更詳細的錯誤檢查,Octave 所輸出的錯誤訊息常常會讓程式設計者很難追蹤錯誤所發生的位置,為了處理這類的問題,Octave 提供了一個特殊的內建變數 nargin,當函數被呼叫時,這個變數會自動被初始化,其會紀錄此函數在呼叫時所傳入的參數個數。上面的 avg() 函數可以更改成這樣:
function retval = avg (v)
retval = 0;
if (nargin != 1)
usage ("avg (vector)");
endif
if (isvector (v))
retval = sum (v) / length (v);
else
error ("avg: expecting vector argument");
endif
endfunction
在 Octave 中若是在呼叫函數時傳入的參數個數超過函數所定義的個數,預設是不會產生錯誤的,但是會出現這樣的情況代表在程式中的某個地方一定有錯誤;若是傳入的變數比函數所宣告的個數少,在函數中使用那些沒有傳入的變數將會導致錯誤。為了避免以上兩種錯誤,建議在函數中加入對於這兩種情況的檢查,並針對各種錯誤輸出詳細的錯誤訊息。
nargin ()
nargin (fcn_name)
若在函數中呼叫 nargin() 函數,則會傳回呼叫此函數時所傳入的參數個數;若在最上層呼叫 nargin() 則會傳回執行 Octave 時所傳入的命令列參數個數。nargin(fcn_name) 函數可以查詢函數 fcn_name 可以接受的最大參數個數,若傳回 -1 則表示此函數可以接受各種不同的參數個數。
inputname (n)
在一個函數中呼叫 inputname(n) 函數會傳回呼叫此函數時所傳入的第 n 個參數。
val = silent_functions ()
old_val = silent_functions (new_val)
silent_functions() 函數可以查詢或設定是否要顯示函數中程式碼的執行輸出,若這個選項設定為 false,則 Octave 會顯示函數中沒有使用分號結尾的指令執行結果,預設是設為 false。例如:
function test_output
x = 1
endfunction
執行 test_output() 函數
test_output
輸出為
x = 1
若是將 silent_functions() 設為 true:
silent_functions(true)
test_output
則執行 test_output 時就不會輸出。
多重傳回值(Multiple Return Values)
Octave 的函數與一般程式語言函數有一個很大的差異,Octave 的函數可以有一個以上的傳回值,其使用方式為:
function [ret-list] = name (arg-list)
body
endfunction
其中 name、 body 與 arg-list 與前面提到的函數用法相同,而 ret-list 是一個逗點分隔序列,其包含多個從此函數傳回的變數名稱,此逗點分隔序列中至少要包含一個元素,若其只包含一個元素,其效果就與前面介紹的單一傳回值的函數相同。
下面這個函數會傳會一個向量中的最大值與其出現的位置:
function [max, idx] = vmax (v)
idx = 1;
max = v (idx);
for i = 2:length (v)
if (v (i) > max)
max = v (i);
idx = i;
endif
endfor
endfunction
這個例子中也可以將兩個傳回值合併成一個向量傳回,但這個方式並不適用每一種情況,例如在每個傳回值有不同的維度時,就很難合併成一個向量,另外使用多重傳回值的方式,可以為每個傳回值命名,使用起來也較為方便。
在 Octave 的函數中,有一個內建的 nargout 變數,其會儲存呼叫此函數時,所要求的傳回值個數,這個變數會自動初始化,就像 nargin 一樣,這個功能可以讓函數依照不同的輸出參數而有不同的功能,例如內建的 svd() 與 lu() 函數就是這樣的函數。
若不指定傳回值而儲存在預設的
ans變數時,nargout不會將這個ans變數計算在內,此時的nargout的值為0。
函數的多個傳回值中可以只設定其中一部分的值,但這樣會出現一些警告訊息,例如:
function [x, y, z] = f ()
x = 1;
z = 2;
endfunction
執行 f()
[a, b, c] = f ()
輸出為
warning: f: some elements in list of return values are undefined a = 1 b = [](0x0) c = 2
沒有被設定的輸出參數,預設為空矩陣。
nargout ()
nargout (fcn_name)
在一個函數中呼叫 nargout() 函數可以傳回呼叫此函數時所要求的傳回值個數;nargout(fcn_name) 函數會傳回函數 fcn_name 最大的傳回值個數,若傳回 -1 則表示此函數可以傳回不固定個數的傳回值。例如:
f()
若這樣呼叫 f() 函數,則在 f() 函數中,nargout() 的值為0,而
[s, t] = f ()
若是這樣呼叫 f() 函數,則在 f() 函數中 nargout() 的值為 2。
nargout() 函數只適用於函數中,若是在最上層的命列呼叫,則會產生錯誤。
msgstr = nargchk (minargs, maxargs, nargs)
msgstr = nargchk (minargs, maxargs, nargs, "string")
msgstruct = nargchk (minargs, maxargs, nargs, "struct")
nargchk() 函數可用來檢查函數在呼叫時,是否有傳入正確個數的參數,若傳入的參數個數不正確,會傳回適當的錯誤訊息。此函數可用於檢查輸入的參數個數是否有在正確的範圍內。
msgstr = nargoutchk (minargs, maxargs, nargs)
msgstr = nargoutchk (minargs, maxargs, nargs, "string")
msgstruct = nargoutchk (minargs, maxargs, nargs, "struct")
nargoutchk() 函數可用來檢查函數在呼叫時,是否有指定正確個數的傳回值,若指定的傳回值個數不正確,會傳回適當的錯誤訊息。此函數可用於檢查指定的傳回值個數是否有在正確的範圍內。
可變長度參數列(Variable-length Argument Lists)
有的時候函數的參數個數在定義函數時還沒有辦法決定,例如一個傳回最小值的函數:
a = smallest (1, 2, 3);
b = smallest (1, 2, 3, 4);
所得到的 a 與 b 都是 1,若要定義這樣的 smallest() 函數有一個辦法是將函數定義為
function val = smallest (arg1, arg2, arg3, arg4, arg5)
body
endfunction
然後在 body 中以 nargin 變數判斷實際傳入的參數個數進而找出最小值,但這種方式只能用於有限個數的輸入參數。
Octave 針對這種不固定參數個數的函數,提供了一個特別的方式,就是將一個特別的參數 varargin 放在最後一個參數的位置,加入這個特殊參數表示這個函數可以接受不過定的參數個數,例如上面的 smallest() 函數可以這樣定義:
function val = smallest (varargin)
body
endfunction
在函數的 body 中,輸入的參數可以經由 varargin 變數來取得,此變數是一個包含輸入參數的巢狀陣列(cell array,請參考巢狀陣列),所以這個函數最後可以定義成這樣:
function val = smallest (varargin)
val = min ([varargin{:}]);
endfunction
這樣定義的 smallest() 函數可以處理任何個數的參數,而且其程式碼非常簡單。
下面這個例子比較複雜一點,它會輸出所有的參數:
function print_arguments (varargin)
for i = 1:length (varargin)
printf ("Input argument %d: ", i);
disp (varargin{i});
endfor
endfunction
執行此函數
print_arguments (1, "two", 3);
輸出為
Input argument 1: 1 Input argument 2: two Input argument 3: 3
[reg, prop] = parseparams (params)
parseparams() 函數會將逗點分隔序列 params 分為兩部分,第一個部份是 reg,其包含從第一個元素到第一個字串出現之前,而第二個 prop 則會包含除了 reg 之外的部分,例如:
[reg, prop] = parseparams ({1, 2, "linewidth", 10})
輸出為
reg =
{
[1,1] = 1
[1,2] = 2
}
prop =
{
[1,1] = linewidth
[1,2] = 10
}可變長度多重傳回值(Variable-length Return Lists)
函數的傳回值亦可指定為不固定的個數,其用法就是在傳回值的部分加入一個 varargout,此參數與輸入參數的 varargin 一樣是一個巢狀陣列(cell array,請參考巢狀陣列)。例如下面這個函數會將第一個傳回值設為 1,第二個傳回值設為 2,以此類推:
function varargout = one_to_n ()
for i = 1:nargout
varargout{i} = i;
endfor
endfunction
執行此函數
[a, b, c] = one_to_n ()
輸出為
a = 1 b = 2 c = 3
varargout 參數要放在最後一個傳回值時,才有這種特別的作用,若不是放在最後一個,則會視為一般的傳回值。
[r1, r2, ..., rn] = deal (a)
[r1, r2, ..., rn] = deal (a1, a2, ..., an)
deal() 函數會將輸入的參數指定為輸出的傳回值,例如:
[a, b, c] = deal (1, 2, 3)
輸出為
a = 1 b = 2 c = 3
若只有一個輸入參數,則會將此參數指定給所有的傳回值,例如:
[a, b, c] = deal (1)
輸出為
a = 1 b = 1 c = 1
函數返回敘述(Returning From a Function)
在 Octave 函數中可以使用 return 敘述跳過之後的程式碼提早離開函數,其使用方式很簡單:
return
Octave 的 return 敘述與 C 語言中的 return 不同,其功能只有離開目前的函數而已,無法傳回任何的值,若需要傳回值必須將要傳回的值指定給傳回值參數。
下面這個範例會檢查一個向量中是否有任何一個元素不為0:
function retval = any_nonzero (v)
retval = 0;
for i = 1:length (v)
if (v (i) != 0)
retval = 1;
return;
endif
endfor
printf ("no nonzero elements found\n");
endfunction
這個函數中若要以 break 敘述來替代 return 敘述,則還要在加入一個判斷式控制最後輸出的訊息。
return
當 Octave 在函數或指令稿中碰到 return 時,會馬上返回;若在最上層執行此敘述則沒有作用。在 Octave 中每一個函數的結尾預設都會執行 return 敘述。
預設參數(Default Arguments)
由於 Octave 支援不固定的輸入參數,若配合設定參數的預設值在許多情況下會非常方便,參數預設值的使用方式為:
function name (arg1 = val1, ...)
body
endfunction
若是這樣定義此函數,則在 arg1 沒有指定時,其預設值為 val1。例如:
function hello (who = "World")
printf ("Hello, %s!\n", who);
endfunction
若呼叫 hello() 沒有指定參數時
hello();
則 who 預設為 "World",其輸出為
Hello, World!
若有指定參數
hello ("Beautiful World of Free Software");
輸出為
Hello, Beautiful World of Free Software!
若要明確告知 Octave 使用函數預設的參數,可以使用冒號,例如:
hello(:);
輸出為
Hello, World!
函數檔案(Function Files)
使用函數檔案(Using Function Files)
在撰寫程式時,除了只使用一次的程式之外,通常會需要將自行定義的函數儲存在檔案中,以便日後使用。
使用儲存在檔案中的函數時,可以不需要事先將其載入,當 Octave 遇到沒有定義的名稱時,會先檢查此名稱是否在已經存在於目前的符號表中,若在符號表中沒有此名稱,則會在載入路徑中尋找主檔名與此名稱相同的 .m 檔案,找到此檔案後會將其自動載入,若此檔案中只有定義一個函數,則此函數會被編譯後執行(若要在一個檔案中定義多個函數,請參考指令稿檔案)。
當 Octave 從檔案中載入函數時,會一並將檔案的名稱與時間戳記(time stamp)記錄下來,若之後檔案的時間戳記有變動,Octave 會自動重新載入,藉由時間戳記的方式,可以讓使用者在執行 Octave 的同時,可以任意更改函數中的內容,存檔後即可在 Octave 中使用,不需要重新啟動 Octave。在以互動式的方式使用 Octave 時,檢查時間戳記的動作只會發生在 Octave 輸出提示符號的時候,而搜尋新的函數定義也只會發生在變更現行目錄的時候。
為了避免 Octave 不斷的重複檢查那些不會更動的函數,而造成執行效率降低,Octave 預設將 octave-home/share/octave/version/m 中的函數都視為固定的函數(這個目錄下的函數都是 Octave 的一些內建函數),凡是放在這個路徑之下的函數,Octave 都會省略檢查時間戳記的動作,以增進效率。
若使用者自行定義的函數也都是固定的,可以使用 ignore_function_time_stamp ("all") 函數設定不檢查所有函數的時間戳記,而使用 "system" 參數可以恢復預設的設定。
edit name
edit field value
value = edit get field
edit 指令可以編輯指定的函數,或更改編輯器的設定,當呼叫 edit 並傳入一個函數名稱 name 時,會開啟函數編輯器。
若指定的函數 name 位於搜尋的路徑內且可以被編輯,則會將其開啟讓使用者進行編輯;若指定的函數是一個 Octave 內建的函數,則 Octave 會將此函數複製一份到 HOME 目錄,再將其開啟給使用者編輯;若是沒有找到,則 Octave 會嘗試將 name 的結尾加上 .m 或開頭加上 @ 後再搜尋,若都沒有則在嘗試同時加上 @ 與 .m。
若 name 是一個在命令列定義的函數,沒有對應的檔案,則 Octave 會自動在 HOME 目錄建立一個 m 檔案,並將目前 name 函數的定義放入此檔案中。
若 name 是一個副檔名為 .cc 的檔案名稱,則會在載入路徑中尋找是否有此一檔案,若有則開啟編輯,否則就會在 HOME 目錄建立一個新的檔案,若其主檔名剛好與一個 m 檔案的主檔名相同,或是與命令列所定義的函數相同,則會將此函數的內容以註解方式加入這個 .cc 檔案中。
若 name 是一個副檔名為 .ext 的檔案名稱,則會在載入路徑中尋找是否有此一檔案,若有此一檔案且可以編輯,則將其開啟編輯,否則就會在 HOME 目錄建立一個新的檔案;若此檔案存在,但不可編輯,則會將此檔案複製一份到 HOME 目錄再開啟編輯。
若編輯一個
.cc檔案,則在使用前必須先以mkoctfile func_name.cc指令編譯後,才能使用。
若呼叫 edit 指令時傳入 field 與 value 參數,可以將 field 的設定值設為 value;若傳入的第一個參數為 get,則會傳回 field 目前的設定,若指定的 field 不存在,則會傳回一個包含所有設定的資料結構,例如:
edit get all
就會傳回所有的設定。以下是所有 edit 可用的設定:
"editor"
設定用來編輯函數的文字編輯器,預設是使用 EDITOR 所指定的編輯器,其中 %s 是表示檔案的名稱,例如:
'[EDITOR, " %s"]':使用在bug_report中所使用的編輯器。'"xedit %s &"':使用 Unix 中的xedit做為編輯器。'"gnudoit -q \"(find-file \\\"%s\\\")\""':以目前開啟的 Emacs 做為編輯器(在.emacs中必須有gnuserv-start)。
若在 Cygwin 中指定 Windows 的編輯器,則需要將 Cygwin 的路徑轉為 Windows 的路徑,例如:
'"C:/Program Files/Good Editor/Editor.exe" "$(cygpath -wa %s)"'
"home"
設定使用者的 m 檔案的放置位置,預設為 ~/octave。若更改此設定,必須注意新的位置有在載入路徑中。
"author"
設定新函數的作者欄位內容。
"email"
設定作者欄位內容中的 E-mail。
"license"
設定授權,可用的選項有:
'gpl':GNU General Public License(預設)。'bsd':BSD-style license without advertising clause。'pd':Public domain。'"text"':自行定義授權。
除了 'pd' 授權之外,edit 會自動加上類似 "Copyright (C) yyyy Function Author" 的版權宣告。
"mode"
設定編輯器開啟的模式,可設定為同步(sync)或非同步(async),若為同步則 Octave 在開啟編輯器之後會一值等待,直到離開編輯器為止,而在非同步的模式,編輯器會在背景執行,不影響 Octave。預設是同步模式。
"editinplace"
設定是否直接編輯檔案,不管檔案是否允許編輯,預設為 false。
mfilename ()
mfilename ("fullpath")
mfilename ("fullpathext")
mfilename() 函數會傳回目前正在執行的 m 檔案名稱,若加入參數 "fullpath",則會傳回完整路徑加上主檔名;若加入參數 "fullpathext",則會傳回完整路徑加上主檔名與副檔名,此函數只適用於檔案中,若在做上層的命令列中執行,則會傳回空字串。
val = ignore_function_time_stamp ()
old_val = ignore_function_time_stamp (new_val)
查詢或設定 Octave 檢查檔案時間戳記的規則,每當 Octave 在尋找定義在檔案中的函數之前,會依照此規則決定是否要檢查檔案的時間戳記,在檢查時間戳記時若發現時間戳記有更動,則會自訂重新載入檔案內容。以下是可用的選項:
"system":對於octave-home/lib/version目錄下面的所有檔案都不會檢查其時間戳記,但其他蒐尋路徑中的檔案則會檢查。"all":對於所有的函數都不檢查,除非使用clear將函數整個移除後重新載入。"none":對於所有的函數都檢查。
管理載入路徑(Manipulating the load path)
在 Octave 中當一個函數被呼叫時,Octave 會從載入路徑(load path)中尋找被呼叫的函數,此路徑預設會包含 Octave 內部存放函數的目錄與目前的工作目錄,若要查看目前的載入路徑,可以使用 path 指令:
path
這樣會列出目前所設定的載入目錄。而要增加或刪除載入路徑,可以使用 addpath() 與 rmpath(),例如要增加一個載入路徑 "~/octave":
addpath("~/Octave")
這樣 Octave 在尋找函數時,就會連同 ~/octave 目錄一起搜尋。
addpath (dir1, ...)
addpath (dir1, ..., option)
addpath(dir1, ...) 函數可以將 dir1, ... 加入函數的載入路徑中,若 option 設為 "-begin" 或0,則會將此路徑加在現有的路徑之前,若設為 "-end" 或 1,則會將此路徑加在現有的路徑之後,若省略 option 則預設為加在現有的路徑之前。所有加入的路徑必須都是存在的路徑。
genpath (dir)
genpath(dir) 函數會傳回由 dir 目錄與其所有子目錄所構成的載入路徑。
rmpath (dir1, ...)
rmpath(dir1, ...) 函數會將 dir1, ... 等目錄從目前的載入路徑中移除。
savepath (file)
savepath(file) 函數會將目前的載入路徑,扣除 Octave 初始化時所設定的路徑,儲存至檔案中,若儲存成功會傳回0。參數 file 是指定儲存的檔名,若省略則預設為 ~/.octaverc。
path (...)
path() 函數可以設定或查詢目前函數的載入路徑。若沒有指定任何參數與傳回值,則會顯示目前的載入路徑;若只有指定傳回值參數,則會傳回目前的載入路徑;若有指定傳入的參數,則會將傳入的參數以 pathsep() 連接起來變成一組載入路徑,並將目前的載入路徑設定為這組,最後傳回新的載入路徑,在建立載入路徑時,Octave 並不會檢查是否有重複的路徑。
val = pathdef ()
pathdef() 函數會傳回預設的載入路徑,若 ~/.octaverc 檔案中有包含路徑,則會將其中的路徑傳回,若 ~/.octaverc 沒有包含路徑則再檢查 <octave-home>/.../<version>/m/startup/octaverc,若有則傳回其中的路徑,若兩者都沒有任何載入路徑,則傳回 Octave 預設的載入路徑。
val = pathsep ()
old_val = pathsep (new_val)
pathsep() 函數可以設定或查詢載入路徑中分隔每個路徑所使用的分隔符號。
rehash ()
rehash() 函數可以重新初始化 Octave 載入路徑的快取(cache)。
file_in_loadpath (file)
file_in_loadpath (file, "all")
file_in_loadpath(file) 函數會在載入路徑中尋找 file 檔案,若找到此檔案則傳回其完整的絕對路徑,若找不到則傳回空陣列。若 file 指定為一個巢狀陣列,則會依序尋找巢狀陣列中的元素,傳回第一個找到的檔案,例如:
file_in_loadpath({"help.m", "plot.m"})
輸出為
ans = C:\Octave\3.2.4_gcc-4.4.0\share\octave\3.2.4\m\help\help.m
若再加入第二個參數 "all",則會將巢狀陣列中所有找到的結果傳回,若全部都找不到,則傳回空矩陣。例如:
file_in_loadpath({"help.m", "plot.m"}, "all")
輸出為
ans =
{
[1,1] = C:\Octave\3.2.4_gcc-4.4.0\share\octave\3.2.4\m\help\help.m
[2,1] = C:\Octave\3.2.4_gcc-4.4.0\share\octave\3.2.4\m\plot\plot.m
}restoredefaultpath (...)
restoredefaultpath() 函數可以將函數的載入路徑重設回最原始的預設設定。
command_line_path (...)
command_line_path() 函數會傳回命令列路徑。
find_dir_in_path (dir)
find_dir_in_path(dir) 函數會在載入路徑中尋找 dir 目錄,若找到則傳回完整的絕對路徑。此函數只會尋找以 dir 結尾的目錄,假設 dir 為 "foo/bar",則會比對到 "/some/dir/foo/bar",但不會比對到 "/some/dir/foo/bar/baz" 或 "/some/dir/allfoo/bar"。
子函數(Subfunctions)
在一個函數檔案中可以定義一個以上的函數,而第二個以後的函數稱為子函數(Subfunctions)。子函數只有在同一個檔案的其他函數中可以使用,例如 f.m 檔案中的內容為:
function f ()
printf ("in f, calling g\n");
g ()
endfunction
function g ()
printf ("in g, calling h\n");
h ()
endfunction
function h ()
printf ("in h\n")
endfunction
在此檔案中定義了一個 f() 函數與兩個子函數 g() 與 h(),所以 g() 與 h() 只有在 f.m 中的函數可以呼叫它們,在 f.m 檔案以外就無法使用。
私有函數(Private Functions)
在許多情況下,使用者會撰寫一些常用的函數,提供給其他的函數呼叫,當只有一個函數需要呼叫這些常用函數時,可以使用子函數的方式處理,但若是一個常用函數須要提供給多個一般函數呼叫,就無法使用子函數的方式,這個時候可以將常用函數放在一個名為 private 的目錄中,則跟 private 目錄同一層的所有函數都可以使用 private 目錄中的函數,這樣的方式稱為私有函數(Private Functions)。例如 func1() 函數會用到一個常用函數 func2():
function y = func1 (x)
y = func2 (x);
endfunction
若 func1() 函數的路徑為 <directory>/func1.m,則可將 func2() 函數放置在 <directory>/private/func2.m,則所有 <directory> 中的函數都可以使用 func2() 函數,但在其他地方的函數則不能使用(因為這是私有函數)。
函數別名與載入設定(Overloading and Autoloading)
dispatch() 函數可以建立函數的別名(alias),它可以將一個函數所有的呼叫都重新導向至另一個函數,或是只針對一些特定的變數類型,例如:
function y = spsin (x)
printf ("Calling spsin\n");
fflush(stdout);
y = spfun ("sin", x);
endfunction
spsin() 是一個自行定義的函數,接著建立函數別名:
dispatch ("sin", "spsin", "sparse matrix");
這樣會將會將函數 spsin() 建立一個別名為 sin(),但此別名只有針對 sparse matrix 的變數類型,
y0 = sin(eye(3))
因為 eye(3) 不是 sparse matrix,所以會使用原本內建的 sin() 函數,輸出為
y0 = 0.84147 0.00000 0.00000 0.00000 0.84147 0.00000 0.00000 0.00000 0.84147
y1 = sin(speye(3))
這裡的 speye(3) 是一個 sparse matrix,所以會呼叫 spsin() 函數,輸出為
Calling spsin y1 = Compressed Column Sparse (rows = 3, cols = 3, nnz = 3 [33%]) (1, 1) -> 0.84147 (2, 2) -> 0.84147 (3, 3) -> 0.84147
內建的 sin() 函數其實本來就可以處理 sparse matrix,這裡只是為了示範別名的用法才如此使用。
dispatch (f, r, type)
dispatch(f, r, type) 函數會建立一個 r() 函數的別名,使 f() 函數在傳入的第一個參數為 type 類型時,以 r() 函數取代。若將 type 設為 "any",則當沒有其他的類型的別名時,都會呼叫 r()。建立別名之後,原始的 f() 函數可以使用 builtin(f, ...) 函數來呼叫。
若呼叫 dispatch() 函數而省略 r 參數,則會將作用於 f() 函數 type 類型的別名移除;若省略 r 與 type 兩個參數,則會顯示 f() 函數所有的別名。
[...] builtin (f, ...)
builtin(f, ...) 函數會忽略別名呼叫原始的 f() 函數。
透過別名可以將一個函數連結至許多個函數,若是使用者在命令列中輸入一個函數的別名,而此別名所指向的函數事實上是儲存在另外一的檔案中,此時 Octave 若以函數的名稱去搜尋檔案,就會發生找不到函數檔案的問題,最簡單的解決方法就是將此函數的定義檔複製一份,並以別名的名稱命名,但是這樣又會浪費硬碟空間。比較好的做法是使用 autoload() 函數設定函數的儲存位置。
autoload (function, file)
autoload(function, file) 函數會設定函數 function 的儲存位置為 file 檔案,當 Octave 要載入 function 函數時,就會從 file 檔案中載入。
file 參數應為一個絕對路徑,若設為不含路徑的檔名,則此檔案必須與 autoload() 函數所在的檔案有相同的路徑,此檔案不可依靠載入路徑來載入。
一般來說在 autoload() 函數會用在 ADD_PKG 這個特殊的指令稿中,在載入路徑的目錄中若包含此指令稿,則在此路徑加入目前的載入路徑時,就會自動執行 ADD_PKG,這個指令稿中可以設定這個目錄中函數的載入方式:
autoload ("foo", "bar.oct");
這會設定 foo() 函數所載入的檔案為 bar.oct。
若呼叫 autoload() 函數沒有加任何參數,則會顯示目前所有函數的載入設定表。
函數鎖定(Function Locking)
mlock() 可以將函數鎖定在記憶體中,不要被 clear() 函數移除,這個常會用在 oct 檔或 mex 檔中包含初始化的函數,讓 clear() 函數不要刪除已經初始化的內容。例如:
function count_calls ()
mlock ();
persistent calls = 0;
printf ("'count_calls' has been called %d times\n", ++calls);
endfunction
這樣會鎖定 count_calls() 函數,使其不會被 clear() 函數刪除:
count_calls ();
輸出為
'count_calls' has been called 1 times
clear count_calls;
count_calls ();
輸出為
'count_calls' has been called 2 times
要查詢函數是否有被鎖定,可用 mislocked() 函數:
mislocked ("count_calls")
而要解除鎖定可以使用 munlock() 函數:
munlock ("count_calls");
mlock ()
mlock() 函數會將目前的函數鎖定,讓此函數不會被 clear() 函數刪除。
munlock (fcn)
munlock(fcn) 函數會將 fcn 函數解除鎖定,若不指定 fcn 參數,則會將目前的函數解除鎖定。
mislocked (fcn)
mislocked(fcn) 函數可以判斷 fcn 函數是否被鎖定,若不指定 fcn 參數,則會判斷目前的函數是否被鎖定。
函數優先權(Function Precedence)
在 Octave 中有許多種定義函數的方式,當同一個函數名稱有多種定義方式時,就會依照函數優先權(Function Precedence)來決定使用哪一個,以下是 Octave 的所使用的優先權列表:
- 子函數(subfunction):目前函數的子函數,請參考子函數。
- 私有函數(private function):目前函數的私有函數,請參考私有函數。
- Class constructor:建構使用者類別的函數。
- Class method:類別的多載函數。
- 函數別名:請參考函數別名與載入設定。
- 命令列函數(Command-line Function):由命令列中所定義的函數。
- 自訂載入函數(Autoload function):經由
autoload()所設定的函數,請參考函數別名與載入設定。 - 載入路徑中的函數:包含載入路徑中各類型的檔案,載入的優先順序為
.oct檔、.mex檔、.m檔。 - 內建函數(Built-in function):Octave 中本身內建的函數,例如:
numel()與size()等。
指令稿檔案(Script Files)
指令稿檔案(Script Files)可以包含(幾乎)任何的 Octave 指令,執行指令稿時 Octave 會將其中的指令讀入後執行,就跟直接在命令列中輸入一樣,這樣可以讓使用者很方便的執行一連串的指令。
指令稿檔案與函數檔案不同,其第一行指令不可以為 function 關鍵字開頭。在指令稿檔案中可以定義多個函數,並且一次將這些函數載入(只有載入,沒有執行),由於第一行指令(不包含註解或空白行)的開頭不可以是 function,所以必須執行別的指令,若沒有需要執行其他的指令,可以加入一行沒有作用的指令,例如:
# Prevent Octave from thinking that this
# is a function file:
1;
# Define function one:
function one ()
...
若指令稿中第一個指令以 function 關鍵字開頭,Octave 會嘗試將其編譯後並執行之,並且產生一些警告訊息。
為了使 Octave 可以正確的載入指令稿檔案,可以將指令稿檔案命名為 .m 檔案,放置在載入路徑中,然後在 Octave 中輸入其主檔名,Octave 就會將其載入,其載入的規則與函數檔案相同。
Octave 在執行函數之前,並不會去檢查其中的指令是否有定義,例如 Octave 在編譯下面這個指令稿檔案時是不會出現問題的:
# not a function file:
1;
function foo ()
do_something ();
endfunction
function do_something ()
do_something_else ();
endfunction
這個指令稿檔案先定義 foo() 函數,而 foo() 函數中呼叫 do_something() 函數,雖然在定義 foo() 函數時 do_something() 函數還沒有被定義,但是因為 Octave 並沒有去執行 foo() 函數,所以此時 Octave 還不需要 do_something() 函數的定義,等到隨後 do_something() 函數定義之後,再執行 foo() 函數時,do_something() 函數已經有定義了,所以這樣寫是沒有問題的。
指令稿檔案除了命名為 .m 檔然後輸入其主檔名將其載入外,也可以使用 source() 函數載入指令稿檔案。
source (file)
source(file) 函數可以載入指令稿檔案 file,這與直接執行 .m 指令稿名稱相同,但此 file 的名稱沒有限制(可以是 .txt 或其他的檔案名稱)。
函數握把 、行內函數與匿名函數(Function Handles, Inline Functions and Anonymous Functions)
在撰寫程式時有時候會需要將函數以變數的形式傳遞,例如將一個函數指定為另一個函數的傳入參數,這時就可以利用下面介紹的方法。
函數握把(Function Handles)
函數握把(function handle)是一個指向函數的指標,其定義方式為
@function-name
例如:
f = @sin;
這樣會建立一個指向 sin() 函數的函數握把 f。
函數握把主要用於將一個函數傳遞給其他的函數(例如:quad() 與 fsolve() 函數)使用,例如:
f = @sin;
quad (f, 0, pi)
輸出為
ans = 2
若要呼叫函數握把所指向的函數,可以使用 feval() 函數或是直接使用函數握把並加入參數,若沒有輸入參數,則還是要加上小括號 (),例如:
f = @sin;
feval (f, pi/4)
輸出為
ans = 0.70711
f (pi/4)
輸出為
ans = 0.70711
functions (fcn_handle)
functions(fcn_handle) 函數會傳回一個包含函數握把 fcn_handle 相關訊息的資料結構(struct)。
func2str (fcn_handle)
func2str(fcn_handle) 函數會傳回一個字串,內容為函數握把 fcn_handle 所指向的函數名稱。
str2func (fcn_name)
str2func(fcn_name) 函數會將包含函數名稱的字串 fcn_name 轉為函數握把。
匿名函數(Anonymous Functions)
匿名函數(Anonymous Functions)就是沒有函數名稱的函數,其定義方式為:
@(argument-list) expression
在 expression 中所使用的變數若不在傳入參數 argument-list 中,則會由目前的變數環境中抓取。匿名函數主要用於定義一個不需要名稱的函數,例如定義一個函數並將此函數傳入 quad() 函數中:
f = @(x) x.^2;
quad (f, 0, 10)
輸出為
ans = 333.33
將 sin() 函數以匿名函數包裝:
quad (@(x) sin (x), 0, pi)
輸出為
ans = 2
將 betainc() 函數包裝成 quad() 函數可以形式:
a = 1;
b = 2;
quad (@(x) betainc (x, a, b), 0, 0.4)
其中 betainc() 函數的參數 a 與 b 會從目前的變數環境中取得,輸出為
ans = 0.13867
行內函數(Inline Functions)
行內函數(Inline Functions)是指使用 inline() 函數從一個包含程式主體(body)的字串所建立的函數,例如:
f = inline("x^2 + 2");
這樣會建立一個 f() 函數,其可接受一個輸入參數,例如 f(2)。
inline (str)
inline (str, arg1, ...)
inline (str, n)
inline() 函數會由包含程式主體的字串 str 建立行內函數,若只有指定一個 str 參數,則函數的參數會自動由 str 中取出,而參數的順序會依照英文字母的順序排序,而在擷取參數時 i 與 j 會被忽略,因為 i 與 j 也代表複數的虛部,使用起來容易造成混淆。所有加上小括號的名稱都會被視為函數。
參數 arg1, ... 可以指定參數的名稱。參數 n 為一個正整數,指定參數的個數為 n,若指定為 n 則所有的參數為 "x", "p1", ..., "pn"。
argnames (fun)
argnames(fun) 函數會傳回包含行內函數 fun 的所有參數名稱的字串巢狀陣列。例如:
f = inline("x^2 + 2");
argnames(f)
輸出為
ans =
{
[1,1] = x
}formula (fun)
formula(fun) 函數會傳回行內函數 fun 的內容,例如:
f = inline("x^2 + 2");
formula(f)
輸出為
ans = x^2 + 2
char(fun)與formula(fun)是相同的。
vectorize (fun)
vectorize(fun) 函數會將行內函數 fun 轉為向量化的函數,即將 * 與 / 等運算子替換為 .* 與 ./ 等運算子。例如:
f = inline("x^2 + 2");
vectorize(f)
輸出為
ans = f(x) = x.^2 + 2
symvar (s)
symvar(s) 函數會從包含函數主體的字串 s 中擷取出所有的函數參數,並以巢狀陣列傳回,一般的常數會被忽略(例如:pi、 NaN、 Inf、 eps、 i 或 j),若沒有找到任何參數,則傳回空的巢狀陣列。例如:
symvar("x^2 + 2 + y")
輸出為
ans =
{
[1,1] = x
[2,1] = y
}命令(Commands)
命令(Commands)是一種特別的函數,他只能接受字串的輸入參數,它可用類似一般函數的方式呼叫,也可以不使用小括號,例如:
my_command hello world
這樣呼叫等同於
my_command("hello", "world")
命令的標準使用方式為:
name arg1 arg2 ...
Octave 會將其轉換為
name ("arg1", "arg2", ...)
一般的函數若其參數皆為字串,也可以使用這樣的方式呼叫,但在呼叫之前必須要使用 mark_as_command 指令將此函數標示為命令,例如:
mark_as_command name
其中 name 就是要標示為命令的函數名稱。
當一個要傳給命令的參數儲存在變數中時,無法直接以命令呼叫的方式傳入變數,因為 Octave 無法區分傳入的是變數名稱還是一般的字串,這個時候唯一的辦法就是以一般函數的呼叫方式來呼叫。
Octave 函數架構(Organization of Functions)
許多 Octave 的標準函數都是以函數檔案的方式儲存,其存放的位置為 <octave-home>/lib/octave/<version>/m 目錄,此目錄中又以不同的主題區分為多個子目錄,以下是各個子目錄名稱與其包含的函數類型:
audio:播放與錄製聲音的相關函數。control:自動控制系統的設計與模擬的相關函數。elfun:基本函數。finance:財務函數。general:各種矩陣操作與其他函數。image:影像處理函數。io:輸入與輸出函數。linear-algebra:線性代數相關函數。miscellaneous:雜項。optimization:最佳化函數。path:路徑管理函數。pkg:安裝 Octave 套件。plot:2D 與 3D 繪圖函數。polynomial:多項式函數。set:集合相關函數。signal:數位訊號處理函數。sparse:稀疏矩陣相關函數。specfun:特殊函數。special-matrix:特殊矩陣函數。startup:Octave 系統啟動函數。statistics:統計相關函數。strings:字串相關函數。testfun:測試用函數。time:時間紀錄函數。