C語言探索之旅 | 第二部分第六課:創(chuàng)建你自己的變量類型

上一課是 [C語言探索之旅 | 第二部分第五課:預(yù)處理(http://www.reibang.com/p/5fad915696de)
應(yīng)該是比較輕松的刁憋。

這一課將會非常令人激動也很有意思,不過有些難度木蹬。

眾所周知至耻,C語言是面向過程的編程語言,與 Java,C++尘颓,等面向?qū)ο蟮木幊陶Z言有所不同走触。

在面向?qū)ο蟮木幊陶Z言中,有 (class)的概念疤苹。

C語言是沒有類這種“類型”的互广,但是 C語言就不能“模擬”面向?qū)ο缶幊塘藛幔?/p>

不,只要你設(shè)計得好痰催,C語言也可以模擬面向?qū)ο缶幊獭?/p>

這一課我們要學(xué)習(xí)的 struct(結(jié)構(gòu)體)的知識就可以使你有能力用 C語言實現(xiàn)“面向?qū)ο蟆薄?/p>

前面我們學(xué)習(xí)了指針兜辞,數(shù)組,字符串和預(yù)處理夸溶,掌握這些知識你的 C語言水平已經(jīng)還不錯啦逸吵。但是我們豈能就此止步,必須 Bigger than bigger~

除了使用 C語言已經(jīng)定義的變量類型缝裁,我們還可以做一些更厲害的事情: 創(chuàng)建你自己的變量類型 扫皱。

我們可以將其稱為“自定義的變量類型”,我們來看三種:struct捷绑,union 和 enum韩脑。

因為當(dāng)你需要編寫比較復(fù)雜的程序時,你會發(fā)現(xiàn)創(chuàng)建自定義的變量類型是很重要的粹污。

幸好段多,這學(xué)起來其實也不是特別難。但是大家需要專心學(xué)習(xí)這一課壮吩,因為從下一課開始进苍,我們會一直用到 struct 了。

2. 定義一個 struct

什么是 struct 呢鸭叙?

struct 是 structure(表示“結(jié)構(gòu)”)的縮寫觉啊,所以 struct 的專業(yè)術(shù)語是“結(jié)構(gòu)體”。

定義:struct 就是一系列變量的集合沈贝,但是這些變量可以是 不同 類型的杠人。

這個定義是不是喚起了大家對我們的老朋友 數(shù)組 的懷念啊宋下?數(shù)組里面的每個成員都必須是 同一個 類型的嗡善,相比之下 struct 更靈活。

一般來說学歧,我們習(xí)慣把 struct 定義在 .h 頭文件中罩引,也就是和預(yù)處理命令以及函數(shù)原型那群“家伙”在一起。

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

struct 你的struct的名字
{
    char variable1;
    short variable2;
    int otherVariable;
    double numberDecimal;
};
復(fù)制代碼

可以看到:struct 的定義以關(guān)鍵字 struct 開始撩满,后面接你自定義的 struct 的名稱(比如 Dog,Cat,Person伺帘,等)昭躺。

一般來說,在我的代碼里伪嫁,我的 struct 的命名也是遵照變量的命名規(guī)則领炫,唯有一點不一樣,就是 struct 的名稱我會將首字母大寫张咳,例如:SchoolName帝洪。

但是我的普通變量一般都是首字母小寫,例如:studentNumber脚猾。

這樣做只是個人習(xí)慣葱峡,便于在代碼里區(qū)分普通的變量和自定義的變量,之后會學(xué)到的 enum 和 union龙助,我也是習(xí)慣將其名稱的首字母大寫砰奕。

在 struct 的名字之后,我們需要寫上一對大括號提鸟,在這對大括號里面寫入你的 struct 要包含的各種類型的變量军援。

通常說來,struct 的大括號內(nèi)至少得定義兩個變量吧称勋。如果只有一個變量胸哥,那定義這個結(jié)構(gòu)體也沒什么意義。

注意:不要忘了赡鲜,在大括號后面還要加上一個分號( ; )空厌, 因為畢竟這個 struct 是一個變量,變量的定義最后都要加分號的蝗蛙。

如你所見蝇庭,創(chuàng)建一個自定義的變量也不復(fù)雜么。其實結(jié)構(gòu)體就是各種基本類型變量的集合捡硅,是一個“大雜燴”哮内。

當(dāng)然以后的課程中我們還會看到:結(jié)構(gòu)體的嵌套定義(結(jié)構(gòu)體里包含另一個結(jié)構(gòu)體)。

結(jié)構(gòu)體的例子

假設(shè)我們需要自定義一個結(jié)構(gòu)體壮韭,它儲存屏幕上的一個點的坐標北发。

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

image

當(dāng)我們在 2D 世界中做研究時喷屋,我們有兩個軸:

  • 橫坐標軸(從左到右琳拨,一般也稱為 x 軸)。
  • 縱坐標軸(從下到上屯曹,一般也稱為 y 軸)狱庇。

只要數(shù)學(xué)還沒有還給小學(xué)體育老師惊畏,應(yīng)該都知道 x 和 y 軸的。

現(xiàn)在密任,你可以寫出一個名叫 Coordinate(表示“坐標”)的 struct 的定義了嗎颜启?我看好你!

可以自己先寫浪讳,然后對一下我們給出的參考答案:

struct Coordinate
{
    int x;  // 橫坐標
    int y;  // 縱坐標
};
復(fù)制代碼

很簡單缰盏,不是嗎?我們的 Coordinate 這個 struct 包含了兩個變量:x 和 y淹遵,都是 int 類型口猜,分別表示橫坐標值和縱坐標值。

當(dāng)然了透揣,如果你愿意济炎,也可以創(chuàng)建一個表示 3D(三維)空間的點的 struct,只需要在剛才的 Coordinate 這個結(jié)構(gòu)體的基礎(chǔ)上加上 z 軸淌实。

結(jié)構(gòu)體里面的數(shù)組

結(jié)構(gòu)體里面也可以存放數(shù)組冻辩。

例如,可以構(gòu)造一個名叫 Person(表示“人”)的結(jié)構(gòu)體拆祈,如下所示:

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

    int age;  // 年齡
    int boy;  // 性別恨闪,布爾值 : 1 = boy(表示“男孩”), 0 = girl(表示“女孩”)
};
復(fù)制代碼

可以看到,這個結(jié)構(gòu)體變量包含五個基本的變量放坏。

  • 前三個分別表示 名咙咽、姓和地址,是字符數(shù)組淤年。

  • 第四個是年齡钧敞。

  • 第五個是性別,是一個“布爾值”(當(dāng)然麸粮,C語言本身沒有定義布爾類型(true 或 false)溉苛,但是可以用數(shù)值來“表示”布爾值的真或假),boy 這個 int 變量的值如果為 1弄诲,那就是表示男孩愚战;如果為 0,那就是女孩齐遵。

這個結(jié)構(gòu)體可以用于構(gòu)建一個通訊錄程序寂玲。當(dāng)然,你完全可以在這個結(jié)構(gòu)體里再添加其他變量梗摇,使其更完善拓哟。

只要內(nèi)存夠,一般來說在一個結(jié)構(gòu)體里沒有變量的數(shù)目限制伶授。

3. 結(jié)構(gòu)體的使用

現(xiàn)在断序,我們的結(jié)構(gòu)體已經(jīng)定義在 .h 頭文件里了流纹,那么我們就可以在 include(“包含”)此頭文件的文件中使用這些結(jié)構(gòu)體了。

以下展示如何創(chuàng)建一個類型為 Coordinate(我們之前已經(jīng)定義了這個結(jié)構(gòu)體违诗,表示二維空間的坐標)的變量:

#include "coordinate.h"  // 假設(shè)包含結(jié)構(gòu)體定義的頭文件叫 coordinate.h

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

    return 0;
}
復(fù)制代碼

如上,我們創(chuàng)建了一個 Coordinate 類型的變量较雕,名字是 point(表示“點”)。

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

你也許要問:“創(chuàng)建結(jié)構(gòu)體變量開頭的那個關(guān)鍵字 struct 是必須的嗎妆毕?”

是的慎玖,是必須的。

struct 關(guān)鍵字使電腦能夠區(qū)分基礎(chǔ)變量類型(例如 int)和自定義變量類型(例如 Coordinate)笛粘。

然而趁怔,每次加 struct 關(guān)鍵字也有點麻煩。所以聰(懶)明(惰)伶(成)俐(性)的 C語言開發(fā)者設(shè)計了 typedef 關(guān)鍵字薪前。

當(dāng)然了润努,人類的大多數(shù)發(fā)明都是為了“懶惰”的緣故,能提高效率誰不愿意笆纠ā铺浇?

typedef 關(guān)鍵字

typedef 是 C語言的一個關(guān)鍵字,是 type(表示“類型”)和 define(表示“定義”)的縮合垛膝,顧名思義是表示“類型定義”鳍侣。

聽到“類型定義”,好像很難理解吼拥。但其實 typedef 的作用并沒有它的含義那么“高深莫測”倚聚。

重新回到剛才定義 Coordinate 這個結(jié)構(gòu)體的 .h 頭文件中。我們來加一條由 typedef 開頭的命令凿可,目的是為 Coordinate 結(jié)構(gòu)體創(chuàng)建一個別名惑折。

什么是別名(alias)呢?

就比如有一個人矿酵,真實姓名叫王小明唬复,別名可以是小明,明明全肮,等敞咧,但都代表那個人。

有點類似 C++ 語言的引用的機制辜腺。

所以對別名的操作就是對原先對象的操作休建。

比如小時候你上課不乖乍恐,老師點名的時候,點到你的小名或者你的真實名字测砂,都是叫的你茵烈,你就得去罰站。

我們就在 Coordinate 結(jié)構(gòu)體的定義之前加這句命令吧砌些,一般習(xí)慣加在后面的呜投,但是加在前面也可以:

typedef struct Coordinate Coordinate;

struct Coordinate
{
    int x;
    int y;
};
復(fù)制代碼

可以看到,我們新加了一行命令:

typedef struct Coordinate Coordinate;
復(fù)制代碼

為了更好地理解這句命令的作用存璃,我們把它拆為三部分來看:

typedef
struct Coordinate
Coordinate

所以仑荐,上面這句命令的含義就是“從今以后, Coordinate 就相當(dāng)于 struct Coordinate 了 ”纵东。

這樣做以后粘招,我們就可以不用每次在創(chuàng)建一個新的 Coordinate 結(jié)構(gòu)體的變量時都加上 struct 關(guān)鍵字了。

所以偎球,我們的 .c 文件中就可以改寫為:

int main(int argc, char *argv[]) {
    Coordinate point;  // 因為用了 typedef洒扎,電腦就清楚地知道此處的 Coordinate 其實就是 struct Coordinate

    return 0;
}
復(fù)制代碼

當(dāng)然,別名不一定要叫 Coordinate衰絮,也可以叫作 Coor袍冷,也許更不容易混淆。例如:

typedef struct Coordinate Coor;

struct Coordinate
{
    int x;
    int y;
};

Coor coor;  // 創(chuàng)建一個結(jié)構(gòu)體變量
復(fù)制代碼

建議大家在平時定義了 struct 類型后猫牡,也加一句 typdedef 命令难裆,這樣在代碼里就不用每次新建一個此類型的變量時都要在開頭寫 struct 關(guān)鍵字了。

很多程序員都會這么做镊掖。 因為一個好的程序員是懂得如何“偷懶”的程序員乃戈,這和一個懶惰的程序員是有區(qū)別的。 我們要使代碼"write less亩进,do more"(用盡量少的代碼做更多的事)症虑。

當(dāng)然,上面的代碼塊可以簡寫為:

typedef struct struct的名字 {
  // struct 的內(nèi)容
} 別名;
復(fù)制代碼

所以上面 Coordinate 的代碼塊可以簡寫為:

typedef struct Coordinate
{
    int x;
    int y;
} Coordinate;
復(fù)制代碼

注意:之后我們的示例代碼归薛,有時會出現(xiàn)例如

Person player1;
復(fù)制代碼

這樣的形式谍憔,那就是假定我們之前已經(jīng)用了 typedef 了:

typedef struct Person Person;
復(fù)制代碼

這樣就可以省略開頭的 struct 關(guān)鍵字,不需要再寫成:

struct Person player1;
復(fù)制代碼

修改 struct 的成員變量

既然我們的 point 變量(是 Coordinate 類型的主籍,希望大家還沒暈)已經(jīng)創(chuàng)建好了习贫,那我們就可以修改它的成員的值了。

我們?nèi)绾卧L問 point 的兩個成員 x 和 y 呢千元?如下所示:

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

    point.x = 10;
    point.y = 20;

    return 0;
}
復(fù)制代碼

這樣苫昌,我們就順利地修改了 point 的兩個成員的值,使其 x 坐標為 10幸海,y 坐標為 20祟身。

因此我們的點就位于坐標系的(10, 20)處了告组。

所以桨醋,為了能訪問到結(jié)構(gòu)體的某個成員栋豫,我們可以這樣做:

結(jié)構(gòu)體實例名稱.成員名
復(fù)制代碼

中間的點( . )表示“從屬”關(guān)系晌端。

如果有面向?qū)ο缶幊袒A(chǔ)的朋友,就會覺得:這與“類和對象”也太像了吧婉陷。

是的帚称,其實我們可以用 struct 來“模擬”類。

如果我們用之前創(chuàng)建的 Person 這個結(jié)構(gòu)體來舉例的話:

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;
}
復(fù)制代碼

運行輸出:

你姓什么世杀?王
你名叫什么?小明
原來你的名字是 王小明肝集,失敬失敬
復(fù)制代碼

我們把 user.lastName 傳給 scanf,使得用戶輸入的值直接修改 user 的 lastName 成員蛛壳;我們對 user.firstName 也是如此杏瞻。

當(dāng)然我們也可以再添加對 address,age衙荐,boy 的賦值捞挥。

當(dāng)然了,你也許會說:“我不知道結(jié)構(gòu)體的使用忧吟,我用兩個單獨的字符串變量 lastName 和 firstName 不是也可以做到和上述程序相同的事么砌函?”

是的,但是用結(jié)構(gòu)體的好處就是我們可以創(chuàng)建此結(jié)構(gòu)體的變量溜族,將很多相關(guān)聯(lián)的數(shù)據(jù)封裝在一起讹俊,成為一個整體,而不是零散地定義煌抒。

比如定義了 Person 這個結(jié)構(gòu)體之后仍劈,凡是用 Person 來創(chuàng)建的變量,里面都自動包含了 lastName寡壮,firstName贩疙,address,age 和 boy 這五個變量况既,非常方便这溅。

比如我們可以這樣創(chuàng)建:

Person player1, player2;  // 之前已經(jīng)用 typedef( typedef struct Person Person; )
復(fù)制代碼

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

我們也可以更“偷懶”一些:創(chuàng)建結(jié)構(gòu)體數(shù)組莫其。例如:

Person players[2];
復(fù)制代碼

這樣对竣,我們就可以很方便的訪問 players[1] 當(dāng)中的變量了庇楞,例如:

players[1].lastName = "xiaoming";
復(fù)制代碼

用結(jié)構(gòu)體數(shù)組的好處是可以方便地使用循環(huán),等等否纬。

自測小練習(xí)

創(chuàng)建一個名叫 CoderHub(「程序員聯(lián)盟」公眾號)的結(jié)構(gòu)體吕晌,在定義里放入你想創(chuàng)建的變量。然后創(chuàng)建此結(jié)構(gòu)體的一個數(shù)組临燃,用循環(huán)的方式給變量賦值睛驳,再用循環(huán)的方式打印出其中變量的信息。

結(jié)構(gòu)體的初始化

之前的課程里膜廊,我們建議對于基本變量乏沸,數(shù)組和指針,最好在創(chuàng)建的時候?qū)ζ涑跏蓟稀=Y(jié)構(gòu)體也不例外蹬跃。

初始化有一個很大的好處,就是避免此變量里存放“任意數(shù)據(jù)”铆铆。

事實上蝶缀,一個變量在創(chuàng)建時,如果沒有初始化薄货,那么它會取當(dāng)時在內(nèi)存那個位置所存的值翁都,所以這個值的隨機性是很大的。

我們來回憶一下谅猾,不同變量的初始化應(yīng)該怎么做:

  • 基礎(chǔ)變量(int柄慰,double,char税娜,等):初始化為 0坐搔。

  • 指針:初始化為 NULL。事實上敬矩,NULL 位于 stdlib.h 標準庫頭文件中薯蝎,是用 #define 預(yù)處理命令定義的一個常量。它的值通常是 0谤绳。雖然是 0占锯,但是有多種定義形式,例如:

#define NULL 0
#define NULL 0L
#define NULL ((void *) 0)
復(fù)制代碼

但是我們只要每次用 NULL 就好了缩筛,為了清楚表明這是指針變量消略,而不是一般變量。

  • 數(shù)組:將每一個成員變量初始化為 0瞎抛。

那么對于我們的“朋友” 結(jié)構(gòu)體 艺演,我們怎么初始化呢?

其實結(jié)構(gòu)體的初始化也很簡單,與數(shù)組的初始化很類似胎撤。我們可以像下面這樣定義:

Coordinate point = {0, 0};
復(fù)制代碼

這樣晓殊,我們就依照順序?qū)?point.x 和 point.y 都初始化為 0 了。

對于像 Person 這樣的結(jié)構(gòu)體伤提,里面的變量類型有 char 型數(shù)組和 int巫俺,那么我們可以將 char 型數(shù)組初始化為 "" (雙引號中間為空)。

我們可以像這樣初始化一個字符串肿男,在 C語言探索之旅 | 第二部分第四課:字符串 那一課忘記提了介汹。不過,我想現(xiàn)在提還不算晚吧舶沛。

所以我們就可以這樣來初始化我們的 Person 結(jié)構(gòu)體變量:

Person player = {"", "", "", 0, 0};
復(fù)制代碼

然而嘹承,我們也可以這樣來初始化一個結(jié)構(gòu)體變量:創(chuàng)建一個函數(shù),比如叫 initializeStruct如庭,可以為每一個傳遞給它的結(jié)構(gòu)體做初始化叹卷,這樣就方便很多,特別是當(dāng)結(jié)構(gòu)體中的變量很多時坪它。

之前指針那一章我們也已經(jīng)學(xué)了骤竹,如果我們對函數(shù)傳遞普通變量,那么因為 C語言的函數(shù)參數(shù)傳遞方式是值傳遞哟楷,所以它會對傳給它的函數(shù)參數(shù)做一份拷貝,這樣函數(shù)里面修改的其實是那一份拷貝否灾,真正的實參并沒有被改變卖擅。

為了讓實參實實在在被修改,我們需要用到指針墨技,也就是傳遞此變量的地址惩阶。

對于結(jié)構(gòu)體,也需要這樣扣汪。因此断楷,接下來我們就來學(xué)習(xí)如何使用結(jié)構(gòu)體指針。開始難起來咯崭别,準備好了嗎冬筒?

4. 結(jié)構(gòu)體指針

結(jié)構(gòu)體指針的創(chuàng)建其實和普通的指針變量創(chuàng)建沒什么區(qū)別。例如:

Coordinate *point = NULL;
復(fù)制代碼

上面的代碼就創(chuàng)建了一個叫做 point 的 Coordinate 結(jié)構(gòu)體指針變量(Coordinate 是我們上面定義的表示坐標的一個結(jié)構(gòu)體)茅主。

我們再來提醒一次:

一般推薦寫成:

Coordinate *point = NULL; // 星號挨著指針變量名字
復(fù)制代碼

而不推薦寫成:

Coordinate* point = NULL;  // 星號挨著結(jié)構(gòu)體名舞痰,這種寫法不好!
復(fù)制代碼

在指針的創(chuàng)建中诀姚,我們推薦第一種寫法响牛。

因為用第二種寫法,如果你在一行上創(chuàng)建好幾個指針變量時,會容易忘記在第二個之后的變量前加 * 號呀打。例如矢赁,容易寫成這樣:

Coordinate* point1 = NULL, point2 = NULL;   // 編譯會出錯
復(fù)制代碼

但這樣編譯會出錯,因為 point2 其實是 Coordinate 結(jié)構(gòu)體變量贬丛,而不是 Coordinate 結(jié)構(gòu)體指針變量撩银!

所以我們建議這樣寫:

Coordinate *point1 = NULL, *point2 = NULL;
復(fù)制代碼

在以前的課程中,對于基礎(chǔ)類型的指針變量瘫寝,我們也是這樣建議:

int *number1 = NULL, *number2 = NULL;
復(fù)制代碼

特別是 int 型的指針蜒蕾,還很不容易察覺到錯誤,如果寫成:

int* number1 = NULL, number2 = NULL;
復(fù)制代碼

編譯器是不會報錯的焕阿。因為 NULL 的值就是 0咪啡,可以賦給 number2 這個 int 型變量(注意:上面的 number2 不是 int 指針)。

回顧總是很好的(“傷心總是難免的...”)暮屡。

結(jié)構(gòu)體作為函數(shù)參數(shù)

這里撤摸,我們主要來學(xué)習(xí)如何將一個結(jié)構(gòu)體指針(為什么是傳結(jié)構(gòu)體指針而不是傳結(jié)構(gòu)體,可以看之前的解釋)傳給一個函數(shù)(作為參數(shù))褒纲,使得函數(shù)內(nèi)部可以真正修改此結(jié)構(gòu)體准夷。

我們來看一個實例:

#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;
}

// 用于初始化結(jié)構(gòu)體變量
void initializeCoordinate(Coordinate *point) {
    // 結(jié)構(gòu)體初始化的代碼
}
復(fù)制代碼

上面的 initializeCoordinate 函數(shù)體內(nèi),我們將放置初始化結(jié)構(gòu)體的成員變量的代碼莺掠。

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

  • 首先衫嵌,我們定義了一個結(jié)構(gòu)體,叫做 Coordinate彻秆,里面包含兩個變量楔绞,x 和 y。

  • 我們在 main 函數(shù)中創(chuàng)建了Coordinate 結(jié)構(gòu)體的變量唇兑,名字叫 myPoint酒朵。

  • 我們將 myPoint 的地址傳遞給 initializeCoordinate 這個函數(shù)。

  • 接下來扎附,我們就在 initializeCoordinate 函數(shù)中添加初始化 x 和 y 變量的代碼吧:

void initializeCoordinate(Coordinate *point){
    *point.x = 0;
    *point.y = 0;
}
復(fù)制代碼

point 前面的 * 號是必不可少的噢蔫耽。因為,傳進函數(shù)的參數(shù)是一個結(jié)構(gòu)體指針留夜,我們要取到此結(jié)構(gòu)體匙铡,就需要用到“解引用”符號:星號( * )。

但是碍粥,認真的讀者看出上面這個函數(shù)中的錯誤了嗎慰枕?

我們的初衷是想要:先用 * 號解引用 point 這個結(jié)構(gòu)體指針,取到結(jié)構(gòu)體即纲,然后再用 . 號取到其中的變量 x 和 y具帮。但是如果按上面的寫法,其實效果相當(dāng)于如下:

*(point.x) = 0;
*(point.y) = 0;
復(fù)制代碼

因為 . 號的優(yōu)先級是高于 * 號的。

有興趣可以看一下 C語言運算符的優(yōu)先級蜂厅,不過之前的課我們也說過了匪凡,記不清怎么辦呢?加括號就解決啦掘猿。

上面的代碼編譯是通不過的病游,因為結(jié)構(gòu)體指針 point 并沒有成員叫 x 和 y,而且稠通,對于結(jié)構(gòu)體指針我們也不能用 . 號來取到什么值衬衬。

因此,我們需要修改一下改橘。改為如下就可以了:

void initializeCoordinate(Coordinate *point) {
    (*point).x = 0;
    (*point).y = 0;
}
復(fù)制代碼

這樣就對了滋尉。用括號去掉了運算符優(yōu)先級的影響。

但是飞主,之前也說過:程序員是懂得偷懶的一群人狮惜。

如果每次要取結(jié)構(gòu)體的成員變量都要這么麻煩,先用 * 號碌识,還要加括號碾篡,再用 . 號。想想都要讓 Denis Ritchie(C語言的作者)老爺子醉了筏餐。他是決不允許這種事發(fā)生的开泽,因此,他就定義了一個新的符號: -> (一個箭頭魁瞪。是的穆律,就是這么“霸氣側(cè)漏”)。

用法如下:

point->x = 0;
復(fù)制代碼

就相當(dāng)于:

(*point).x = 0;
復(fù)制代碼

是不是簡便了很多佩番?

記字谄臁:這個符號罢杉,只能用在指針上面趟畏。

因此,我們的函數(shù)可以改寫為:

void initializeCoordinate(Coordinate *point) {
    point->x = 0;
    point->y = 0;
}
復(fù)制代碼

我們在 main 函數(shù)里也可以這樣寫:

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

    myPoint.x = 10;  // 用結(jié)構(gòu)體的方式滩租,修改 myPoint 中的 x 值
    myPointPointer->y = 15;  // 用結(jié)構(gòu)體指針的方式赋秀,修改 myPoint 中的 y 值

    return 0;
}
復(fù)制代碼

結(jié)構(gòu)體是 C語言中一個非常好用且很重要的概念,希望大家好好掌握律想!

