1. 程式人生 > >C語言 複合資料型別(結構體 共用體 列舉)

C語言 複合資料型別(結構體 共用體 列舉)

我們在前邊學資料型別的時候,主要是字元型、整型、浮點型等基本型別,而學陣列的時候,陣列的定義要求陣列元素必須是相同的資料型別。在實際應用中,有時候還需要把不同型別的資料組成一個有機的整體來處理,這些組合在一個整體中的資料之間還有一定的聯絡,比如一個學生的姓名、性別、年齡、考試成績等,這就引入了複合資料型別。複合資料型別主要包含結構體資料型別、共用體資料型別和列舉體資料型別。
結構體資料型別

首先我們回顧一下上面的例程,我們把 DS1302 的 7 個位元組的時間放到一個緩衝陣列中,然後把陣列中的值稍作轉換顯示到液晶上,這裡就存在一個小問題,DS1302 時間暫存器的定義並不是我們常用的“年月日時分秒”的順序,而是在中間加了一個位元組的“星期幾”,而且每當我要用這個時間的時候都要清楚的記得陣列的第幾個元素表示的是什麼,這樣一來,一是很容易出錯,二是程式的可讀性不強。當然你可以把每一個元素都定一個明確的變數名字,這樣就不容易出錯也易讀了,但結構上卻顯得很零散了。於是,我們就可以用結構體來將這一組彼此相關的資料做一個封裝,它們既組成了一個整體,易讀不易錯,而且可以單獨定義其中每一個成員的資料型別,比如說把年份用 unsigned int 型別,即 4 個十進位制位來表示顯然比 2 位更符合日常習慣,而其它的型別還是可以用 2 位來表示。結構體本身不是一個基本的資料型別,而是構造的,它每個成員可以是一個基本的資料型別或者是一個構造型別。

結構體既然是一種構造而成的資料型別,那麼在使用之前必須先定義它。

宣告結構體變數的一般格式如下:
struct 結構體名 {
型別 1
型別 2
……
型別 n
變數名 1;
變數名 2;
變數名 n;
} 結構體變數名 1, 結構體變數名 2, … 結構體變數名 n;
這種宣告方式是在宣告結構體型別的同時又用它定義了結構體變數,此時的結構體名是可以省略的,但如果省略後,就不能在別處再次定義這樣的結構體變量了。這種方式把型別定義和變數定義混在了一起,降低了程式的靈活性和可讀性,因此我們並不建議採用這種方式,而是推薦用以下這種方式:
struct 結構體名 {
型別 1 變數名 1;
型別 2 變數名 2;
……
型別 n 變數名 n;
};
struct 結構體名 結構體變數名 1, 結構體變數名 2, … 結構體變數名 n;
為了方便大家理解,我們來構造一個實際的表示日期時間的結構體。
struct sTime { //日期時間結構體定義
unsigned int year; //年
unsigned char mon; // 月
unsigned char day; // 日
unsigned char hour; // 時
unsigned char min; // 分
unsigned char sec; // 秒
unsigned char week; // 星期
};
struct sTime bufTime;
struct 是結構體型別的關鍵字,sTime 是這個結構體的名字,bufTime 就是定義了一個具體的結構體變數。那如果要給結構體變數的成員賦值的話,寫法是
bufTime.year = 0x2013;
bufTime.mon = 0x10;
陣列的元素也可以是結構體型別,因此可以構成結構體陣列,結構體陣列的每一個元素都是具有相同結構型別的結構體變數。例如我們前邊構造的這個結構型別,直接定義成 struct sTime bufTime[3];就表示定義了一個結構體陣列,這個陣列的 3 個元素,每一個都是一個結構體變數。同樣的道理,結構體陣列中的元素的成員如果需要賦值,就可以寫成
bufTime[0].year = 0x2013;
bufTime[0].mon = 0x10;
一個指標變數如果指向了一個結構體變數的時候,稱之為結構指標變數。結構指標變數是指向的結構體變數的首地址,通過結構體指標也可以訪問到這個結構變數。

