零基礎學C語言——結構體、共同體、枚舉

這是一個C語言系列文章,如果是初學者的話,建議先行閱讀之前的文章。筆者也會按照章節順序發佈。

結構體

在 一文中,我們提到過,數組是一種集合,這個集合中的元素都是同一種數據類型的。下面介紹一種用於整合不同數據類型的集合結構——結構體

結構體定義的一般形式

<code>struct 結構體名 {
數據類型 成員名1;
數據類型 成員名2;
...
};

struct 結構體名 {
數據類型 成員名1;
數據類型 成員名2;
...
} 結構體變量/數組[數組長度];/<code>

這裡:

  • 結構體名和成員名都符合 的命名規範
  • 數據類型不僅涵蓋了基礎數據類型,還包含了自定義類型以及下面兩小節介紹的共同體和枚舉
  • 採用後一種形式定義結構體同時定義結構體變量或數組時,結構體名可以省略不寫
  • 定義結構體同時定義變量/數組時也可以同時給變量/數組賦初值,參見下面結構體變量賦初值方式

來看一個例子:

<code>struct person {
char name[64];
unsigned long age;
};/<code>

我們定義了一個名為person的結構體類型,這個結構體中包含了兩個成員,一個是字符數組name,一個是無符號長整型age。這是一個程序模擬人類的簡單例子。

上面我將類型二字做了突出,是因為這個結構體定義是一個類型。舉個例子,我們稱自己為人類,而人類並不是一個具體的人,而是泛指一類生物。而碼哥我是一個具體的人,我屬於人類這個稱謂所定義的範疇。等價到結構體上,這個結構體person只是一個類型(人類),而由這個類型定義的變量才是一個具體的人。


既然,我們定義的是一個人,那麼人也應該有性別。不考慮特殊群體的話,性別僅分為男和女。我們可以如下定義:

<code>struct person {
char name[64];
unsigned long age;
unsigned long sex;
};/<code>

我們可以給sex的值做個約定:0代表男,1代表女。

但是0和1其實僅僅需要一個比特就可以表達了,我們卻使用了一個8字節(64比特)的變量來表示。這是一種浪費,是否可以優化呢?

答案是肯定的。我們將使用位域來進行優化:

<code>struct person {
char name[64];
unsigned long age:63;
unsigned long sex:1;
};/<code>

對比一下,差別在於成員名後加了冒號和一個整數值。這是什麼含義呢?

unsigned long佔8字節內存,即64比特。冒號後的值表示,這個變量佔這個數據類型中的比特數。即age佔unsigned long這64個比特中的63個,還剩餘了一個比特,而sex佔了unsigned long中的1個比特。這二者組合在一起正好64個比特,也就是一個unsigned long變量的大小。此時,編譯器會將這兩個成員放入一個8字節內存中,一個佔63比特,一個佔1比特。

位域的一般形式

<code>數據類型 成員名1:所佔比特數;/<code>

其中,如果所佔比特數大於其數據類型自身所佔比特數,則編譯會報錯。


既然類型已經定義好了,我們就可以用其定義變量了。

<code>struct person Tom;
//或賦上初值
struct person Tom = {"Tom", 18, 0};/<code>

這裡,我們定義了一個類型為struct person的變量Tom。

可以看到,變量的定義形式與常規的變量定義形式一樣,並且對結構體的初始化是用大括號(非塊語句)包裹住的。

有了變量後,我希望對其name、age、sex成員進行訪問,例如對他們進行賦值,方法如下:

<code>Tom.name[0] = 'T';
Tom.name[1] = 'o';
Tom.name[2] = 'm';
Tom.name[3] = '\\0';
Tom.age = 18;
Tom.sex = 0;//0-male 1-female/<code>

對結構體變量中的成員訪問是通過 (.)來完成的

現在還有一個問題,這個Tom變量佔多大內存呢?

結構體所佔內存大小等於其所有成員大小之和加上編譯器額外填充的字節。以Tom為例,其成員大小之和為64字節(name)+8字節(age+sex),一共72字節。編譯器填充的字節涉及結構體對齊規則,本文不做延展,初學者暫時以快速入門為主,優化進階類內容可以自行查閱網上資料。


既然定義了變量,而變量又是存在於內存中,那麼結構體變量就有其內存地址,因此也就有結構體指針這一概念。

<code>struct person *ptr;
//或定義同時初始化
struct person *ptr = &Tom;/<code>

可以看到,結構體指針的定義與變量指針定義形式完全一樣。

記得 一文中有兩個成員選擇運算符—— . 和 ->。

在結構體變量訪問其成員時,使用的是.。而在結構體指針訪問其成員時,使用的是->。例如:

<code>ptr->age = 8; //將Tom的age改為了8/<code> 

我們曾在 一文中說過,函數參數都是值傳遞,因此不可能傳遞結構體這樣的集合結構給函數,所以當需要傳遞結構體給函數參數時,傳遞的是結構體指針。

<code>void output_person_information(struct person *p)
{
printf("name: %s, age: %lu, sex: %lu\\n", p->name, p->age, p->sex);
}/<code>

