在這一節裡面,我們要示範如何自己寫一個函式,並且將它獨立出來, 使得它可以被許多不同的函式呼叫。 我們先看一個例子: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 單維彰