結構指標變數宣告的一般形式如下:
struct sTime *pbufTime;
這裡要特別注意的是,使用結構體指標對結構體成員的訪問,和使用結構體變數名對結構體成員的訪問,其表示式有所不同。結構體指標對結構體成員的訪問表示式為
pbufTime->year = 0x2013;
或者是
(*pbufTime).year = 0x2013;
很明顯前者更簡潔,所以推薦大家使用前者。
共用體資料型別

共用體也稱之為聯合體,共用體定義和結構體十分類似,我們同樣是推薦以下形式:
union 共用體名 {
資料型別 1 成員名 1;
資料型別 2 成員名 2;
……
資料型別 n 成員名 n;
};
union 共用體名 共用體變數;
共用體表示的是幾個變數共用一個記憶體位置,也就是成員 1、成員 2……成員 n 都用一個記憶體位置。共用體成員的訪問方式和結構體是一樣的,成員訪問的方式是:共用體名.成員名,使用指標來訪問的方式是:共用體名->成員名。

共用體可以出現在結構體內,結構體也可以出現在共用體內,在我們程式設計的日常應用中,最多應用是結構體出現在共用體內,例如:
union{
unsigned int value;
struct{
unsigned char first;
unsigned char second;
} half;
} number;
這樣將一個結構體定義到一個共用體內部,我們如果採用無符號整型賦值的時候,直接呼叫 value 這個變數,同時,我們也可以通過訪問或賦值給 first 和 second 這兩個變數來訪問或修改 value 的高位元組和低位元組。

這樣看起來似乎是可以高效率的在 int 型變數和它的高低位元組之間切換訪問,但請回想一下,我們在介紹資料指標的時候就曾提到過,多位元組變數的位元組序取決於微控制器架構和編譯器,並非是固定不變的,所以這種方式寫好的程式程式碼在換到另一種微控制器和編譯環境後,就有可能是錯的,從安全和可移植的角度來講,這樣的程式碼是存在隱患的,所以現在諸多以安全為首要訴求的 C 語言程式設計規範裡乾脆直接禁止使用共用體。我們雖然不禁止,但也不推薦你用,除非你清楚的瞭解你所使用的開發環境的實現細節。

共用體和結構體的主要區別如下:
結構體和共用體都是由多個不同的資料型別成員組成,但在任何一個時刻,共用體只能存放一個被選中的成員,而結構體所有的成員都存在。
對於共同體的不同成員的賦值,將會改變其它成員的值,而對於結構體不同成員的賦值是相互之間不影響的。
列舉資料型別

在實際問題中,有些變數的取值被限定在一個有限的範圍內。例如,一個星期從週一到週日有 7 天,一年從 1 月到 12 月有 12 個月,蜂鳴器有響和不響兩種狀態等等。如果把這些變數定義成整型或者字元型不是很合適,因為這些變數都有自己的範圍。C 語言提供了一種稱為“列舉”的型別,在列舉型別的定義中列舉出所有可能的值,並可以為每一個值取一個形象化的名字,它的這一特性可以提高程式程式碼的可讀性。

列舉的說明形式如下:
enum 列舉名{
識別符號 1[=整型常數],
識別符號 2[=整型常數],
……
識別符號 n[=整型常數]
};
enum 列舉名 列舉變數;
列舉的說明形式中,如果沒有被初始化,那麼“=整型常數”是可以被省略的,如果是預設值的話,從第一個識別符號順序賦值 0、1、2„„,但是當列舉中任何一個成員被賦值後,它後邊的成員按照依次加 1 的規則確定數值。

列舉的使用,有幾點要注意:
列舉中每個成員結束符是逗號,而不是分號,最後一個成員可以省略逗號。
列舉成員的初始化值可以是負數,但是後邊的成員依然依次加 1。
列舉變數只能取列舉結構中的某個識別符號常量,不可以在範圍之外。