C語言允許陣列指標和函數三者混合宣告,以表達複雜的資料結構。對於要撰寫比較複雜的應用程式來說,了解這三者的關係是非常必要的。例如下面的變數x到底是甚麼?
char (*x[])();
要看懂這些東西,首先要查型別與運算裡有關優先權與結合序的表格,得到()[]是第一優先權左結合,而*是第二優先權右結合。在看變數宣告時,如同運算式的推演過程,必須遵守C程式語言對*()[]的優先權定義。接下來請讀者背誦下面的口訣
上述口訣配合*()[]的優先權,依序找出其執行的順序,每看到運算符號就把這幾句口訣念出來。因此變數的意義如下面範例
char *x; // x: a pointer to char char x[3]; // x: an array[3] of char char x(); // x: a function() returning char char *x[3]; // x: an array[3] of pointer to char char (*x)[3]; // x: a pointer to array[3] of char char **x; // x: a pointer to pointer to char char *x(); // x: a function() returning pointer to char char *x()[3]; // x: a function() returning array[3] of pointer to char char (*x[])(); // x: an array[] of pointer to function() returning char char (*x())(); // x: a function() returning pointer to function() returning char char (*(*x)[])(int, int); // x: a pointer to array[] of pointer to function(int,int) returning char
讀者在閱讀上面範例時,千萬不要把這些英文翻成中文! 英文文法是後面修飾前面,比中文更能確切表達文句的意義,因此在處理這些複雜宣告時,用英文去了解即可。
在讀運算式時,則每看到一個運算符號,就把宣告前面的口訣拿掉一個,就是該運算式的意義
char *x; // x: a pointer to char *x : a char
char *x[3]; // x: an array[3] of pointer to char x[0] : a pointer to char
char **x; // x: a pointer to pointer to char *x : a pointer to char
char *x(); // x: a function() returning pointer to char x() : a pointer to char
char *x()[3]; // x: a function() returning array[3] of pointer to char x()[1] : a pointer to char
下面的宣告摘錄自"The C Programming Language"第二版的第122頁,請讀者寫出其宣告意義,並參考該書核對答案,相信可以增強讀者的自信:
char **argv; int (*daytab)[13]; int *daytab[13]; void *comp(); void (*comp)(); char (*(*x())[])(); char (*(*x[3])())[5];
以下是計算積分的程式範例,用到pointer to function的觀念
#include <stdio.h> #include <math.h> /* * 計算平方 */ double square(double x) { return x * x; } /* * 計算三次方 */ double cube(double x) { return x * x * x; } /* * 計算f()在(x,y)之間以n等份來逼近的積分數值,使用梯形法 */ double integral(double (*f)(double), int n, double x, double y) { int i; double gap = (y - x) / n; double fy1 = (*f)(x); double fy2 = (*f)(x + gap); double area = 0; for (i = 0; i < n; i++) { area += (fy1 + fy2) * gap / 2; // 使用梯形面積公式 fy1 = fy2; fy2 = (*f)(x + gap * (i + 1)); //下底 } return area; } int main() { char fun[100]; int n; double x, y; double (*f)(double); // f: a pointer to function(double) returning double while (scanf("%99s",fun) != EOF) { // EOF定義於stdio.h內,一般系統上為-1 if (strcmp(fun,"square")==0) { f = square; } else if (strcmp(fun,"cube")==0) { f = cube; } else if (strcmp(fun,"sqrt")==0) { f = sqrt; // sqrt is defined in math.h } else if (strcmp(fun,"cbrt")==0) { f = cbrt; // cbrt is defined in math.h } else if (strcmp(fun,"end")==0) { break; } else { printf("Unknown function\n"); continue; } scanf("%d%lf%lf", &n, &x, &y); printf("Integral of %s from %lf to %lf is: %lf\n", fun, x, y, integral(f, n, x, y)); } return 0; }
如果要讓積分算得更快的話,integral也可改寫如下
double integral(double (*f)(double), int n, double x, double y) { int i; double area = ((*f)(x) + (*f)(y)) / 2.0L; double gap = (y - x) / n; double next = x; for (i = 1; i < n; i++) { area += (*f)(next += gap); } return area * gap; }
上面寫法會比較快的精神在於,讓迴圈內的東西越簡單越好,因為迴圈通常是程式花最多時間的地方。
在命令列(DOS視窗或UNIX Shell)下達指令時,常需要告訴該指令一些訊息,如
gcc -c hello.c告訴gcc我們要編譯的原始檔是hello.c,而且-c說只要編譯不要產生執行檔。這些參數是由Shell(命令解譯程式)透過作業系統傳遞給應用程式的。那麼寫C程式的人要如何得到這些資訊呢?其實我們前面範例裡的main都把一些參數忽略了,完整的寫法應如下:
#include <stdio.h> int main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) { printf("option %i = %s\n", i, argv[i]); } }如果我們下達a.out one two three則會印出
option 0 = a.out option 1 = one option 2 = two option 3 = three也就是說參數argc告訴我們命令列上有幾個字串(含執行檔檔名),argv則是個array[] of pointer to char,紀錄命令列上的所有字串。UNIX上有個有趣的指令叫做echo,會把參數全部印出來
>echo a b c a b c >
#include <stdio.h> int main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) { if (i != 1) { printf(" "); } printf("%s", argv[i]); } printf("\n"); }
在說明函數的章節裡,所有範例函數的參數個數都是固定的,但讀者有沒有發覺使用printf,scanf函數時,我們所給予的參數個數卻是不定長的!本節說明如何定義具有不定長參數的函數。
printf的宣告如下:
int printf(char *fmt, ...)處理不定長參數需要用到<stdarg.h>裡面的函數。va_list型態可用來存取每一個參數:
#include <stdarg.h> // or #include <sys/varargs.h> void minprintf(char *fmt, ...) { va_list ap; /* pointer to each unnamed arg in turn */ char *p, *sval; int ival; double dval; va_start(ap, fmt); /* make ap point to 1st unnamed arg */ for (p = fmt; *p; p++) { // check each character if (*p != '%') { // 不是特殊字元,直接輸出即可 putchar(*p); continue; } switch(*++p) { // 檢查%的下一個字元是甚麼 case 'd': ival = va_arg(ap, int); printf("%d", ival); break; case 'f': dval = va_arg(ap, double); printf("%f", dval); break; case 's': for (sval = va_arg(ap, char *); *sval; sval++) { // 印出sval所指到的所有字元 putchar(*sval); } break; default : putchar(*p); break; } } va_end(ap); // clean up when done */ }