這裡,printf的%s用於輸出字符數組類型變量的內容,%lu用於輸出無符號長整型變量的數值。


既然結構體是一種類型,那麼可否定義這個類型的數組呢?

當然可以,例如:

<code>struct person couple[2];
//或同時賦初值
struct person couple[2] = {
{"Tom", 25, 0},
{"Susan", 23, 1}
};/<code>

共同體

首先給出共同體(union)定義的一般形式

<code>union 共同體名 {
數據類型 成員名1;

數據類型 成員名2;
...
};
或定義共同體同時定義共同體變量
union 共同體名 {
數據類型 成員名1;
數據類型 成員名2;
...
} 共同體變量/數組[數組長度];/<code>

其中:

  • 成員名符合 的命名規則
  • 採用後一種形式定義共同體同時定義共同體變量或數組時,共同體名可以省略不寫

共同體定義的也是一種類型定義

在我們日常生活中,每一個人都會有多重身份。例如,在公司我是員工,在家長面前我是子女,在孩子面前我是長輩。共同體就像將一個人的多重身份彙總在一起。沿用結構體一節中的person結構體定義,可以給出如下共同體代碼:

<code>union identity {
struct person employee;
struct person child;
struct person parent;
};/<code>

下面利用這個共同體類型來定義共同體變量:

<code>union identity my_identity;/<code>

共同體變量的大小是其定義包含的成員中佔內存最大的成員的大小

即,這個my_identity佔用的內存大小為72字節。

再例如:

<code>union example {
int i;
double r;
};
union example ex;/<code>

此時,ex的大小為8字節,即double類型的大小。


共同體的成員的使用與結構體有很大不同。結構體變量內的每個成員都有其各自的內存單元,因此可以賦予不同的值。但是共同體則不同,共同體所有成員共用同一塊存儲空間,對每個成員的修改,會直接影響到其他成員的內容。繼續前面identity的例子:

<code>struct person couple[2] = {
{"Tom", 25, 0},
{"Susan", 23, 1}
};
my_identity.employee = couple[0];
output_person_information(&my_identity.employee);//輸出的是Tom的信息

output_person_information(&my_identity.child);//輸出的也是Tom的信息
my_identity.parent = couple[1]; //由於共用同一塊內存,因此這次賦值將會影響employee和child的數據
output_person_information(&my_identity.employee);//輸出的是Susan的信息
output_person_information(&my_identity.child);//輸出的也是Susan的信息/<code>

枚舉

枚舉這個詞的意思大致是:列出某些有窮序列集的所有成員。舉個網上隨處可見的例子:一週有7天,週一到週日。一週就是一個有窮序列集,而它的所有成員就是週一到週日。

在C語言中,枚舉定義的一般形式為:

<code>enum 枚舉類型名 {
成員名1,
成員名2,
成員名3 = 值3,
...
};
或定義枚舉同時定義枚舉變量
enum 枚舉類型名 {
成員名1,
成員名2,
成員名3 = 值3,
...
} 枚舉變量/數組[數組長度];/<code>

其中:

  • 成員名符合 的命名規範
  • 採用後一種形式定義枚舉同時定義枚舉變量或數組時,枚舉名可以省略不寫
  • 成員名後可以給出值,也可以不給值,值是一個整數,值也可以是簡單的表達式,如:成員名1+10
  • 在不給值的情況下,第一個成員的值為0,第二個為1,逐個加1,以此類推;遇到給定值,則該成員的值為該值,並且其後成員的值默認情況下基於該值累加1

例如:

<code>enum week {
Monday,
Tuesday = 0,
Wednesday,
Thursday,
Friday = Wednesday+99,
Saturday,
Sunday
};/<code>

這個例子中,每個成員的值如下:

  • Monday——0
  • Tuesday——0
  • Wednesday——1
  • Thursday——2
  • Friday——100
  • Saturday——101
  • Sunday——102

枚舉類型的用處主要是:用成員名取代具體數值,有些類似常量,讓代碼更加直觀,便於維護。

綜合示例

<code>#include <stdio.h>

enum data_type {
integer,
real
};
union data {
int i;
float r;
};
struct input_value {
enum data_type type;
union data data;
};

int main(void)
{
struct input_value arr[2];
arr[0].type = integer;
arr[0].data.i = 10;
arr[1].type = real, arr[1].data.r = 0.1;
int i;
for (i = 0; i < sizeof(arr)/sizeof(struct input_value); ++i) {
if (arr[i].type == integer) {
printf("integer %d\\n", arr[i].data.i);
} else {
printf("real %f\\n", arr[i].data.r);
}
}
return 0;
}/<stdio.h>/<code>

這個例子中,定義了一個input_value結構體,這個結構體有兩個成員,一個類型為枚舉data_type的變量,一個類型為共同體data的變量。main中定義了一個結構體數組,先對數組內成員進行初始化。然後利用for和if遍歷並輸出數組元素的data成員數據。


喜歡的小夥伴可以關注碼哥,也可以給碼哥留言評論,如有建議或者意見也歡迎私信碼哥,我會第一時間回覆。


分享到:


相關文章: