我們在前邊學(xué)數(shù)據(jù)類型的時候,主要是字符型、整型、浮點型等基本類型,而學(xué)數(shù)組的時候,數(shù)組的定義要求數(shù)組元素必須是相同的數(shù)據(jù)類型。在實際應(yīng)用中,有時候還需要把不同類型的數(shù)據(jù)組成一個有機的整體來處理,這些組合在一個整體中的數(shù)據(jù)之間還有一定的聯(lián)系,比如一個學(xué)生的姓名、性別、年齡、考試成績等,這就引入了復(fù)合數(shù)據(jù)類型。復(fù)合數(shù)據(jù)類型主要包含結(jié)構(gòu)體數(shù)據(jù)類型、共用體數(shù)據(jù)類型和枚舉體數(shù)據(jù)類型。
首先我們回顧一下上面的例程,我們把 DS1302 的7個字節(jié)的時間放到一個緩沖數(shù)組中,然后把數(shù)組中的值稍作轉(zhuǎn)換顯示到液晶上,這里就存在一個小問題,DS1302 時間寄存器的定義并不是我們常用的“年月日時分秒”的順序,而是在中間加了一個字節(jié)的“星期幾”,而且每當(dāng)我要用這個時間的時候都要清楚的記得數(shù)組的第幾個元素表示的是什么,這樣一來,一是很容易出錯,二是程序的可讀性不強。當(dāng)然你可以把每一個元素都定一個明確的變量名字,這樣就不容易出錯也易讀了,但結(jié)構(gòu)上卻顯得很零散了。于是,我們就可以用結(jié)構(gòu)體來將這一組彼此相關(guān)的數(shù)據(jù)做一個封裝,它們既組成了一個整體,易讀不易錯,而且可以單獨定義其中每一個成員的數(shù)據(jù)類型,比如說把年份用 unsigned int 類型,即4個十進(jìn)制位來表示顯然比2位更符合日常習(xí)慣,而其它的類型還是可以用2位來表示。結(jié)構(gòu)體本身不是一個基本的數(shù)據(jù)類型,而是構(gòu)造的,它每個成員可以是一個基本的數(shù)據(jù)類型或者是一個構(gòu)造類型。
結(jié)構(gòu)體既然是一種構(gòu)造而成的數(shù)據(jù)類型,那么在使用之前必須先定義它。
聲明結(jié)構(gòu)體變量的一般格式如下:
struct 結(jié)構(gòu)體名 {
類型 1
類型 2
……
類型 n
變量名1;
變量名2;
變量名 n;
} 結(jié)構(gòu)體變量名1, 結(jié)構(gòu)體變量名2, ... 結(jié)構(gòu)體變量名 n;
這種聲明方式是在聲明結(jié)構(gòu)體類型的同時又用它定義了結(jié)構(gòu)體變量,此時的結(jié)構(gòu)體名是可以省略的,但如果省略后,就不能在別處再次定義這樣的結(jié)構(gòu)體變量了。這種方式把類型定義和變量定義混在了一起,降低了程序的靈活性和可讀性,因此我們并不建議采用這種方式,而是推薦用以下這種方式:
struct 結(jié)構(gòu)體名 {
類型1 變量名1;
類型2 變量名2;
……
類型 n 變量名 n;
};
struct 結(jié)構(gòu)體名 結(jié)構(gòu)體變量名1, 結(jié)構(gòu)體變量名2, ... 結(jié)構(gòu)體變量名 n;
為了方便大家理解,我們來構(gòu)造一個實際的表示日期時間的結(jié)構(gòu)體。
struct sTime { //日期時間結(jié)構(gòu)體定義
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 是結(jié)構(gòu)體類型的關(guān)鍵字,sTime 是這個結(jié)構(gòu)體的名字,bufTime 就是定義了一個具體的結(jié)構(gòu)體變量。那如果要給結(jié)構(gòu)體變量的成員賦值的話,寫法是
bufTime.year = 0x2013;
bufTime.mon = 0x10;
數(shù)組的元素也可以是結(jié)構(gòu)體類型,因此可以構(gòu)成結(jié)構(gòu)體數(shù)組,結(jié)構(gòu)體數(shù)組的每一個元素都是具有相同結(jié)構(gòu)類型的結(jié)構(gòu)體變量。例如我們前邊構(gòu)造的這個結(jié)構(gòu)類型,直接定義成 struct sTime bufTime[3];就表示定義了一個結(jié)構(gòu)體數(shù)組,這個數(shù)組的3個元素,每一個都是一個結(jié)構(gòu)體變量。同樣的道理,結(jié)構(gòu)體數(shù)組中的元素的成員如果需要賦值,就可以寫成
bufTime[0].year = 0x2013;
bufTime[0].mon = 0x10;
一個指針變量如果指向了一個結(jié)構(gòu)體變量的時候,稱之為結(jié)構(gòu)指針變量。結(jié)構(gòu)指針變量是指向的結(jié)構(gòu)體變量的首地址,通過結(jié)構(gòu)體指針也可以訪問到這個結(jié)構(gòu)變量。
結(jié)構(gòu)指針變量聲明的一般形式如下:
struct sTime *pbufTime;
這里要特別注意的是,使用結(jié)構(gòu)體指針對結(jié)構(gòu)體成員的訪問,和使用結(jié)構(gòu)體變量名對結(jié)構(gòu)體成員的訪問,其表達(dá)式有所不同。結(jié)構(gòu)體指針對結(jié)構(gòu)體成員的訪問表達(dá)式為
pbufTime->year = 0x2013;
或者是
(*pbufTime).year = 0x2013;
很明顯前者更簡潔,所以推薦大家使用前者。
共用體也稱之為聯(lián)合體,共用體定義和結(jié)構(gòu)體十分類似,我們同樣是推薦以下形式:
union 共用體名 {
數(shù)據(jù)類型1 成員名1;
數(shù)據(jù)類型2 成員名2;
……
數(shù)據(jù)類型 n 成員名 n;
};
union 共用體名 共用體變量;
共用體表示的是幾個變量共用一個內(nèi)存位置,也就是成員1、成員2……成員 n 都用一個內(nèi)存位置。共用體成員的訪問方式和結(jié)構(gòu)體是一樣的,成員訪問的方式是:共用體名.成員名,使用指針來訪問的方式是:共用體名->成員名。
共用體可以出現(xiàn)在結(jié)構(gòu)體內(nèi),結(jié)構(gòu)體也可以出現(xiàn)在共用體內(nèi),在我們編程的日常應(yīng)用中,最多應(yīng)用是結(jié)構(gòu)體出現(xiàn)在共用體內(nèi),例如:
union{
unsigned int value;
struct{
unsigned char first;
unsigned char second;
} half;
} number;
這樣將一個結(jié)構(gòu)體定義到一個共用體內(nèi)部,我們?nèi)绻捎脽o符號整型賦值的時候,直接調(diào)用 value 這個變量,同時,我們也可以通過訪問或賦值給 first 和 second 這兩個變量來訪問或修改 value 的高字節(jié)和低字節(jié)。
這樣看起來似乎是可以高效率的在 int 型變量和它的高低字節(jié)之間切換訪問,但請回想一下,我們在介紹數(shù)據(jù)指針的時候就曾提到過,多字節(jié)變量的字節(jié)序取決于單片機架構(gòu)和編譯器,并非是固定不變的,所以這種方式寫好的程序代碼在換到另一種單片機和編譯環(huán)境后,就有可能是錯的,從安全和可移植的角度來講,這樣的代碼是存在隱患的,所以現(xiàn)在諸多以安全為首要訴求的 C 語言編程規(guī)范里干脆直接禁止使用共用體。我們雖然不禁止,但也不推薦你用,除非你清楚的了解你所使用的開發(fā)環(huán)境的實現(xiàn)細(xì)節(jié)。
共用體和結(jié)構(gòu)體的主要區(qū)別如下:
在實際問題中,有些變量的取值被限定在一個有限的范圍內(nèi)。例如,一個星期從周一到周日有7天,一年從1月到12月有12個月,蜂鳴器有響和不響兩種狀態(tài)等等。如果把這些變量定義成整型或者字符型不是很合適,因為這些變量都有自己的范圍。C 語言提供了一種稱為“枚舉”的類型,在枚舉類型的定義中列舉出所有可能的值,并可以為每一個值取一個形象化的名字,它的這一特性可以提高程序代碼的可讀性。
枚舉的說明形式如下:
enum 枚舉名{
標(biāo)識符 1[=整型常數(shù)],
標(biāo)識符 2[=整型常數(shù)],
……
標(biāo)識符 n[=整型常數(shù)]
};
enum 枚舉名 枚舉變量;
枚舉的說明形式中,如果沒有被初始化,那么“=整型常數(shù)”是可以被省略的,如果是默認(rèn)值的話,從第一個標(biāo)識符順序賦值0、1、2??,但是當(dāng)枚舉中任何一個成員被賦值后,它后邊的成員按照依次加1的規(guī)則確定數(shù)值。
枚舉的使用,有幾點要注意: