1. 程式人生 > >C語言入門(15)——結構體與資料抽象

C語言入門(15)——結構體與資料抽象

大多數的計算機運算是對現實世界的模擬,如果想用計算機來模擬現實世界需要用到資料抽象的方法。所謂抽象是從實際的人、物、事和概念中抽取所關心的共同特徵,,忽略非本質的細節,吧這些特徵用各種概念精確的加以描述,從而使這些概念構成某種對現實世界進行描述的模型。

下面以數學中的複數為例項,通過結構體講解資料型別的組合和抽象。至於過程抽象我們已經見過最簡單的形式,就是把一組語句用一個函式名封裝起來,當作一個整體使用。

現在我們用C語言表示一個複數。如果從直角座標系來看,複數由實部和虛部組成,如果從極座標系來看,複數由模和輻角組成,兩種座標系可以相互轉換。如下圖所示

比如用實部和虛部表示一個複數,我們可以採用兩個double型組成的結構體:

struct complex_struct {
         doublex, y;
};

 

這樣定義了complex_struct這個識別符號,既然是識別符號,那麼它的命名規則就和變數一樣,但它不表示一個變數,而表示一個型別,struct complex_struct { double x, y; }整個可以看作一個型別名,就像int或double一樣,只不過它是一個複合型別,如果用這個型別名來定義變數,可以這樣寫:

struct complex_struct {
         doublex, y;
} z1, z2;

 

這樣z1和z2就是兩個變數名,變數定義後面要帶個;號。這點一定要注意,結構體定義後面少;號是初學者很常犯的錯誤。不管是用上面兩種形式的哪一種形式定義了complex_struct這個識別符號,以後都可以直接用struct complex_struct來代替型別名了。例如可以這樣定義另外兩個複數變數:

struct complex_struct z3, z4;

 

結構體變數也可以在定義時初始化,例如:

struct complex_struct z = { 3.0, 4.0 };

 

複數加法的運演算法則是實部與實部相加,虛部與虛部相加。複數相加運算的函式程式碼如下:

struct complex_struct add_complex(structcomplex_struct z1, struct complex_struct z2)
{
         z1.x= z1.x + z2.x;
         z1.y= z1.y + z2.y;
         returnz1;
}

 

此外,我們還提供一個函式用來構造複數變數:

struct complex_struct make (double x,double y)
{
         structcomplex_struct z;
         z.x= x;
         z.y= y;
         returnz;
}

 

現在我們來實現一個完整的複數運算的程式。在上一節我們已經定義了複數的結構體,現在需要圍繞它定義一些函式。複數可以用直角座標或極座標表示,直角座標做加減法比較方便,極座標做乘除法比較方便。如果我們定義的複數結構體是直角座標的,那麼應該提供極座標的轉換函式,以便在需要的時候可以方便地取它的模和輻角:

struct complex_struct {
         doublex, y;
};
 
double real_part(struct complex_struct z)
{
         returnz.x;
}
 
double img_part(struct complex_struct z)
{
         returnz.y;
}
 
double magnitude(struct complex_struct z)
{
         returnsqrt(z.x * z.x + z.y * z.y);
}
 
double angle(struct complex_struct z)
{
         doublePI = acos(-1.0);
 
         if(z.x > 0)
                   returnatan(z.y / z.x);
         else
                   returnatan(z.y / z.x) + PI;
}

此外,我們再提供一個可以提供極座標的函式用來構造複數變數,在函式中自動做相應的轉換然後返回構造的複數變數:

struct complex_structmake_from_mag_ang(double r, double A)
{
         structcomplex_struct z;
         z.x= r * cos(A);
         z.y= r * sin(A);
         returnz;
}


在此基礎上就可以實現複數的加減乘除運算了:

struct complex_struct add_complex(structcomplex_struct z1, struct complex_struct z2)
{
         returnmake_from_real_img(real_part(z1) + real_part(z2),
                                       img_part(z1) + img_part(z2));
}
 
struct complex_struct sub_complex(structcomplex_struct z1, struct complex_struct z2)
{
         returnmake_from_real_img(real_part(z1) - real_part(z2),
                                       img_part(z1) - img_part(z2));
}
 
struct complex_struct mul_complex(structcomplex_struct z1, struct complex_struct z2)
{
         returnmake_from_mag_ang(magnitude(z1) * magnitude(z2),
                                      angle(z1) + angle(z2));
}
 
struct complex_struct div_complex(structcomplex_struct z1, struct complex_struct z2)
{
         returnmake_from_mag_ang(magnitude(z1) / magnitude(z2),
                                      angle(z1) - angle(z2));
}

