在這一節裡面,我們要示範如何自己寫一個函式,並且將它獨立出來, 使得它可以被許多不同的函式呼叫。 我們先看一個例子:s2i()。 它將一個字串 (string) 型態的資料,轉換為一個 int 型態的資料。
將以下原始碼獨立寫在一個檔案裡面,譬如叫 s2i.c。
#define WHITE(c) (c == ' ' || c == '\t') #define SIGN(c) (c == '+' || c == '-') #define DIGIT(c) (c <= '9' && c >= '0') /* s2i: convert string s to int (return value) */ int s2i(char s[]) { int i, n, sign; for (i=0; WHITE(s[i]); ++i) ; /* skip leading spaces and tabs */ sign = (s[i] == '-') ? -1 : 1; if (SIGN(s[i])) ++i; for (n=0; DIGIT(s[i]); ++i) n = 10*n + s[i] - '0'; return sign*n; }
for (i=0; WHITE(s[i]); ++i) ; /* skip leading spaces and tabs */把字串開頭部分的所有空白都略過。此時 s[i] 的值是一個非空白字元, 包括了 '\0' 和 '\n' 的可能性 (s[] 是空字串)。 然後,用
sign = (s[i] == '-') ? -1 : 1;檢查 s[i] 是不是負號。如果是的話,定義 sign = -1, 否則就定義 sign = -1。 所以不管此時的 s[i] 是什麼 (總之不是空白), sign 是有定義的了。 注意,直到此刻,i 都還未改變。 現在,才以
if (SIGN(s[i])) ++i;檢查 s[i] 是否為正負號,如果是,就略過,否則 i 還是不動。 接著,就以
for (n=0; DIGIT(s[i]); ++i) n = 10*n + s[i] - '0';來獲取數值了。一直重複執行到第一個非數目字的字元為止。 此時的 n 是那串表示十進制數字的值,不含正負號。 最後,用
return sign*n;將所求的答案返回原函式。
注意,s2i(s) 之中,即使 s[] 全是空白、或是空字串、 或者裡面只有一個正負號,則 sign 和 n 還是有定義, 分別是 1 和 0。所以,返回的值就是 0。 以上所說的,就是極端狀況。可見 s2i(s) 可以處理極端狀況, 至於處理是否得當,就不知道了。
再注意,s2i(s) 還可以接受更糟的輸入。 例如,輸入 "abcd" 則返回 0,輸入 "3.14" 則返回 3。 這些特色都是從原始碼之中可以觀察的。 將 "3.14" 返回 3,似乎是正確的反應。 但是,將 "abcd" 返回 0,就難以斷言是否正確了。 不過,這些問題都應該留給使用者自己去注意, 或者由原函式來注意。這個 s2i(s) 只負責做好它分內的工作。
將 s2i.c 存檔之後,讓我們寫個簡單的程式來測試它。 這個程式的原始碼如下,也將它獨立儲存在一個檔案內,譬如叫做 test_s2i.c。
#include <stdio.h> #include <string.h> int s2i(char[]); /* Test of s2i() (test_s2i.c) */ main() { char s[20]="2000"; printf("%s\t%d\n", s, s2i(s)); strcpy(s, "-2000"); printf("%s\t%d\n", s, s2i(s)); strcpy(s, "-"); printf("%s\t%d\n", s, s2i(s)); strcpy(s, "3.14"); printf("%s\t%d\n", s, s2i(s)); strcpy(s, "abcd"); printf("%s\t%d\n", s, s2i(s)); strcpy(s, ""); printf("%s\t%d\n", s, s2i(s)); }
現在我們示範,如何處理這些原始碼檔案。 首先,我們做個錯誤的示範
shell% gcc test_s2i.c In function `main': undefined reference to `s2i' more undefined references to `s2i' follow collect2: ld returned 1 exit status您看到的錯誤訊息或多或少像這個樣子。 這表示,C 不知道 s2i() 是什麼東西。 雖然在 test_s2i.c 裡面,我們以
int s2i(char[]);宣告 了 s2i(),但那只是告訴 C 說 s2i() 的規格是什麼, 卻沒有說明 s2i() 做什麼事? 換句話說,C 只知道 s2i() 的規格,不知道它的內容。
正確的做法是,先將 s2i() 的內容編譯成機器碼, 並將其機器碼獨立儲存在一個檔案裡面。如下
shell% gcc -c s2i.c shell% ls ... s2i.c s2i.o ...在執行成功之後,您會發現多出來一個檔案,叫做 s2i.o, 那個 o 是 Object code 的意思,我們就稱它為 s2i.c 的機器碼。 除非它的原始碼 (s2i.c) 經過修改, 或是想要把它複製到不同的作業系統上執行, 否則機器碼只需製造一次,就可以重複使用。
譬如說,一台以 Pentium II 或 Pentium III 甚至 AMD 為 CPU 的個人電腦, 如果都執行 Windows 98,則被視為同一類型的電腦。 但是,同樣一台個人電腦,執行了 Windows NT 或 Linux 不同的作業系統, 就被視為不同類型的電腦。 但是,同樣是 UNIX 類型的作業系統, 若細分各種品牌,例如 Linux、Sun 的 Solaris 或 SunOS、IBM 的 AIX, 卻都是不同類型的。 總之,如果發現機器碼不能執行,重新編譯就對了。
好了,假使 s2i.o 已經存在,現在說
shell% gcc test_s2i.c s2i.o就會產生新的 a.out 檔案,這時候,就可以執行它了:
shell% a.out 2000 2000 -2000 -2000 - 0 3.14 3 abcd 0 0
如果一個函式的原始碼經過修改,那麼就要重新製造它的機器碼。 然後,所有用到它的機器碼的原函式,也都要重新編譯,才能讀入新的函式機器碼。 譬如說,如果因為任何理由修改了 s2i.c (加註解不算), 就應該要重新執行
shell% gcc -c s2i.c shell% gcc test_s2i.c s2i.o shell% a.out
習題
注意:此處所有文件均為原著,個別的版權宣告日後會一一公布, 整體版面設計亦尚未完成。但仍請勿抄襲文字與圖片,以免觸犯著作權法。
Created: May 18, 2000
Last Revised: May 18, 2000
© Copyright 2000 Wei-Chang Shann 單維彰