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