現在我們來實現一個完整的複數運算程序。在上一節我們已經定義了複數的結構體類型,現在需要圍繞它定義一些函數。複數可以用直角座標或極座標表示,直角座標做加減法比較方便,極座標做乘除法比較方便。如果我們定義的複數結構體是直角座標的,那麼應該提供極座標的轉換函數,以便在需要的時候可以方便地取它的模和輻角:
#include <math.h> struct complex_struct { double x, y; }; double real_part(struct complex_struct z) { return z.x; } double img_part(struct complex_struct z) { return z.y; } double magnitude(struct complex_struct z) { return sqrt(z.x * z.x + z.y * z.y); } double angle(struct complex_struct z) { return atan2(z.y, z.x); }
此外,我們還提供兩個函數用來構造複數變數,既可以提供直角座標也可以提供極座標,在函數中自動做相應的轉換然後返回構造的複數變數:
struct complex_struct make_from_real_img(double x, double y) { struct complex_struct z; z.x = x; z.y = y; return z; } struct complex_struct make_from_mag_ang(double r, double A) { struct complex_struct z; z.x = r * cos(A); z.y = r * sin(A); return z; }
在此基礎上就可以實現複數的加減乘除運算了:
struct complex_struct add_complex(struct complex_struct z1, struct complex_struct z2) { return make_from_real_img(real_part(z1) + real_part(z2), img_part(z1) + img_part(z2)); } struct complex_struct sub_complex(struct complex_struct z1, struct complex_struct z2) { return make_from_real_img(real_part(z1) - real_part(z2), img_part(z1) - img_part(z2)); } struct complex_struct mul_complex(struct complex_struct z1, struct complex_struct z2) { return make_from_mag_ang(magnitude(z1) * magnitude(z2), angle(z1) + angle(z2)); } struct complex_struct div_complex(struct complex_struct z1, struct complex_struct z2) { return make_from_mag_ang(magnitude(z1) / magnitude(z2), angle(z1) - angle(z2)); }
可以看出,複數加減乘除運算的實現並沒有直接訪問結構體complex_struct
的成員x
和y
,而是把它看成一個整體,通過調用相關函數來取它的直角座標和極座標。這樣就可以非常方便地替換掉結構體complex_struct
的存儲表示,例如改為用極座標來存儲:
#include <math.h> struct complex_struct { double r, A; }; double real_part(struct complex_struct z) { return z.r * cos(z.A); } double img_part(struct complex_struct z) { return z.r * sin(z.A); } double magnitude(struct complex_struct z) { return z.r; } double angle(struct complex_struct z) { return z.A; } struct complex_struct make_from_real_img(double x, double y) { struct complex_struct z; z.A = atan2(y, x); z.r = sqrt(x * x + y * y); } struct complex_struct make_from_mag_ang(double r, double A) { struct complex_struct z; z.r = r; z.A = A; return z; }
雖然結構體complex_struct
的存儲表示做了這樣的改動,add_complex
、sub_complex
、mul_complex
、div_complex
這幾個複數運算的函數卻不需要做任何改動,仍然可以用,原因在於這幾個函數只把結構體complex_struct
當作一個整體來使用,而沒有直接訪問它的成員,因此也不依賴于它有哪些成員。我們結合下圖具體分析一下。
這裡是一種抽象的思想。其實“抽象”這個概念並沒有那麼抽象,簡單地說就是“提取公因式”:ab+ac=a(b+c)。如果a變了,ab和ac這兩項都需要改,但如果寫成a(b+c)的形式就只需要改其中一個因子。
在我們的複數運算程序中,複數有可能用直角座標或極座標來表示,我們把這個有可能變動的因素提取出來組成複數存儲表示層:real_part
、img_part
、magnitude
、angle
、make_from_real_img
、make_from_mag_ang
。這一層看到的數據是結構體的兩個成員x
和y
,或者r
和A
,如果改變了結構體的實現就要改變這一層函數的實現,但函數介面不改變,因此調用這一層函數介面的複數運算層也不需要改變。複數運算層看到的數據只是一個抽象的“複數”的概念,知道它有直角座標和極座標,可以調用複數存儲表示層的函數得到這些座標。再往上看,其它使用複數運算的程序看到的數據是一個更為抽象的“複數”的概念,只知道它是一個數,像整數、小數一樣可以加減乘除,甚至連它有直角座標和極座標也不需要知道。
這裡的複數存儲表示層和複數運算層稱為抽象層(Abstraction Layer),從底層往上層來看,複數越來越抽象了,把所有這些層組合在一起就是一個完整的系統。組合使得系統可以任意複雜,而抽象使得系統的複雜性是可以控制的,任何改動都只侷限在某一層,而不會波及整個系統。著名的計算機科學家Butler Lampson說過:“All problems in computer science can be solved by another level of indirection.”這裡的indirection其實就是abstraction的意思。
1、在本節的基礎上實現一個打印複數的函數,打印的格式是x+yi,如果實部或虛部為0則省略,例如:1.0、-2.0i、-1.0+2.0i、1.0-2.0i。最後編寫一個main
函數測試本節的所有代碼。想一想這個打印函數應該屬於上圖中的哪一層?
2、實現一個用分子分母的格式來表示有理數的結構體rational
以及相關的函數,rational
結構體之間可以做加減乘除運算,運算的結果仍然是rational
。測試代碼如下:
int main(void) { struct rational a = make_rational(1, 8); /* a=1/8 */ struct rational b = make_rational(-1, 8); /* b=-1/8 */ print_rational(add_rational(a, b)); print_rational(sub_rational(a, b)); print_rational(mul_rational(a, b)); print_rational(div_rational(a, b)); return 0; }
注意要約分為最簡分數,例如1/8和-1/8相減的打印結果應該是1/4而不是2/8,可以利用第 3 節 “遞歸”練習題中的Euclid算法來約分。在動手編程之前先思考一下這個問題實現了什麼樣的數據抽象,抽象層應該由哪些函數組成。