當(dāng)然猎莲,還有不少知識細節(jié),就要大家自己去看 C語言的經(jīng)典教材了技即,例如《C程序設(shè)計語言》(不是譚浩強那本《C語言程序設(shè)計》著洼!而是 C語言作者寫的經(jīng)典之作),《C和指針》,《C專家編程》身笤,《C語言深度解剖》豹悬,《C陷阱和缺陷》,等等液荸。

5. union

union 是“聯(lián)合”的意思瞻佛,是 C語言的關(guān)鍵字,也有的書上翻譯為“共用體”娇钱。

我們可以來寫一個 union 的例子伤柄。

union CoderHub
{
    char character;
    int memberNumber;
    double rate;
};
復(fù)制代碼

乍看之下,和 struct 沒什么區(qū)別么文搂。但是真的沒有區(qū)別嗎适刀?

假如我們用 C語言的 sizeof 關(guān)鍵字(size 表示“尺寸,大小”细疚,of 表示“...的”)來測試此 union 的大姓嵬(大小指的是在內(nèi)存中所占的字節(jié)(byte)數(shù),一個字節(jié)相當(dāng)于 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;
}
復(fù)制代碼

運行程序疯兼,輸出:

此 union 的大小是 8 個字節(jié)
復(fù)制代碼

假如我們對結(jié)構(gòu)體也做一次測試然遏,對比一下:

#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;
}
復(fù)制代碼

運行程序,輸出:

此 struct 的大小是 16 個字節(jié)
復(fù)制代碼

為什么我們自定義的 union 的大小是 8 個字節(jié)吧彪,而 struct 是 16 個字節(jié)呢待侵?

這就涉及到 union(共用體)和 struct(結(jié)構(gòu)體)的區(qū)別了。

struct 的大小是其中所有變量大小的總和姨裸。

但是你會說:“不對啊秧倾, 1 + 4 + 8 = 13,為什么 sizeof(coderHub) 的值為 16 呢傀缩?”

好問題那先!這個有點復(fù)雜,涉及到內(nèi)存對齊的問題赡艰,我們以后再說售淡。如果你一定要知道,那是因為內(nèi)存對齊使得第一個 char 變量對齊了第二個 int 變量的空間慷垮,也變成了 4揖闸,如此一來:4 + 4 + 8 = 16。

有興趣的讀者可以去參考《 C語言深度解剖 》的解釋料身。

在嵌入式編程等內(nèi)存有限的環(huán)境下汤纸,需要考慮內(nèi)存對齊,以節(jié)省空間芹血。

union 的大小等于其中最大( sizeof() 得到的值最大)的那個變量的大小贮泞。所以我們就知道了楞慈,其實 union 的儲存是這樣的:其中的每個變量在內(nèi)存中的起始地址是一樣的,所以 union 同一時刻只能存放其中一個變量啃擦,union 的大小等于其中最大的那個變量抖部,以保證可以容納任意一個成員。

union 適合用在很多相同類型的變量集议惰,但是某一時刻只需用到其中一個的情況慎颗,比較節(jié)省空間。

6. enum

看完了 struct(結(jié)構(gòu)體)和 union(聯(lián)合)言询,我們最后來學(xué)習(xí)很常用的一個自定義變量類型:enum俯萎。

enum 是 enumeration(表示“枚舉”)的縮寫,也是一個 C語言關(guān)鍵字运杭。

枚舉是一個比較特別的自定義變量類型夫啊。當(dāng)初我學(xué) C語言時,一開始還真有點不理解辆憔。但用得好撇眯,卻非常實用。

我們之前學(xué)了:結(jié)構(gòu)體里面包含了多個可以是不同類型的成員變量(一說“成員”就有點面向?qū)ο蟮母杏X :P)虱咧。

但是 enum(枚舉)里面是一系列可選擇的值熊榛。也就是說每次只能取其中一個值,聽著和 union 有點類似啊腕巡。但是 enum 和 union 還是有區(qū)別的玄坦。

我們來舉一個例子就知道區(qū)別了:

typedef enum Shape Shape;

enum Shape   // shape 表示“身材、體型”
{
    THIN,   // thin 表示“瘦”
    MEDIUM,   // medium 表示“中等”
    FAT   // fat 表示“胖”
};
復(fù)制代碼

所以绘沉,我們定義了一個名叫 Shape 的 enum 變量煎楣。其中有三個值,分別是 THIN车伞,MEDIUM 和 FAT(身材有瘦择懂,中等和胖之分)。不一定要大寫另玖,只是習(xí)慣困曙。

那我們怎么來創(chuàng)建 enum 變量呢?如下:

Shape shape = MEDIUM;
復(fù)制代碼

shape 這個變量日矫,我們在程序里也可以再將其修改為 THIN 或者 FAT赂弓。

將數(shù)值賦給 enum 的成員

大家看到 enum 和 union 以及 struct 的區(qū)別了嗎绑榴?是的哪轿,enum 的定義里,每個成員沒有變量類型(int翔怎,char窃诉,double杨耙,之類)!

很奇怪吧飘痛。想起來為什么 enum 的成員習(xí)慣用大寫了嗎珊膜?

對,就是因為 enum 的每個成員都不是變量宣脉,而是常量车柠!但是 enum 的機制和常量定義以及 #define 還是有些區(qū)別:

像上面的代碼:

typedef enum Shape {
    THIN,
    MEDIUM,
    FAT
} Shape;
復(fù)制代碼

編譯器會自動為其中的每一個成員綁定一個常量值,我們寫程序測試一下:

#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;
}
復(fù)制代碼

運行程序塑猖,輸出:

THIN = 0
MEDIUM = 1
FAT = 2
復(fù)制代碼

看到了嗎竹祷?編譯器自動給這三個成員賦值 0,1 和 2羊苟。如果沒有指定 enum 成員的值塑陵,那么它們的值是從 0 開始,依次加 1蜡励。

我們也可以自己來定義 enum 成員的值令花,不一定要每次讓編譯器給我們自動分配。

我們可以這樣寫:

typedef enum Shape {
    THIN = 40,
    MEDIUM = 60,
    FAT = 90
} Shape;
復(fù)制代碼

這樣凉倚,我們就自己給每個成員定義了值兼都。

我們也可以讓編譯器為我們自動分配幾個值,再自己定義幾個值稽寒,例如:

typedef enum Shape {
    THIN,
    MEDIUM,
    FAT = 90
} Shape;
復(fù)制代碼

上面俯抖,我們沒有為 THIN 和 MEDIUM 賦值,那么編譯器會將他們賦值為 0 和 1瓦胎。

而 FAT芬萍,因為我們已經(jīng)指定了其值為 90,所以 FAT 就等于 90搔啊。

enum 和 #define 的區(qū)別

是不是覺得 enum 和用 #define 來定義的常量是有些類似呢柬祠?

其實,還是有些不同的:

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

  • 一般在編譯器里,可以調(diào)試枚舉常量旧蛾,但是不能調(diào)試宏常量莽龟。

  • 枚舉可以一次定義大量相關(guān)的常量,而 #define 宏一次只能定義一個锨天。

其實做為一個學(xué)習(xí)者毯盈,有一個學(xué)習(xí)的氛圍跟一個交流圈子特別重要這里我推薦一個C/C++基礎(chǔ)交流583650410,不管你是小白還是轉(zhuǎn)行人士歡迎入駐病袄,大家一起交流成長搂赋。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赘阀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脑奠,更是在濱河造成了極大的恐慌基公,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宋欺,死亡現(xiàn)場離奇詭異轰豆,居然都是意外死亡,警方通過查閱死者的電腦和手機齿诞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門秒咨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掌挚,你說我怎么就攤上這事雨席。” “怎么了吠式?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵陡厘,是天一觀的道長。 經(jīng)常有香客問我特占,道長糙置,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任是目,我火速辦了婚禮谤饭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懊纳。我一直安慰自己揉抵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布嗤疯。 她就那樣靜靜地躺著冤今,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茂缚。 梳的紋絲不亂的頭發(fā)上戏罢,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音脚囊,去河邊找鬼龟糕。 笑死,一個胖子當(dāng)著我的面吹牛悔耘,可吹牛的內(nèi)容都是我干的讲岁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼催首!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泄鹏,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤郎任,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后备籽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舶治,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年车猬,在試婚紗的時候發(fā)現(xiàn)自己被綠了霉猛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡珠闰,死狀恐怖惜浅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伏嗜,我是刑警寧澤坛悉,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站承绸,受9級特大地震影響裸影,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜军熏,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一轩猩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荡澎,春花似錦均践、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至热鞍,卻和暖如春葫慎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背薇宠。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工偷办, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人澄港。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓椒涯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親回梧。 傳聞我的和親對象是個殘疾皇子废岂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353