讀者詢問如何在Arduino中建立數個自訂函式,並且透過「函式指標陣列」,在不使用if…else或switch…case等條件判斷式的情況下,讓程式依據變數的值,執行不同的自訂函式。
下文將先介紹Arduino與C程式語言的函式指標語法。
建立函式指標程式
就像變數一樣,函式(function)同樣被暫存在某個記憶體區塊,可以透過指標(pointer)取用;指向函式的指標稱為「函式指標(function pointer)」。
底下的Arduino程式宣告了一個hello()自訂函式,並透過一個叫做‘f’的指標指向並執行它:
若在Arduino執行此程式,將能在序列埠監控視窗看見“hello world.”訊息。
函式指標的語法
函式指標的語法如下:
指標名稱必須用代表「優先處理」的小括號包圍,如果少了小括號,程式敘述的意義將大不相同:
上面的Arduino程式碼,若用C語言改寫,將變成:
#include// 宣告自訂函式 void hello() { printf("hello world.\n"); } // 宣告指向hello函式的函式指標 void (*f)() = hello; void main(void) { // 透過函式指標執行hello函式 (*f)(); }
函式指標的參數傳遞與傳回值
假如要透過指標參照具有輸入參數和傳回值的函式,例如,一個計算並傳回兩整數相加值的自訂函式“sum”:
函式指標的定義也要跟著修改:
完整的Arduino範例程式如下:
// 宣告自訂函式 int sum(int x, int y) { return x + y; } // 宣告具有兩個參數以及整數傳回值的函式指標 int (*f)(int x, int y) = sum; void setup() { Serial.begin(9600); // 執行函式指標並傳遞兩個參數 int val = (*f)(8, 4); // 將在序列埠監控視窗顯示"Ans: 12" Serial.print("Ans: "); Serial.println(val); } void loop() { // 這裡沒有程式 }
相同功能的C語言程式的範例如下:
#includeint sum(int x, int y) { return x + y; } int (*f)(int x, int y) = sum; void main(void) { int val = (*f)(8, 4); printf("Ans: %d\n", val); }
函式指標陣列
認識函式指標的語法之後,函式指標陣列的語法也很容易理解。假設程式事先宣告了名叫fn0, fn1和fn3的函式,底下的敘述將透過自訂的 "f" 指標陣列指向它們:
底下是Arduino版本的函式指標陣列範例,上傳程式碼之後,開啟序列埠監控視窗,接著在序列埠監控視窗中輸入0~2的數字,程式將執行對應的fn0~fn2函式。
// 宣告三個自訂函式 void fn0() { Serial.println("Hello from fn0."); } void fn1() { Serial.println("Hello from fn1."); } void fn2() { Serial.println("Hello from fn2."); } // 建立函式陣列指標 void (*f[3]) () = {fn0,fn1,fn2}; void setup() { Serial.begin(9600); Serial.println("Please input number 0~2:"); } void loop() { if( Serial.available() ) { // 讀取序列輸入值,並轉換成整數。 byte val = Serial.read() - 48; // 確認輸入值介於0和2之間 if (val >= 0 && val < = 2) { // 將val當作陣列索引,執行對應的函式。 (*f[val])(); } else { // 若輸入值超過指定範圍,則顯示“Wrong number!”(錯誤數字!) Serial.println("Wrong number!"); } } }
底下是C程式語言的版本:
#includevoid fn0() { printf("Hello from fn0.\n"); } void fn1() { printf("Hello from fn1.\n"); } void fn2() { printf("Hello from fn2.\n"); } void (*f[3]) () = {fn0,fn1,fn2}; void main(void) { int val; printf("\nPlease input number 0~2:"); // 讀取輸入值 scanf("%d",&val); if(val>=0 && val< =2) { (*f[val])(); } else { printf("Wrong number!\n"); } }
感謝老師指點, 我晚上K一下, ^_^
不客氣~
thanks,
jeffrey
昨天研究了一下老師的方法, 解決了我的問題, 且我發現更棒的是函式命名不必有數字上順序的關係, 只要函式指標陣列跟函式有對應到, 順序就照陣列使用方式取用, 這樣說是對的吧?
void fn0() {
}
void power_on() {
}
void check_status() {
}
void (*f[3]) () = {fn0, power_on, check_status};
沒錯!陣列是依照索引編號而非名稱來存取元素。
thanks,
jeffrey
感謝老師^_^
我昨天發現原來我第一本老師的書是Flash 5 撼動網頁寶典, 而非Flash MX 網頁動畫寶典, ^_^
thanks 🙂
Thanks, it helpful
不客氣~
thanks,
jeffrey
老師您好:我發現 void (*f[3]) () = {fn0,fn1,fn2}; 這樣宣告了3個函式不僅會佔用3個ROM空間,也多佔用了3個記憶空間(RAM)。
在MCU 裡,RAM空間是很有限的。
想請教是否可將{fn0,fn1,fn2} 3個函式”位置”宣告為CONST常數,共用一個指標變數?
如果有很多的函式放在”陣列”中,也只會多佔用ROM空間 ,而不會佔用RAM空間。謝謝。
函式並非內容固定不變的常數,執行期間一定會被載入記憶體(堆疊),但執行完畢就從記憶體移除。除非你要執行大量的函式(例如:遞迴),通常不用擔心這個問題。
thanks,
jeffrey