C語言探索之旅 | 第二部分第六課:建立你本身的變量類型

做者 謝恩銘,公衆號「程序員聯盟」(微信號:coderhub)。 轉載請註明出處。 原文:www.jianshu.com/p/39b41aa5c…程序員

《C語言探索之旅》全系列編程

內容簡介


  1. 前言
  2. 定義一個 struct
  3. 結構體的使用
  4. 結構體指針
  5. union
  6. enum
  7. 總結
  8. 第二部分第七課預告

1. 前言


上一課是 C語言探索之旅 | 第二部分第五課:預處理 ,應該是比較輕鬆的。數組

這一課將會很是使人激動也頗有意思,不過有些難度。bash

衆所周知,C語言是面向過程的編程語言,與 Java,C++,等面向對象的編程語言有所不一樣。微信

在面向對象的編程語言中,有(class)的概念。less

C語言是沒有類這種「類型」的,可是 C語言就不能「模擬」面向對象編程了嗎?編程語言

不,只要你設計得好,C語言也能夠模擬面向對象編程。函數

這一課咱們要學習的 struct(結構體)的知識就可使你有能力用 C語言實現「面向對象」。學習

前面咱們學習了指針,數組,字符串和預處理,掌握這些知識你的 C語言水平已經還不錯啦。可是咱們豈能就此止步,必須 Bigger than bigger~測試

除了使用 C語言已經定義的變量類型,咱們還能夠作一些更厲害的事情:建立你本身的變量類型

咱們能夠將其稱爲「自定義的變量類型」,咱們來看三種:struct,union 和 enum。

由於當你須要編寫比較複雜的程序時,你會發現建立自定義的變量類型是很重要的。

幸虧,這學起來其實也不是特別難。可是你們須要專心學習這一課,由於從下一課開始,咱們會一直用到 struct 了。

2. 定義一個 struct


什麼是 struct 呢?

struct 是 structure(表示「結構」)的縮寫,因此 struct 的專業術語是「結構體」。

定義:struct 就是一系列變量的集合,可是這些變量能夠是不一樣類型的。

這個定義是否是喚起了你們對咱們的老朋友數組的懷念啊?數組裏面的每一個成員都必須是同一個類型的,相比之下 struct 更靈活。

通常來講,咱們習慣把 struct 定義在 .h 頭文件中,也就是和預處理命令以及函數原型那羣「傢伙」在一塊兒。

下面就給出一個 struct 的例子:

struct 你的struct的名字
{
    char variable1;
    short variable2;
    int otherVariable;
    double numberDecimal;
};
複製代碼

能夠看到:struct 的定義以關鍵字 struct 開始,後面接你自定義的 struct 的名稱(好比 Dog,Cat,Person,等)。

通常來講,在個人代碼裏,個人 struct 的命名也是遵守變量的命名規則,惟有一點不同,就是 struct 的名稱我會將首字母大寫,例如:SchoolName。

可是個人普通變量通常都是首字母小寫,例如:studentNumber。

這樣作只是我的習慣,便於在代碼裏區分普通的變量和自定義的變量,以後會學到的 enum 和 union,我也是習慣將其名稱的首字母大寫。

在 struct 的名字以後,咱們須要寫上一對大括號,在這對大括號裏面寫入你的 struct 要包含的各類類型的變量。

一般說來,struct 的大括號內至少得定義兩個變量吧。若是隻有一個變量,那定義這個結構體也沒什麼意義。

注意:不要忘了,在大括號後面還要加上一個分號(;), 由於畢竟這個 struct 是一個變量,變量的定義最後都要加分號的。

如你所見,建立一個自定義的變量也不復雜麼。其實結構體就是各類基本類型變量的集合,是一個「大雜燴」。

固然之後的課程中咱們還會看到:結構體的嵌套定義(結構體裏包含另外一個結構體)。

結構體的例子


假設咱們須要自定義一個結構體,它儲存屏幕上的一個點的座標。

下面就給出 2D(D 是英語 dimension 的首字母,表示維度)世界的座標系的大體印象:

當咱們在 2D 世界中作研究時,咱們有兩個軸:

  • 橫座標軸(從左到右,通常也稱爲 x 軸)。
  • 縱座標軸(從下到上,通常也稱爲 y 軸)。

只要數學尚未還給小學體育老師,應該都知道 x 和 y 軸的。

如今,你能夠寫出一個名叫 Coordinate(表示「座標」)的 struct 的定義了嗎?我看好你!

能夠本身先寫,而後對一下咱們給出的參考答案:

struct Coordinate
{
    int x;  // 橫座標
    int y;  // 縱座標
};
複製代碼

很簡單,不是嗎?咱們的 Coordinate 這個 struct 包含了兩個變量:x 和 y,都是 int 類型,分別表示橫座標值和縱座標值。

固然了,若是你願意,也能夠建立一個表示 3D(三維)空間的點的 struct,只須要在剛纔的 Coordinate 這個結構體的基礎上加上 z 軸。

結構體裏面的數組


結構體裏面也能夠存放數組。

例如,能夠構造一個名叫 Person(表示「人」)的結構體,以下所示:

struct Person
{
    char firstName[100];  // 名
    char lastName[100];   // 姓
    char address[1000];   // 地址

    int age;  // 年齡
    int boy;  // 性別,布爾值 : 1 = boy(表示「男孩」), 0 = girl(表示「女孩」)
};
複製代碼

能夠看到,這個結構體變量包含五個基本的變量。

  • 前三個分別表示 名、姓和地址,是字符數組。

  • 第四個是年齡。

  • 第五個是性別,是一個「布爾值」(固然,C語言自己沒有定義布爾類型(true 或 false),可是能夠用數值來「表示」布爾值的真或假),boy 這個 int 變量的值若是爲 1,那就是表示男孩;若是爲 0,那就是女孩。

這個結構體能夠用於構建一個通信錄程序。固然,你徹底能夠在這個結構體裏再添加其餘變量,使其更完善。

只要內存夠,通常來講在一個結構體裏沒有變量的數目限制。

3. 結構體的使用


如今,咱們的結構體已經定義在 .h 頭文件裏了,那麼咱們就能夠在 include(「包含」)此頭文件的文件中使用這些結構體了。

如下展現如何建立一個類型爲 Coordinate(咱們以前已經定義了這個結構體,表示二維空間的座標)的變量:

#include "coordinate.h" // 假設包含結構體定義的頭文件叫 coordinate.h

int main(int argc, char *argv[])
{
    struct Coordinate point;  // 建立一個 Coordinate 類型的變量,名字是 point

    return 0;
}
複製代碼

如上,咱們建立了一個 Coordinate 類型的變量,名字是 point(表示「點」)。

這個變量自動擁有兩個子變量:x 和 y,都是 int 類型,分別表示此二維座標的橫座標值和縱座標值。

你也許要問:「建立結構體變量開頭的那個關鍵字 struct 是必須的嗎?」

是的,是必須的。

struct 關鍵字使電腦可以區分基礎變量類型(例如 int)和自定義變量類型(例如 Coordinate)。

然而,每次加 struct 關鍵字也有點麻煩。因此聰(懶)明(惰)伶(成)俐(性)的 C語言開發者設計了 typedef 關鍵字。

固然了,人類的大多數發明都是爲了「懶惰」的緣故,能提升效率誰不肯意啊?

typedef 關鍵字


typedef 是 C語言的一個關鍵字,是 type(表示「類型」)和 define(表示「定義」)的縮合,顧名思義是表示「類型定義」。

聽到「類型定義」,好像很難理解。但其實 typedef 的做用並無它的含義那麼「高深莫測」。

從新回到剛纔定義 Coordinate 這個結構體的 .h 頭文件中。咱們來加一條由 typedef 開頭的命令,目的是爲 Coordinate 結構體建立一個別名。

什麼是別名(alias)呢?

就好比有一我的,真實姓名叫王小明,別名能夠是小明,明明,等,但都表明那我的。

有點相似 C++ 語言的引用的機制。

因此對別名的操做就是對原先對象的操做。

好比小時候你上課不乖,老師點名的時候,點到你的小名或者你的真實名字,都是叫的你,你就得去罰站。

咱們就在 Coordinate 結構體的定義以前加這句命令吧,通常習慣加在後面的,可是加在前面也能夠:

typedef struct Coordinate Coordinate;

struct Coordinate
{
    int x;
    int y;
};
複製代碼

能夠看到,咱們新加了一行命令:

typedef struct Coordinate Coordinate;
複製代碼

爲了更好地理解這句命令的做用,咱們把它拆爲三部分來看:

  1. typedef:說明咱們將要建立一個別名。
  2. struct Coordinate:這是咱們要爲其建立別名的結構體。
  3. Coordinate:這就是要建立的別名。

因此,上面這句命令的含義就是「從今之後,Coordinate 就至關於 struct Coordinate 了 」。

這樣作之後,咱們就能夠不用每次在建立一個新的 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;  // 建立一個結構體變量
複製代碼

建議你們在平時定義了 struct 類型後,也加一句 typdedef 命令,這樣在代碼裏就不用每次新建一個此類型的變量時都要在開頭寫 struct 關鍵字了。

不少程序員都會這麼作。 由於一個好的程序員是懂得如何「偷懶」的程序員,這和一個懶惰的程序員是有區別的。 咱們要使代碼"write less,do more"(用盡可能少的代碼作更多的事)。

固然,上面的代碼塊能夠簡寫爲:

typedef struct struct的名字
{
  // struct 的內容
} 別名;
複製代碼

因此上面 Coordinate 的代碼塊能夠簡寫爲:

typedef struct Coordinate
{
    int x;
    int y;
} Coordinate;
複製代碼

注意:以後咱們的示例代碼,有時會出現例如

Person player1;
複製代碼

這樣的形式,那就是假定咱們以前已經用了 typedef 了:

typedef struct Person Person;
複製代碼

這樣就能夠省略開頭的 struct 關鍵字,不須要再寫成:

struct Person player1;
複製代碼

修改 struct 的成員變量


既然咱們的 point 變量(是 Coordinate 類型的,但願你們還沒暈)已經建立好了,那咱們就能夠修改它的成員的值了。

咱們如何訪問 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 來「模擬」類。

若是咱們用以前建立的 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 不是也能夠作到和上述程序相同的事麼?」

是的,可是用結構體的好處就是咱們能夠建立此結構體的變量,將不少相關聯的數據封裝在一塊兒,成爲一個總體,而不是零散地定義。

好比定義了 Person 這個結構體以後,凡是用 Person 來建立的變量,裏面都自動包含了 lastName,firstName,address,age 和 boy 這五個變量,很是方便。

好比咱們能夠這樣建立:

Person player1, player2;  // 以前已經用 typedef( typedef struct Person Person; )
複製代碼

在 player1 和 player2 中都包含 lastName,firstName,address,age 和 boy 這五個變量。

咱們也能夠更「偷懶」一些:建立結構體數組。例如:

Person players[2];
複製代碼

這樣,咱們就能夠很方便的訪問 players[1] 當中的變量了,例如:

players[1].lastName = "xiaoming";
複製代碼

用結構體數組的好處是能夠方便地使用循環,等等。

自測小練習


建立一個名叫 CoderHub(「程序員聯盟」公衆號)的結構體,在定義裏放入你想建立的變量。而後建立此結構體的一個數組,用循環的方式給變量賦值,再用循環的方式打印出其中變量的信息。

結構體的初始化

以前的課程裏,咱們建議對於基本變量,數組和指針,最好在建立的時候對其初始化。結構體也不例外。

初始化有一個很大的好處,就是避免此變量裏存放「任意數據」。

事實上,一個變量在建立時,若是沒有初始化,那麼它會取當時在內存那個位置所存的值,因此這個值的隨機性是很大的。

咱們來回憶一下,不一樣變量的初始化應該怎麼作:

  • 基礎變量(int,double,char,等):初始化爲 0。

  • 指針:初始化爲 NULL。事實上,NULL 位於 stdlib.h 標準庫頭文件中,是用 #define 預處理命令定義的一個常量。它的值一般是 0。雖然是 0,可是有多種定義形式,例如:

#define NULL 0
#define NULL 0L
#define NULL ((void *) 0)
複製代碼

可是咱們只要每次用 NULL 就行了,爲了清楚代表這是指針變量,而不是通常變量。

  • 數組:將每個成員變量初始化爲 0。

那麼對於咱們的「朋友」 結構體,咱們怎麼初始化呢?

其實結構體的初始化也很簡單,與數組的初始化很相似。咱們能夠像下面這樣定義:

Coordinate point = {0, 0};
複製代碼

這樣,咱們就依照順序將 point.x 和 point.y 都初始化爲 0 了。

對於像 Person 這樣的結構體,裏面的變量類型有 char 型數組和 int,那麼咱們能夠將 char 型數組初始化爲 ""(雙引號中間爲空)。

咱們能夠像這樣初始化一個字符串,在 C語言探索之旅 | 第二部分第四課:字符串 那一課忘記提了。不過,我想如今提還不算晚吧。

因此咱們就能夠這樣來初始化咱們的 Person 結構體變量:

Person player = {"", "", "", 0, 0};
複製代碼

然而,咱們也能夠這樣來初始化一個結構體變量:建立一個函數,好比叫 initializeStruct,能夠爲每個傳遞給它的結構體作初始化,這樣就方便不少,特別是當結構體中的變量不少時。

以前指針那一章咱們也已經學了,若是咱們對函數傳遞普通變量,那麼由於 C語言的函數參數傳遞方式是值傳遞,因此它會對傳給它的函數參數作一份拷貝,這樣函數裏面修改的實際上是那一份拷貝,真正的實參並無被改變。

爲了讓實參實實在在被修改,咱們須要用到指針,也就是傳遞此變量的地址。

對於結構體,也須要這樣。所以,接下來咱們就來學習如何使用結構體指針。開始難起來咯,準備好了嗎?

4. 結構體指針


結構體指針的建立其實和普通的指針變量建立沒什麼區別。例如:

Coordinate *point = NULL;
複製代碼

上面的代碼就建立了一個叫作 point 的 Coordinate 結構體指針變量(Coordinate 是咱們上面定義的表示座標的一個結構體)。

咱們再來提醒一次:

通常推薦寫成:

Coordinate *point = NULL; // 星號挨着指針變量名字
複製代碼

而不推薦寫成:

Coordinate* point = NULL;  // 星號挨着結構體名,這種寫法很差!
複製代碼

在指針的建立中,咱們推薦第一種寫法。

由於用第二種寫法,若是你在一行上建立好幾個指針變量時,會容易忘記在第二個以後的變量前加 * 號。例如,容易寫成這樣:

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 指針)。

回顧老是很好的(「傷心老是不免的...」)。

結構體做爲函數參數


這裏,咱們主要來學習如何將一個結構體指針(爲何是傳結構體指針而不是傳結構體,能夠看以前的解釋)傳給一個函數(做爲參數),使得函數內部能夠真正修改此結構體。

咱們來看一個實例:

#include <stdio.h>

typedef struct Coordinate
{
    int x;  // 橫座標值
    int y;  // 縱座標值
} Coordinate;

void initializeCoordinate(Coordinate *point);  // 函數原型

int main(int argc, char *argv[]) {
    Coordinate myPoint;

    initializeCoordinate(&myPoint);  // 函數的參數是 myPoint 變量的地址

    return 0;
}

// 用於初始化結構體變量
void initializeCoordinate(Coordinate *point) {
    // 結構體初始化的代碼
}
複製代碼

上面的 initializeCoordinate 函數體內,咱們將放置初始化結構體的成員變量的代碼。

咱們按順序來看一下這段代碼:

  • 首先,咱們定義了一個結構體,叫作 Coordinate,裏面包含兩個變量,x 和 y。

  • 咱們在 main 函數中建立了Coordinate 結構體的變量,名字叫 myPoint。

  • 咱們將 myPoint 的地址傳遞給 initializeCoordinate 這個函數。

  • 接下來,咱們就在 initializeCoordinate 函數中添加初始化 x 和 y 變量的代碼吧:

void initializeCoordinate(Coordinate *point){
    *point.x = 0;
    *point.y = 0;
}
複製代碼

point 前面的 * 號是必不可少的噢。由於,傳進函數的參數是一個結構體指針,咱們要取到此結構體,就須要用到「解引用」符號:星號(*)。

可是,認真的讀者看出上面這個函數中的錯誤了嗎?

咱們的初衷是想要:先用 * 號解引用 point 這個結構體指針,取到結構體,而後再用 . 號取到其中的變量 x 和 y。可是若是按上面的寫法,其實效果至關於以下:

*(point.x) = 0;
*(point.y) = 0;
複製代碼

由於 . 號的優先級是高於 * 號的。

有興趣能夠看一下 C語言運算符的優先級,不過以前的課咱們也說過了,記不清怎麼辦呢?加括號就解決啦。

上面的代碼編譯是通不過的,由於結構體指針 point 並無成員叫 x 和 y,並且,對於結構體指針咱們也不能用 . 號來取到什麼值。

所以,咱們須要修改一下。改成以下就能夠了:

void initializeCoordinate(Coordinate *point) {
    (*point).x = 0;
    (*point).y = 0;
}
複製代碼

這樣就對了。用括號去掉了運算符優先級的影響。

可是,以前也說過:程序員是懂得偷懶的一羣人。

若是每次要取結構體的成員變量都要這麼麻煩,先用 * 號,還要加括號,再用 . 號。想一想都要讓 Denis Ritchie(C語言的做者)老爺子醉了。他是決不容許這種事發生的,所以,他就定義了一個新的符號: ->(一個箭頭。是的,就是這麼「霸氣側漏」)。

用法以下:

point->x = 0;
複製代碼

就至關於:

(*point).x = 0;
複製代碼

是否是簡便了不少?

記住:這個符號,只能用在指針上面。

所以,咱們的函數能夠改寫爲:

void initializeCoordinate(Coordinate *point) {
    point->x = 0;
    point->y = 0;
}
複製代碼

咱們在 main 函數裏也能夠這樣寫:

int main(int argc, char *argv[])
{
    Coordinate myPoint;
    Coordinate *myPointPointer = &myPoint;

    myPoint.x = 10;  // 用結構體的方式,修改 myPoint 中的 x 值
    myPointPointer->y = 15;  // 用結構體指針的方式,修改 myPoint 中的 y 值

    return 0;
}
複製代碼

結構體是 C語言中一個很是好用且很重要的概念,但願你們好好掌握!

固然,還有很多知識細節,就要你們本身去看 C語言的經典教材了,例如《C程序設計語言》(不是譚浩強那本《C語言程序設計》!而是 C語言做者寫的經典之做),《C和指針》,《C專家編程》,《C語言深度解剖》,《C陷阱和缺陷》,等等。

5. union


union 是「聯合」的意思,是 C語言的關鍵字,也有的書上翻譯爲「共用體」。

咱們能夠來寫一個 union 的例子。

union CoderHub
{
    char character;
    int memberNumber;
    double rate;
};
複製代碼

乍看之下,和 struct 沒什麼區別麼。可是真的沒有區別嗎?

假如咱們用 C語言的 sizeof 關鍵字(size 表示「尺寸,大小」,of 表示「...的」)來測試此 union 的大小(大小指的是在內存中所佔的字節(byte)數,一個字節至關於 8 個 bit(二進制位)):

#include <stdio.h>

typedef union CoderHub
{
    char character;  // 大小是 1 個字節
    int memberNumber;  // 大小是 4 個字節
    double rate;  // 大小是 8 個字節
} CoderHub;

int main(int argc, char *argv[]){
    CoderHub coderHub;
 
    printf("此 union 的大小是 %lu 個字節\n", sizeof(coderHub));
 
    return 0;
}
複製代碼

運行程序,輸出:

此 union 的大小是 8 個字節
複製代碼

假如咱們對結構體也作一次測試,對比一下:

#include <stdio.h>

typedef struct CoderHub
{
    char character;  // 大小是 1 個字節
    int memberNumber;  // 大小是 4 個字節
    double rate;  // 大小是 8 個字節
} CoderHub;

int main(int argc, char *argv[]){

    CoderHub coderHub;

    printf("此 struct 的大小是 %lu 個字節\n", sizeof(coderHub));

    return 0;
}
複製代碼

運行程序,輸出:

此 struct 的大小是 16 個字節
複製代碼

爲何咱們自定義的 union 的大小是 8 個字節,而 struct 是 16 個字節呢?

這就涉及到 union(共用體)和 struct(結構體)的區別了。

struct 的大小是其中全部變量大小的總和。

可是你會說:「不對啊, 1 + 4 + 8 = 13,爲何 sizeof(coderHub) 的值爲 16 呢?」

好問題!這個有點複雜,涉及到內存對齊的問題,咱們之後再說。若是你必定要知道,那是由於內存對齊使得第一個 char 變量對齊了第二個 int 變量的空間,也變成了 4,如此一來:4 + 4 + 8 = 16。

有興趣的讀者能夠去參考《C語言深度解剖》的解釋。

在嵌入式編程等內存有限的環境下,須要考慮內存對齊,以節省空間。

union 的大小等於其中最大(sizeof() 獲得的值最大)的那個變量的大小。因此咱們就知道了,其實 union 的儲存是這樣的:其中的每一個變量在內存中的起始地址是同樣的,因此 union 同一時刻只能存放其中一個變量,union 的大小等於其中最大的那個變量,以保證能夠容納任意一個成員。

union 適合用在不少相同類型的變量集,可是某一時刻只需用到其中一個的狀況,比較節省空間。

6. enum


看完了 struct(結構體)和 union(聯合),咱們最後來學習很經常使用的一個自定義變量類型:enum。

enum 是 enumeration(表示「枚舉」)的縮寫,也是一個 C語言關鍵字。

枚舉是一個比較特別的自定義變量類型。當初我學 C語言時,一開始還真有點不理解。但用得好,卻很是實用。

咱們以前學了:結構體裏面包含了多個能夠是不一樣類型的成員變量(一說「成員」就有點面向對象的感受 :P)。

可是 enum(枚舉)裏面是一系列可選擇的值。也就是說每次只能取其中一個值,聽着和 union 有點相似啊。可是 enum 和 union 仍是有區別的。

咱們來舉一個例子就知道區別了:

typedef enum Shape Shape;

enum Shape   // shape 表示「身材、體型」
{
    THIN,   // thin 表示「瘦」
    MEDIUM,   // medium 表示「中等」
    FAT   // fat 表示「胖」
};
複製代碼

因此,咱們定義了一個名叫 Shape 的 enum 變量。其中有三個值,分別是 THIN,MEDIUM 和 FAT(身材有瘦,中等和胖之分)。不必定要大寫,只是習慣。

那咱們怎麼來建立 enum 變量呢?以下:

Shape shape = MEDIUM;
複製代碼

shape 這個變量,咱們在程序裏也能夠再將其修改成 THIN 或者 FAT。

將數值賦給 enum 的成員


你們看到 enum 和 union 以及 struct 的區別了嗎?是的,enum 的定義裏,每一個成員沒有變量類型(int,char,double,之類)!

很奇怪吧。想起來爲何 enum 的成員習慣用大寫了嗎?

對,就是由於 enum 的每一個成員都不是變量,而是常量!可是 enum 的機制和常量定義以及 #define 仍是有些區別:

像上面的代碼:

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,由於咱們已經指定了其值爲 90,因此 FAT 就等於 90。

enum 和 #define 的區別


是否是以爲 enum 和用 #define 來定義的常量是有些相似呢?

其實,仍是有些不一樣的:

  • #define 宏常量(或預處理常量)是在預處理階段進行簡單替換,枚舉常量則是在編譯的時候肯定其值。

  • 通常在編譯器裏,能夠調試枚舉常量,可是不能調試宏常量。

  • 枚舉能夠一次定義大量相關的常量,而 #define 宏一次只能定義一個。

7. 總結


  1. 結構體(struct)是一種自定義的變量類型,徹底由咱們自由發揮,本身定製(走的是「高級定製」的路線啊),與 int,double 等基礎變量類型有所區別。結構體的使用可讓咱們的 C語言程序更加靈活,能夠作更多事。

  2. 結構體裏包含成員變量,一般是基礎變量類型的變量,如 int,double 等變量,但也能夠有指針變量,數組,甚至其餘的結構體變量。

  3. 爲了訪問到結構體的成員變量,咱們能夠用普通的結構體方式訪問:結構體變量名稱.成員變量名(中間用一個「點」鏈接)。

  4. 咱們也能夠用特別簡便的結構體指針的方式來訪問結構體的成員變量:結構體指針變量名->成員變量名(中間用一個「箭頭」鏈接)。

  5. union(「共用體」,或「聯合」)和 struct 的最大不一樣就是:union 的大小是其中容量最大的那個成員變量的大小,而結構體的大小是每個成員變量的總和(還要考慮內存對齊)。union 一次只能取其中一個變量。

  6. enum(枚舉)一次只能取其中的一個成員的值,這一點和 union 有些相似。可是 enum 的成員都是常量,而不是變量。並且 enum 的成員若是沒有指定數值,編譯器會按照遞增順序爲每個變量賦值,從 0 開始。

8. 第二部分第七課預告


今天的課就到這裏,一塊兒加油吧!

下一課:C語言探索之旅 | 第二部分第七課:文件讀寫


我是 謝恩銘,公衆號「程序員聯盟」(微信號:coderhub)運營者,慕課網精英講師 Oscar 老師,終生學習者。 熱愛生活,喜歡游泳,略懂烹飪。 人生格言:「向着標杆直跑」

相關文章
相關標籤/搜索