作者 謝恩銘,公眾號「程序員聯(lián)盟」(微信號:coderhub)。
轉載請注明出處。
原文:http://www.itdecent.cn/p/39b41aa5cca7
《C語言探索之旅》全系列
內容簡介
- 前言
- 定義一個 struct
- 結構體的使用
- 結構體指針
- union
- enum
- 總結
- 第二部分第七課預告
1. 前言
上一課是 C語言探索之旅 | 第二部分第五課:預處理 ,應該是比較輕松的。
這一課將會非常令人激動也很有意思,不過有些難度。
眾所周知,C語言是面向過程的編程語言,與 Java,C++,等面向對象的編程語言有所不同。
在面向對象的編程語言中,有類(class)的概念。
C語言是沒有類這種“類型”的,但是 C語言就不能“模擬”面向對象編程了嗎?
不,只要你設計得好,C語言也可以模擬面向對象編程。
這一課我們要學習的 struct(結構體)的知識就可以使你有能力用 C語言實現(xiàn)“面向對象”。
前面我們學習了指針,數(shù)組,字符串和預處理,掌握這些知識你的 C語言水平已經(jīng)還不錯啦。但是我們豈能就此止步,必須 Bigger than bigger~
除了使用 C語言已經(jīng)定義的變量類型,我們還可以做一些更厲害的事情:創(chuàng)建你自己的變量類型。
我們可以將其稱為“自定義的變量類型”,我們來看三種:struct,union 和 enum。
因為當你需要編寫比較復雜的程序時,你會發(fā)現(xiàn)創(chuàng)建自定義的變量類型是很重要的。
幸好,這學起來其實也不是特別難。但是大家需要專心學習這一課,因為從下一課開始,我們會一直用到 struct 了。
2. 定義一個 struct
什么是 struct 呢?
struct 是 structure(表示“結構”)的縮寫,所以 struct 的專業(yè)術語是“結構體”。
定義:struct 就是一系列變量的集合,但是這些變量可以是不同類型的。
這個定義是不是喚起了大家對我們的老朋友數(shù)組的懷念???數(shù)組里面的每個成員都必須是同一個類型的,相比之下 struct 更靈活。
一般來說,我們習慣把 struct 定義在 .h 頭文件中,也就是和預處理命令以及函數(shù)原型那群“家伙”在一起。
下面就給出一個 struct 的例子:
struct 你的struct的名字
{
char variable1;
short variable2;
int otherVariable;
double numberDecimal;
};
可以看到:struct 的定義以關鍵字 struct 開始,后面接你自定義的 struct 的名稱(比如 Dog,Cat,Person,等)。
一般來說,在我的代碼里,我的 struct 的命名也是遵照變量的命名規(guī)則,唯有一點不一樣,就是 struct 的名稱我會將首字母大寫,例如:SchoolName。
但是我的普通變量一般都是首字母小寫,例如:studentNumber。
這樣做只是個人習慣,便于在代碼里區(qū)分普通的變量和自定義的變量,之后會學到的 enum 和 union,我也是習慣將其名稱的首字母大寫。
在 struct 的名字之后,我們需要寫上一對大括號,在這對大括號里面寫入你的 struct 要包含的各種類型的變量。
通常說來,struct 的大括號內至少得定義兩個變量吧。如果只有一個變量,那定義這個結構體也沒什么意義。
注意:不要忘了,在大括號后面還要加上一個分號(
;),
因為畢竟這個 struct 是一個變量,變量的定義最后都要加分號的。
如你所見,創(chuàng)建一個自定義的變量也不復雜么。其實結構體就是各種基本類型變量的集合,是一個“大雜燴”。
當然以后的課程中我們還會看到:結構體的嵌套定義(結構體里包含另一個結構體)。
結構體的例子
假設我們需要自定義一個結構體,它儲存屏幕上的一個點的坐標。
下面就給出 2D(D 是英語 dimension 的首字母,表示維度)世界的坐標系的大致印象:

