複雜宣告的讀法
文法,最令我印象深刻的,莫過於印度工程師Vikram的"The right-left rule"。他是這麼說的:
「從最內層的括號讀起,變數名稱,然後往右,遇到括號就往左。當括號內的東西都解讀完畢了,就跳出括號繼續未完成的部份,重覆上面的步驟直到解讀完畢。」
舉個例子:void ** (*d) (int &, char*)依下面方式解讀:
1. 最內層括號的讀起,變數名稱: d
2. 往右直到碰到) : (空白)
3. 往左直到碰到( :是一個函數指標
4. 跳出括號,往右,碰到(int &, char*): 此函式接受兩個參數:第一個參數是reference to integer,第二個參數是character pointer。
5. 往左遇上void **: 此函式回傳的型態為pointer to pointer to void。
==> d是一個函式指標,指向的函式接受int&和char*兩個參數並回傳void**的型態。
如何,是不是好懂很多了呢?
標題中的void ** (*d) (int &, char **(*)(char *, char **))其實和上面的例子幾乎一樣,只是函式的第二個參數又是一個函式指標,接受char*和char**兩個參數並回傳char**的型態。
---------
下面結合例子來演示一下「右左法則」的使用。
int * (* (*fp1) (int) ) [10];
閱讀步驟:
1. 從變數名開始 -------------------------------------------- fp1
2. 往右看,什麼也沒有,碰到了),因此往左看,碰到一個* ------ 一個指標
3. 跳出括弧,碰到了(int) ----------------------------------- 一個帶一個int參數的函數
4. 向左看,發現一個* --------------------------------------- (函數)返回一個指標
5. 跳出括弧,向右看,碰到[10] ------------------------------ 一個10元素的陣列
6. 向左看,發現一個* --------------------------------------- 指標
7. 向左看,發現int ----------------------------------------- int類型
總結:fp1被聲明成為一個函數的指標,該函數返回指向指標陣列的指標.
再來看一個例子:
int *( *( *arr[5])())();
閱讀步驟:
1. 從變數名開始 -------------------------------------------- arr
2. 往右看,發現是一個陣列 ---------------------------------- 一個5元素的陣列
3. 向左看,發現一個* --------------------------------------- 指標
4. 跳出括弧,向右看,發現() -------------------------------- 不帶參數的函數
5. 向左看,碰到* ------------------------------------------- (函數)返回一個指標
6. 跳出括弧,向右發現() ------------------------------------ 不帶參數的函數
7. 向左,發現* --------------------------------------------- (函數)返回一個指標
8. 繼續向左,發現int --------------------------------------- int類型
還有更多的例子:
float ( * ( *b()) [] )(); // b is a function that returns a
// pointer to an array of pointers
// to functions returning floats.
void * ( *c) ( char, int (*)()); // c is a pointer to a function that takes
// two parameters:
// a char and a pointer to a
// function that takes no
// parameters and returns
// an int
// and returns a pointer to void.
void ** (*d) (int &,
char **(*)(char *, char **)); // d is a pointer to a function that takes
// two parameters:
// a reference to an int and a pointer
// to a function that takes two parameters:
// a pointer to a char and a pointer
// to a pointer to a char
// and returns a pointer to a pointer
// to a char
// and returns a pointer to a pointer to void
float ( * ( * e[10])
(int &) ) [5]; // e is an array of 10 pointers to
// functions that take a single
// reference to an int as an argument
// and return pointers to
// an array of 5 floats.
---------
下面的宣告摘錄自"The C Programming Language"第二版的第122頁,請讀者寫出其宣告意義
char **argv; argv : pointer to pointer to char int (*daytab)[13]; daytab : pointer to array[13] of int int *daytab[13]; daytab : array[13] of pointer to int void *comp(); comp : function returning pointer to void void (*comp)(); comp : pointer to function returning void char (*(*x())[])(); x : function returning pointer to array[] of pointer to function returning char char (*(*x[3])())[5];
x : array[3] of pointer to function returning pointer to array[5] of char
口訣
- 看見[]就說array[] of
- 看見*就說pointer to
- 看見變數後面的()就說function() returning
以下是計算積分的程式範例,用到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; }
上面寫法會比較快的精神在於,讓迴圈內的東西越簡單越好,因為迴圈通常是程式花最多時間的地方。