可以看出,複數加減乘除運算的實現並沒有直接訪問結構體complex_struct的成員x和y,而是把它看成一個整體,通過呼叫相關函式來取它的直角座標和極座標。這樣就可以非常方便地替換掉結構體complex_struct的儲存表示,例如改為用極座標來儲存:

struct complex_struct {
         doubler, A;
};
 
double real_part(struct complex_struct z)
{
         returnz.r * cos(z.A);
}
 
double img_part(struct complex_struct z)
{
         returnz.r * sin(z.A);
}
 
double magnitude(struct complex_struct z)
{
         returnz.r;
}
 
double angle(struct complex_struct z)
{
         returnz.A;
}
 
struct complex_structmake_from_real_img(double x, double y)
{
         structcomplex_struct z;
         doublePI = acos(-1.0);
         z.r= sqrt(x * x + y * y);
         if(x > 0)
                   z.A= atan(y / x);
         else
                   z.A= atan(y / x) + PI;
 
         returnz;
}
 
struct complex_structmake_from_mag_ang(double r, double A)
{
         structcomplex_struct z;
         z.r= r;
         z.A= A;
         returnz;
}


雖然結構體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,如果改變了結構體的實現就要改變這一層函式的實現,但函式介面不改變,因此呼叫這一層函式介面的複數運算層也不需要改變。複數運算層看到的資料只是一個抽象的“複數”的概念,知道它有直角座標和極座標,可以呼叫複數儲存表示層的函式得到這些座標。再往上看,其它使用複數運算的程式看到的資料是一個更為抽象的“複數”的概念,只知道它是一個數,像整數、小數一樣可以加減乘除,甚至連它有直角座標和極座標也不需要知道。

這裡的複數儲存表示層和複數運算層稱為抽象層,從底層往上層來看,“複數”這種資料越來越抽象了,把所有這些層組合在一起就是一個完整的系統。組合使得系統可以任意複雜,而抽象使得系統的複雜性是可以控制的,任何改動都只侷限在某一層,而不會影響整個系統。

我們通過一個複數儲存表示抽象層把complex_struct結構體的儲存格式和上層的複數運算函式隔開,complex_struct結構體既可以採用直角座標也可以採用極座標儲存。但有時候需要同時支援兩種儲存格式,比如先前已經採集了一些資料存在計算機中,有些資料是以極座標儲存的,有些資料是以直角座標儲存的,如果要把這些資料都存到complex_struct結構體中怎麼辦?一種辦法是complex_struct結構體採用直角座標格式,直角座標的資料可以直接存入complex_struct結構體,極座標的資料先用make_from_mag_ang函式轉成直角座標再存,但轉換總是會損失精度的。這裡介紹另一種辦法,complex_struct結構體由一個數據型別標誌和兩個浮點陣列成,如果資料型別標誌為0,那兩個浮點數就表示直角座標,如果資料型別標誌為1,那兩個浮點數就表示極座標。這樣,直角座標和極座標的資料都可以適配(Adapt)到complex_struct結構體中,無需轉換和損失精度:

enum coordinate_type { RECTANGULAR, POLAR};
struct complex_struct {
         enumcoordinate_type t;
         doublea, b;
};


enum關鍵字的作用和struct關鍵字類似,把coordinate_type這個識別符號定義為一個型別,只不過struct complex_struct表示一個結構體型別,而enum coordinate_type表示一個列舉(Enumeration)型別。列舉型別的成員是常量,它們的值編譯器自動分配,例如定義了上面的列舉型別之後,RECTANGULAR就表示常量0,POLAR就表示常量1。如果不希望從0開始分配,可以這樣定義:

enum coordinate_type { RECTANGULAR = 1,POLAR };


這樣,RECTANGULAR就表示常量1,而POLAR就表示常量2,這些常量的型別就是int。有一點需要注意,結構體的成員名和變數名不在同一名稱空間,但列舉的成員名和變數名卻在同一名稱空間,所以會出現命名衝突。例如這樣是不合法的:

int main(void)
{
         enumcoordinate_type { RECTANGULAR = 1, POLAR };
         intRECTANGULAR;
         printf("%d%d\n", RECTANGULAR, POLAR);
         return0;
}


complex_struct結構體的格式變了,就需要修改複數儲存表示層的函式,但只要保持函式介面不變就不會影響到上層函式。例如:

struct complex_structmake_from_real_img(double x, double y)
{
         structcomplex_struct z;
         z.t= RECTANGULAR;
         z.a= x;
         z.b= y;
         returnz;
}

struct complex_structmake_from_mag_ang(double r, double A)

{

         structcomplex_struct z;

         z.t= POLAR;

         z.a= r;

         z.b= A;

         returnz;

}