當我們在 2D 世界中做研究時,我們有兩個軸:
- 橫坐標軸(從左到右,一般也稱為 x 軸)。
- 縱坐標軸(從下到上,一般也稱為 y 軸)。
只要數(shù)學還沒有還給小學體育老師,應該都知道 x 和 y 軸的。
現(xiàn)在,你可以寫出一個名叫 Coordinate(表示“坐標”)的 struct 的定義了嗎?我看好你!
可以自己先寫,然后對一下我們給出的參考答案:
struct Coordinate
{
int x; // 橫坐標
int y; // 縱坐標
};
很簡單,不是嗎?我們的 Coordinate 這個 struct 包含了兩個變量:x 和 y,都是 int 類型,分別表示橫坐標值和縱坐標值。
當然了,如果你愿意,也可以創(chuàng)建一個表示 3D(三維)空間的點的 struct,只需要在剛才的 Coordinate 這個結構體的基礎上加上 z 軸。
結構體里面的數(shù)組
結構體里面也可以存放數(shù)組。
例如,可以構造一個名叫 Person(表示“人”)的結構體,如下所示:
struct Person
{
char firstName[100]; // 名
char lastName[100]; // 姓
char address[1000]; // 地址
int age; // 年齡
int boy; // 性別,布爾值 : 1 = boy(表示“男孩”), 0 = girl(表示“女孩”)
};
可以看到,這個結構體變量包含五個基本的變量。
前三個分別表示 名、姓和地址,是字符數(shù)組。
第四個是年齡。
第五個是性別,是一個“布爾值”(當然,C語言本身沒有定義布爾類型(true 或 false),但是可以用數(shù)值來“表示”布爾值的真或假),boy 這個 int 變量的值如果為 1,那就是表示男孩;如果為 0,那就是女孩。
這個結構體可以用于構建一個通訊錄程序。當然,你完全可以在這個結構體里再添加其他變量,使其更完善。
只要內存夠,一般來說在一個結構體里沒有變量的數(shù)目限制。
3. 結構體的使用
現(xiàn)在,我們的結構體已經(jīng)定義在 .h 頭文件里了,那么我們就可以在 include(“包含”)此頭文件的文件中使用這些結構體了。
以下展示如何創(chuàng)建一個類型為 Coordinate(我們之前已經(jīng)定義了這個結構體,表示二維空間的坐標)的變量:
#include "coordinate.h" // 假設包含結構體定義的頭文件叫 coordinate.h
int main(int argc, char *argv[])
{
struct Coordinate point; // 創(chuàng)建一個 Coordinate 類型的變量,名字是 point
return 0;
}
如上,我們創(chuàng)建了一個 Coordinate 類型的變量,名字是 point(表示“點”)。
這個變量自動擁有兩個子變量:x 和 y,都是 int 類型,分別表示此二維坐標的橫坐標值和縱坐標值。
你也許要問:“創(chuàng)建結構體變量開頭的那個關鍵字 struct 是必須的嗎?”
是的,是必須的。
struct 關鍵字使電腦能夠區(qū)分基礎變量類型(例如 int)和自定義變量類型(例如 Coordinate)。
然而,每次加 struct 關鍵字也有點麻煩。所以聰(懶)明(惰)伶(成)俐(性)的 C語言開發(fā)者設計了 typedef 關鍵字。
當然了,人類的大多數(shù)發(fā)明都是為了“懶惰”的緣故,能提高效率誰不愿意???
typedef 關鍵字
typedef 是 C語言的一個關鍵字,是 type(表示“類型”)和 define(表示“定義”)的縮合,顧名思義是表示“類型定義”。
聽到“類型定義”,好像很難理解。但其實 typedef 的作用并沒有它的含義那么“高深莫測”。
重新回到剛才定義 Coordinate 這個結構體的 .h 頭文件中。我們來加一條由 typedef 開頭的命令,目的是為 Coordinate 結構體創(chuàng)建一個別名。
什么是別名(alias)呢?
就比如有一個人,真實姓名叫王小明,別名可以是小明,明明,等,但都代表那個人。
有點類似 C++ 語言的引用的機制。
所以對別名的操作就是對原先對象的操作。
比如小時候你上課不乖,老師點名的時候,點到你的小名或者你的真實名字,都是叫的你,你就得去罰站。
我們就在 Coordinate 結構體的定義之前加這句命令吧,一般習慣加在后面的,但是加在前面也可以:
typedef struct Coordinate Coordinate;
struct Coordinate
{
int x;
int y;
};
可以看到,我們新加了一行命令:
typedef struct Coordinate Coordinate;
為了更好地理解這句命令的作用,我們把它拆為三部分來看:
-
typedef:說明我們將要創(chuàng)建一個別名。 -
struct Coordinate:這是我們要為其創(chuàng)建別名的結構體。 -
Coordinate:這就是要創(chuàng)建的別名。
所以,上面這句命令的含義就是“從今以后,Coordinate 就相當于 struct Coordinate 了 ”。
這樣做以后,我們就可以不用每次在創(chuàng)建一個新的 Coordinate 結構體的變量時都加上 struct 關鍵字了。
所以,我們的 .c 文件中就可以改寫為:
int main(int argc, char *argv[])
{
Coordinate point; // 因為用了 typedef,電腦就清楚地知道此處的 Coordinate 其實就是 struct Coordinate
return 0;
}
當然,別名不一定要叫 Coordinate,也可以叫作 Coor,也許更不容易混淆。例如:
typedef struct Coordinate Coor;
struct Coordinate
{
int x;
int y;
};
Coor coor; // 創(chuàng)建一個結構體變量
建議大家在平時定義了 struct 類型后,也加一句 typdedef 命令,這樣在代碼里就不用每次新建一個此類型的變量時都要在開頭寫 struct 關鍵字了。
很多程序員都會這么做。
因為一個好的程序員是懂得如何“偷懶”的程序員,這和一個懶惰的程序員是有區(qū)別的。
我們要使代碼"write less,do more"(用盡量少的代碼做更多的事)。
當然,上面的代碼塊可以簡寫為:
typedef struct struct的名字
{
// struct 的內容
} 別名;
所以上面 Coordinate 的代碼塊可以簡寫為:
typedef struct Coordinate
{
int x;
int y;
} Coordinate;
注意:之后我們的示例代碼,有時會出現(xiàn)例如
Person player1;
這樣的形式,那就是假定我們之前已經(jīng)用了 typedef 了:
typedef struct Person Person;
這樣就可以省略開頭的 struct 關鍵字,不需要再寫成:
struct Person player1;
修改 struct 的成員變量
既然我們的 point 變量(是 Coordinate 類型的,希望大家還沒暈)已經(jīng)創(chuàng)建好了,那我們就可以修改它的成員的值了。
我們如何訪問 point 的兩個成員 x 和 y 呢?如下所示:
int main(int argc, char *argv[])
{
Coordinate point;
point.x = 10;
point.y = 20;
return 0;
}
這樣,我們就順利地修改了 point 的兩個成員的值,使其 x 坐標為 10,y 坐標為 20。
因此我們的點就位于坐標系的(10, 20)處了。
所以,為了能訪問到結構體的某個成員,我們可以這樣做:
結構體實例名稱.成員名
中間的點(.)表示“從屬”關系。
如果有面向對象編程基礎的朋友,就會覺得:這與“類和對象”也太像了吧。
是的,其實我們可以用 struct 來“模擬”類。
如果我們用之前創(chuàng)建的 Person 這個結構體來舉例的話:
int main(int argc, char *argv[])
{
Person user; // user 表示“用戶”
printf("你姓什么 ? ");
scanf("%s", user.lastName);
printf("你名叫什么 ? ");
scanf("%s", user.firstName);
printf("原來你的名字是 %s%s,失敬失敬\n", user.lastName, user.firstName);
return 0;
}
運行輸出:
你姓什么?王
你名叫什么?小明
原來你的名字是 王小明,失敬失敬
我們把 user.lastName 傳給 scanf,使得用戶輸入的值直接修改 user 的 lastName 成員;我們對 user.firstName 也是如此。
當然我們也可以再添加對 address,age,boy 的賦值。
當然了,你也許會說:“我不知道結構體的使用,我用兩個單獨的字符串變量 lastName 和 firstName 不是也可以做到和上述程序相同的事么?”
是的,但是用結構體的好處就是我們可以創(chuàng)建此結構體的變量,將很多相關聯(lián)的數(shù)據(jù)封裝在一起,成為一個整體,而不是零散地定義。
比如定義了 Person 這個結構體之后,凡是用 Person 來創(chuàng)建的變量,里面都自動包含了 lastName,firstName,address,age 和 boy 這五個變量,非常方便。
比如我們可以這樣創(chuàng)建:
Person player1, player2; // 之前已經(jīng)用 typedef( typedef struct Person Person; )
在 player1 和 player2 中都包含 lastName,firstName,address,age 和 boy 這五個變量。
我們也可以更“偷懶”一些:創(chuàng)建結構體數(shù)組。例如:
Person players[2];
這樣,我們就可以很方便的訪問 players[1] 當中的變量了,例如:
players[1].lastName = "xiaoming";
用結構體數(shù)組的好處是可以方便地使用循環(huán),等等。
自測小練習
創(chuàng)建一個名叫 CoderHub(「程序員聯(lián)盟」公眾號)的結構體,在定義里放入你想創(chuàng)建的變量。然后創(chuàng)建此結構體的一個數(shù)組,用循環(huán)的方式給變量賦值,再用循環(huán)的方式打印出其中變量的信息。
結構體的初始化
之前的課程里,我們建議對于基本變量,數(shù)組和指針,最好在創(chuàng)建的時候對其初始化。結構體也不例外。
初始化有一個很大的好處,就是避免此變量里存放“任意數(shù)據(jù)”。
事實上,一個變量在創(chuàng)建時,如果沒有初始化,那么它會取當時在內存那個位置所存的值,所以這個值的隨機性是很大的。
我們來回憶一下,不同變量的初始化應該怎么做:
基礎變量(int,double,char,等):初始化為 0。
指針:初始化為 NULL。事實上,NULL 位于 stdlib.h 標準庫頭文件中,是用
#define預處理命令定義的一個常量。它的值通常是 0。雖然是 0,但是有多種定義形式,例如:
#define NULL 0
#define NULL 0L
#define NULL ((void *) 0)
但是我們只要每次用 NULL 就好了,為了清楚表明這是指針變量,而不是一般變量。
- 數(shù)組:將每一個成員變量初始化為 0。
那么對于我們的“朋友” 結構體,我們怎么初始化呢?
其實結構體的初始化也很簡單,與數(shù)組的初始化很類似。我們可以像下面這樣定義:
Coordinate point = {0, 0};
這樣,我們就依照順序將 point.x 和 point.y 都初始化為 0 了。
對于像 Person 這樣的結構體,里面的變量類型有 char 型數(shù)組和 int,那么我們可以將 char 型數(shù)組初始化為 ""(雙引號中間為空)。
我們可以像這樣初始化一個字符串,在 C語言探索之旅 | 第二部分第四課:字符串 那一課忘記提了。不過,我想現(xiàn)在提還不算晚吧。
所以我們就可以這樣來初始化我們的 Person 結構體變量:
Person player = {"", "", "", 0, 0};
然而,我們也可以這樣來初始化一個結構體變量:創(chuàng)建一個函數(shù),比如叫 initializeStruct,可以為每一個傳遞給它的結構體做初始化,這樣就方便很多,特別是當結構體中的變量很多時。
之前指針那一章我們也已經(jīng)學了,如果我們對函數(shù)傳遞普通變量,那么因為 C語言的函數(shù)參數(shù)傳遞方式是值傳遞,所以它會對傳給它的函數(shù)參數(shù)做一份拷貝,這樣函數(shù)里面修改的其實是那一份拷貝,真正的實參并沒有被改變。
為了讓實參實實在在被修改,我們需要用到指針,也就是傳遞此變量的地址。
對于結構體,也需要這樣。因此,接下來我們就來學習如何使用結構體指針。開始難起來咯,準備好了嗎?
4. 結構體指針
結構體指針的創(chuàng)建其實和普通的指針變量創(chuàng)建沒什么區(qū)別。例如:
Coordinate *point = NULL;
上面的代碼就創(chuàng)建了一個叫做 point 的 Coordinate 結構體指針變量(Coordinate 是我們上面定義的表示坐標的一個結構體)。
我們再來提醒一次:
一般推薦寫成:
Coordinate *point = NULL; // 星號挨著指針變量名字
而不推薦寫成:
Coordinate* point = NULL; // 星號挨著結構體名,這種寫法不好!
在指針的創(chuàng)建中,我們推薦第一種寫法。
因為用第二種寫法,如果你在一行上創(chuàng)建好幾個指針變量時,會容易忘記在第二個之后的變量前加 * 號。例如,容易寫成這樣:
Coordinate* point1 = NULL, point2 = NULL; // 編譯會出錯
但這樣編譯會出錯,因為 point2 其實是 Coordinate 結構體變量,而不是 Coordinate 結構體指針變量!
所以我們建議這樣寫:
Coordinate *point1 = NULL, *point2 = NULL;
在以前的課程中,對于基礎類型的指針變量,我們也是這樣建議:
int *number1 = NULL, *number2 = NULL;
特別是 int 型的指針,還很不容易察覺到錯誤,如果寫成:
int* number1 = NULL, number2 = NULL;
編譯器是不會報錯的。因為 NULL 的值就是 0,可以賦給 number2 這個 int 型變量(注意:上面的 number2 不是 int 指針)。
回顧總是很好的(“傷心總是難免的...”)。
結構體作為函數(shù)參數(shù)
這里,我們主要來學習如何將一個結構體指針(為什么是傳結構體指針而不是傳結構體,可以看之前的解釋)傳給一個函數(shù)(作為參數(shù)),使得函數(shù)內部可以真正修改此結構體。
我們來看一個實例:
#include <stdio.h>
typedef struct Coordinate
{
int x; // 橫坐標值
int y; // 縱坐標值
} Coordinate;
void initializeCoordinate(Coordinate *point); // 函數(shù)原型
int main(int argc, char *argv[]) {
Coordinate myPoint;
initializeCoordinate(&myPoint); // 函數(shù)的參數(shù)是 myPoint 變量的地址
return 0;
}
// 用于初始化結構體變量
void initializeCoordinate(Coordinate *point) {
// 結構體初始化的代碼
}
上面的 initializeCoordinate 函數(shù)體內,我們將放置初始化結構體的成員變量的代碼。
我們按順序來看一下這段代碼:
首先,我們定義了一個結構體,叫做 Coordinate,里面包含兩個變量,x 和 y。
我們在 main 函數(shù)中創(chuàng)建了Coordinate 結構體的變量,名字叫 myPoint。
我們將 myPoint 的地址傳遞給 initializeCoordinate 這個函數(shù)。
接下來,我們就在 initializeCoordinate 函數(shù)中添加初始化 x 和 y 變量的代碼吧:
void initializeCoordinate(Coordinate *point){
*point.x = 0;
*point.y = 0;
}
point 前面的 * 號是必不可少的噢。因為,傳進函數(shù)的參數(shù)是一個結構體指針,我們要取到此結構體,就需要用到“解引用”符號:星號(*)。
但是,認真的讀者看出上面這個函數(shù)中的錯誤了嗎?
我們的初衷是想要:先用 * 號解引用 point 這個結構體指針,取到結構體,然后再用 . 號取到其中的變量 x 和 y。但是如果按上面的寫法,其實效果相當于如下:
*(point.x) = 0;
*(point.y) = 0;
因為 . 號的優(yōu)先級是高于 * 號的。
有興趣可以看一下 C語言運算符的優(yōu)先級,不過之前的課我們也說過了,記不清怎么辦呢?加括號就解決啦。
上面的代碼編譯是通不過的,因為結構體指針 point 并沒有成員叫 x 和 y,而且,對于結構體指針我們也不能用 . 號來取到什么值。
因此,我們需要修改一下。改為如下就可以了:
void initializeCoordinate(Coordinate *point) {
(*point).x = 0;
(*point).y = 0;
}
這樣就對了。用括號去掉了運算符優(yōu)先級的影響。
但是,之前也說過:程序員是懂得偷懶的一群人。
如果每次要取結構體的成員變量都要這么麻煩,先用 * 號,還要加括號,再用 . 號。想想都要讓 Denis Ritchie(C語言的作者)老爺子醉了。他是決不允許這種事發(fā)生的,因此,他就定義了一個新的符號: ->(一個箭頭。是的,就是這么“霸氣側漏”)。
用法如下:
point->x = 0;
就相當于:
(*point).x = 0;
是不是簡便了很多?
記?。哼@個符號,只能用在指針上面。
因此,我們的函數(shù)可以改寫為:
void initializeCoordinate(Coordinate *point) {
point->x = 0;
point->y = 0;
}
我們在 main 函數(shù)里也可以這樣寫:
int main(int argc, char *argv[])
{
Coordinate myPoint;
Coordinate *myPointPointer = &myPoint;
myPoint.x = 10; // 用結構體的方式,修改 myPoint 中的 x 值
myPointPointer->y = 15; // 用結構體指針的方式,修改 myPoint 中的 y 值
return 0;
}
結構體是 C語言中一個非常好用且很重要的概念,希望大家好好掌握!
當然,還有不少知識細節(jié),就要大家自己去看 C語言的經(jīng)典教材了,例如《C程序設計語言》(不是譚浩強那本《C語言程序設計》!而是 C語言作者寫的經(jīng)典之作),《C和指針》,《C專家編程》,《C語言深度解剖》,《C陷阱和缺陷》,等等。
5. union
union 是“聯(lián)合”的意思,是 C語言的關鍵字,也有的書上翻譯為“共用體”。
我們可以來寫一個 union 的例子。
union CoderHub
{
char character;
int memberNumber;
double rate;
};
乍看之下,和 struct 沒什么區(qū)別么。但是真的沒有區(qū)別嗎?
假如我們用 C語言的 sizeof 關鍵字(size 表示“尺寸,大小”,of 表示“...的”)來測試此 union 的大?。ù笮≈傅氖窃趦却嬷兴嫉淖止?jié)(byte)數(shù),一個字節(jié)相當于 8 個 bit(二進制位)):
#include <stdio.h>
typedef union CoderHub
{
char character; // 大小是 1 個字節(jié)
int memberNumber; // 大小是 4 個字節(jié)
double rate; // 大小是 8 個字節(jié)
} CoderHub;
int main(int argc, char *argv[]){
CoderHub coderHub;
printf("此 union 的大小是 %lu 個字節(jié)\n", sizeof(coderHub));
return 0;
}
運行程序,輸出:
此 union 的大小是 8 個字節(jié)
假如我們對結構體也做一次測試,對比一下:
#include <stdio.h>
typedef struct CoderHub
{
char character; // 大小是 1 個字節(jié)
int memberNumber; // 大小是 4 個字節(jié)
double rate; // 大小是 8 個字節(jié)
} CoderHub;
int main(int argc, char *argv[]){
CoderHub coderHub;
printf("此 struct 的大小是 %lu 個字節(jié)\n", sizeof(coderHub));
return 0;
}
運行程序,輸出:
此 struct 的大小是 16 個字節(jié)
為什么我們自定義的 union 的大小是 8 個字節(jié),而 struct 是 16 個字節(jié)呢?
這就涉及到 union(共用體)和 struct(結構體)的區(qū)別了。
struct 的大小是其中所有變量大小的總和。
但是你會說:“不對啊, 1 + 4 + 8 = 13,為什么 sizeof(coderHub) 的值為 16 呢?”
好問題!這個有點復雜,涉及到內存對齊的問題,我們以后再說。如果你一定要知道,那是因為內存對齊使得第一個 char 變量對齊了第二個 int 變量的空間,也變成了 4,如此一來:4 + 4 + 8 = 16。
有興趣的讀者可以去參考《C語言深度解剖》的解釋。
在嵌入式編程等內存有限的環(huán)境下,需要考慮內存對齊,以節(jié)省空間。
union 的大小等于其中最大(sizeof() 得到的值最大)的那個變量的大小。所以我們就知道了,其實 union 的儲存是這樣的:其中的每個變量在內存中的起始地址是一樣的,所以 union 同一時刻只能存放其中一個變量,union 的大小等于其中最大的那個變量,以保證可以容納任意一個成員。
union 適合用在很多相同類型的變量集,但是某一時刻只需用到其中一個的情況,比較節(jié)省空間。
6. enum
看完了 struct(結構體)和 union(聯(lián)合),我們最后來學習很常用的一個自定義變量類型:enum。
enum 是 enumeration(表示“枚舉”)的縮寫,也是一個 C語言關鍵字。
枚舉是一個比較特別的自定義變量類型。當初我學 C語言時,一開始還真有點不理解。但用得好,卻非常實用。
我們之前學了:結構體里面包含了多個可以是不同類型的成員變量(一說“成員”就有點面向對象的感覺 :P)。
但是 enum(枚舉)里面是一系列可選擇的值。也就是說每次只能取其中一個值,聽著和 union 有點類似啊。但是 enum 和 union 還是有區(qū)別的。
我們來舉一個例子就知道區(qū)別了:
typedef enum Shape Shape;
enum Shape // shape 表示“身材、體型”
{
THIN, // thin 表示“瘦”
MEDIUM, // medium 表示“中等”
FAT // fat 表示“胖”
};
所以,我們定義了一個名叫 Shape 的 enum 變量。其中有三個值,分別是 THIN,MEDIUM 和 FAT(身材有瘦,中等和胖之分)。不一定要大寫,只是習慣。
那我們怎么來創(chuàng)建 enum 變量呢?如下:
Shape shape = MEDIUM;
shape 這個變量,我們在程序里也可以再將其修改為 THIN 或者 FAT。
將數(shù)值賦給 enum 的成員
大家看到 enum 和 union 以及 struct 的區(qū)別了嗎?是的,enum 的定義里,每個成員沒有變量類型(int,char,double,之類)!
很奇怪吧。想起來為什么 enum 的成員習慣用大寫了嗎?
對,就是因為 enum 的每個成員都不是變量,而是常量!但是 enum 的機制和常量定義以及 #define 還是有些區(qū)別:
像上面的代碼:
typedef enum Shape
{
THIN,
MEDIUM,
FAT
} Shape;
編譯器會自動為其中的每一個成員綁定一個常量值,我們寫程序測試一下:
#include <stdio.h>
typedef enum Shape Shape;
enum Shape
{
THIN,
MEDIUM,
FAT
};
int main(int argc, char *argv[]) {
Shape shape = THIN;
printf("THIN = %d\n", shape);
shape = MEDIUM;
printf("MEDIUM = %d\n", shape);
shape = FAT;
printf("FAT = %d\n", shape);
return 0;
}
運行程序,輸出:
THIN = 0
MEDIUM = 1
FAT = 2
看到了嗎?編譯器自動給這三個成員賦值 0,1 和 2。如果沒有指定 enum 成員的值,那么它們的值是從 0 開始,依次加 1。
我們也可以自己來定義 enum 成員的值,不一定要每次讓編譯器給我們自動分配。
我們可以這樣寫:
typedef enum Shape
{
THIN = 40,
MEDIUM = 60,
FAT = 90
} Shape;
這樣,我們就自己給每個成員定義了值。
我們也可以讓編譯器為我們自動分配幾個值,再自己定義幾個值,例如:
typedef enum Shape
{
THIN,
MEDIUM,
FAT = 90
} Shape;
上面,我們沒有為 THIN 和 MEDIUM 賦值,那么編譯器會將他們賦值為 0 和 1。
而 FAT,因為我們已經(jīng)指定了其值為 90,所以 FAT 就等于 90。
enum 和 #define 的區(qū)別
是不是覺得 enum 和用 #define 來定義的常量是有些類似呢?
其實,還是有些不同的:
#define宏常量(或預處理常量)是在預處理階段進行簡單替換,枚舉常量則是在編譯的時候確定其值。一般在編譯器里,可以調試枚舉常量,但是不能調試宏常量。
枚舉可以一次定義大量相關的常量,而
#define宏一次只能定義一個。
7. 總結
結構體(struct)是一種自定義的變量類型,完全由我們自由發(fā)揮,自己定制(走的是“高級定制”的路線?。?,與 int,double 等基礎變量類型有所區(qū)別。結構體的使用可以讓我們的 C語言程序更加靈活,可以做更多事。
結構體里包含成員變量,通常是基礎變量類型的變量,如 int,double 等變量,但也可以有指針變量,數(shù)組,甚至其他的結構體變量。
為了訪問到結構體的成員變量,我們可以用普通的結構體方式訪問:
結構體變量名稱.成員變量名(中間用一個“點”連接)。我們也可以用特別簡便的結構體指針的方式來訪問結構體的成員變量:
結構體指針變量名->成員變量名(中間用一個“箭頭”連接)。union(“共用體”,或“聯(lián)合”)和 struct 的最大不同就是:union 的大小是其中容量最大的那個成員變量的大小,而結構體的大小是每一個成員變量的總和(還要考慮內存對齊)。union 一次只能取其中一個變量。
enum(枚舉)一次只能取其中的一個成員的值,這一點和 union 有些類似。但是 enum 的成員都是常量,而不是變量。而且 enum 的成員如果沒有指定數(shù)值,編譯器會按照遞增順序為每一個變量賦值,從 0 開始。
8. 第二部分第七課預告
今天的課就到這里,一起加油吧!
我是 謝恩銘,公眾號「程序員聯(lián)盟」(微信號:coderhub)運營者,慕課網(wǎng)精英講師 Oscar 老師,終生學習者。
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:「向著標桿直跑」