c语言 如何创建adt_C语言探索之旅 | 第二部分第六课:创建你自己的变量类型
內(nèi)容簡(jiǎn)介
前言
定義一個(gè) struct
結(jié)構(gòu)體的使用
結(jié)構(gòu)體指針
union
enum
總結(jié)
第二部分第七課預(yù)告
1. 前言
上一課是 C語言探索之旅 | 第二部分第五課:預(yù)處理 ,應(yīng)該是比較輕松的。
這一課將會(huì)非常令人激動(dòng)也很有意思,不過有些難度。
眾所周知,C語言是面向過程的編程語言,與 Java,C++,等面向?qū)ο蟮木幊陶Z言有所不同。
在面向?qū)ο蟮木幊陶Z言中,有類(class)的概念。
C語言是沒有類這種“類型”的,但是 C語言就不能“模擬”面向?qū)ο缶幊塘藛?#xff1f;
不,只要你設(shè)計(jì)得好,C語言也可以模擬面向?qū)ο缶幊獭?/p>
這一課我們要學(xué)習(xí)的 struct(結(jié)構(gòu)體)的知識(shí)就可以使你有能力用 C語言實(shí)現(xiàn)“面向?qū)ο蟆薄?/p>
前面我們學(xué)習(xí)了指針,數(shù)組,字符串和預(yù)處理,掌握這些知識(shí)你的 C語言水平已經(jīng)還不錯(cuò)啦。但是我們豈能就此止步,必須 Bigger than bigger~
除了使用 C語言已經(jīng)定義的變量類型,我們還可以做一些更厲害的事情:創(chuàng)建你自己的變量類型。
我們可以將其稱為“自定義的變量類型”,我們來看三種:struct,union 和 enum。
因?yàn)楫?dāng)你需要編寫比較復(fù)雜的程序時(shí),你會(huì)發(fā)現(xiàn)創(chuàng)建自定義的變量類型是很重要的。
幸好,這學(xué)起來其實(shí)也不是特別難。但是大家需要專心學(xué)習(xí)這一課,因?yàn)閺南乱徽n開始,我們會(huì)一直用到 struct 了。
2. 定義一個(gè) struct
什么是 struct 呢?
struct 是 structure(表示“結(jié)構(gòu)”)的縮寫,所以 struct 的專業(yè)術(shù)語是“結(jié)構(gòu)體”。
定義:struct 就是一系列變量的集合,但是這些變量可以是不同類型的。
這個(gè)定義是不是喚起了大家對(duì)我們的老朋友數(shù)組的懷念啊?數(shù)組里面的每個(gè)成員都必須是同一個(gè)類型的,相比之下 struct 更靈活。
一般來說,我們習(xí)慣把 struct 定義在 .h 頭文件中,也就是和預(yù)處理命令以及函數(shù)原型那群“家伙”在一起。
下面就給出一個(gè) struct 的例子:
struct?你的struct的名字{
????char?variable1;
????short?variable2;
????int?otherVariable;
????double?numberDecimal;
};
可以看到:struct 的定義以關(guān)鍵字 struct 開始,后面接你自定義的 struct 的名稱(比如 Dog,Cat,Person,等)。
一般來說,在我的代碼里,我的 struct 的命名也是遵照變量的命名規(guī)則,唯有一點(diǎn)不一樣,就是 struct 的名稱我會(huì)將首字母大寫,例如:SchoolName。
但是我的普通變量一般都是首字母小寫,例如:studentNumber。
這樣做只是個(gè)人習(xí)慣,便于在代碼里區(qū)分普通的變量和自定義的變量,之后會(huì)學(xué)到的 enum 和 union,我也是習(xí)慣將其名稱的首字母大寫。
在 struct 的名字之后,我們需要寫上一對(duì)大括號(hào),在這對(duì)大括號(hào)里面寫入你的 struct 要包含的各種類型的變量。
通常說來,struct 的大括號(hào)內(nèi)至少得定義兩個(gè)變量吧。如果只有一個(gè)變量,那定義這個(gè)結(jié)構(gòu)體也沒什么意義。
注意:不要忘了,在大括號(hào)后面還要加上一個(gè)分號(hào)(;),
因?yàn)楫吘惯@個(gè) struct 是一個(gè)變量,變量的定義最后都要加分號(hào)的。
如你所見,創(chuàng)建一個(gè)自定義的變量也不復(fù)雜么。其實(shí)結(jié)構(gòu)體就是各種基本類型變量的集合,是一個(gè)“大雜燴”。
當(dāng)然以后的課程中我們還會(huì)看到:結(jié)構(gòu)體的嵌套定義(結(jié)構(gòu)體里包含另一個(gè)結(jié)構(gòu)體)。
結(jié)構(gòu)體的例子
假設(shè)我們需要自定義一個(gè)結(jié)構(gòu)體,它儲(chǔ)存屏幕上的一個(gè)點(diǎn)的坐標(biāo)。
下面就給出 2D(D 是英語 dimension 的首字母,表示維度)世界的坐標(biāo)系的大致印象:
當(dāng)我們?cè)?2D 世界中做研究時(shí),我們有兩個(gè)軸:
橫坐標(biāo)軸(從左到右,一般也稱為 x 軸)。
縱坐標(biāo)軸(從下到上,一般也稱為 y 軸)。
只要數(shù)學(xué)還沒有還給小學(xué)體育老師,應(yīng)該都知道 x 和 y 軸的。
現(xiàn)在,你可以寫出一個(gè)名叫 Coordinate(表示“坐標(biāo)”)的 struct 的定義了嗎?我看好你!
可以自己先寫,然后對(duì)一下我們給出的參考答案:
struct?Coordinate{
????int?x;??//?橫坐標(biāo)
????int?y;??//?縱坐標(biāo)
};
很簡(jiǎn)單,不是嗎?我們的 Coordinate 這個(gè) struct 包含了兩個(gè)變量:x 和 y,都是 int 類型,分別表示橫坐標(biāo)值和縱坐標(biāo)值。
當(dāng)然了,如果你愿意,也可以創(chuàng)建一個(gè)表示 3D(三維)空間的點(diǎn)的 struct,只需要在剛才的 Coordinate 這個(gè)結(jié)構(gòu)體的基礎(chǔ)上加上 z 軸。
結(jié)構(gòu)體里面的數(shù)組
結(jié)構(gòu)體里面也可以存放數(shù)組。
例如,可以構(gòu)造一個(gè)名叫 Person(表示“人”)的結(jié)構(gòu)體,如下所示:
struct?Person{
????char?firstName[100];??//?名
????char?lastName[100];???//?姓
????char?address[1000];???//?地址
????int?age;??//?年齡
????int?boy;??//?性別,布爾值?:?1?=?boy(表示“男孩”),?0?=?girl(表示“女孩”)
};
可以看到,這個(gè)結(jié)構(gòu)體變量包含五個(gè)基本的變量。
前三個(gè)分別表示 名、姓和地址,是字符數(shù)組。
第四個(gè)是年齡。
第五個(gè)是性別,是一個(gè)“布爾值”(當(dāng)然,C語言本身沒有定義布爾類型(true 或 false),但是可以用數(shù)值來“表示”布爾值的真或假),boy 這個(gè) int 變量的值如果為 1,那就是表示男孩;如果為 0,那就是女孩。
這個(gè)結(jié)構(gòu)體可以用于構(gòu)建一個(gè)通訊錄程序。當(dāng)然,你完全可以在這個(gè)結(jié)構(gòu)體里再添加其他變量,使其更完善。
只要內(nèi)存夠,一般來說在一個(gè)結(jié)構(gòu)體里沒有變量的數(shù)目限制。
3. 結(jié)構(gòu)體的使用
現(xiàn)在,我們的結(jié)構(gòu)體已經(jīng)定義在 .h 頭文件里了,那么我們就可以在 include(“包含”)此頭文件的文件中使用這些結(jié)構(gòu)體了。
以下展示如何創(chuàng)建一個(gè)類型為 Coordinate(我們之前已經(jīng)定義了這個(gè)結(jié)構(gòu)體,表示二維空間的坐標(biāo))的變量:
#include?"coordinate.h"??//?假設(shè)包含結(jié)構(gòu)體定義的頭文件叫?coordinate.hint?main(int?argc,?char?*argv[]){
????struct?Coordinate?point;??//?創(chuàng)建一個(gè)?Coordinate?類型的變量,名字是?point
????return?0;
}
如上,我們創(chuàng)建了一個(gè) Coordinate 類型的變量,名字是 point(表示“點(diǎn)”)。
這個(gè)變量自動(dòng)擁有兩個(gè)子變量:x 和 y,都是 int 類型,分別表示此二維坐標(biāo)的橫坐標(biāo)值和縱坐標(biāo)值。
你也許要問:“創(chuàng)建結(jié)構(gòu)體變量開頭的那個(gè)關(guān)鍵字 struct 是必須的嗎?”
是的,是必須的。
struct 關(guān)鍵字使電腦能夠區(qū)分基礎(chǔ)變量類型(例如 int)和自定義變量類型(例如 Coordinate)。
然而,每次加 struct 關(guān)鍵字也有點(diǎn)麻煩。所以聰(懶)明(惰)伶(成)俐(性)的 C語言開發(fā)者設(shè)計(jì)了 typedef 關(guān)鍵字。
當(dāng)然了,人類的大多數(shù)發(fā)明都是為了“懶惰”的緣故,能提高效率誰不愿意啊?
typedef 關(guān)鍵字
typedef 是 C語言的一個(gè)關(guān)鍵字,是 type(表示“類型”)和 define(表示“定義”)的縮合,顧名思義是表示“類型定義”。
聽到“類型定義”,好像很難理解。但其實(shí) typedef 的作用并沒有它的含義那么“高深莫測(cè)”。
重新回到剛才定義 Coordinate 這個(gè)結(jié)構(gòu)體的 .h 頭文件中。我們來加一條由 typedef 開頭的命令,目的是為 Coordinate 結(jié)構(gòu)體創(chuàng)建一個(gè)別名。
什么是別名(alias)呢?
就比如有一個(gè)人,真實(shí)姓名叫王小明,別名可以是小明,明明,等,但都代表那個(gè)人。
有點(diǎn)類似 C++ 語言的引用的機(jī)制。
所以對(duì)別名的操作就是對(duì)原先對(duì)象的操作。
比如小時(shí)候你上課不乖,老師點(diǎn)名的時(shí)候,點(diǎn)到你的小名或者你的真實(shí)名字,都是叫的你,你就得去罰站。
我們就在 Coordinate 結(jié)構(gòu)體的定義之前加這句命令吧,一般習(xí)慣加在后面的,但是加在前面也可以:
typedef?struct?Coordinate?Coordinate;struct?Coordinate
{
????int?x;
????int?y;
};
可以看到,我們新加了一行命令:
typedef?struct?Coordinate?Coordinate;為了更好地理解這句命令的作用,我們把它拆為三部分來看:
typedef:說明我們將要?jiǎng)?chuàng)建一個(gè)別名。
struct Coordinate:這是我們要為其創(chuàng)建別名的結(jié)構(gòu)體。
Coordinate:這就是要?jiǎng)?chuàng)建的別名。
所以,上面這句命令的含義就是“從今以后,Coordinate 就相當(dāng)于 struct Coordinate 了 ”。
這樣做以后,我們就可以不用每次在創(chuàng)建一個(gè)新的 Coordinate 結(jié)構(gòu)體的變量時(shí)都加上 struct 關(guān)鍵字了。
所以,我們的 .c 文件中就可以改寫為:
int?main(int?argc,?char?*argv[]){????Coordinate?point;??//?因?yàn)橛昧?typedef,電腦就清楚地知道此處的?Coordinate?其實(shí)就是?struct?Coordinate
????return?0;
}
當(dāng)然,別名不一定要叫 Coordinate,也可以叫作 Coor,也許更不容易混淆。例如:
typedef?struct?Coordinate?Coor;struct?Coordinate
{
????int?x;
????int?y;
};
Coor?coor;??//?創(chuàng)建一個(gè)結(jié)構(gòu)體變量
建議大家在平時(shí)定義了 struct 類型后,也加一句 typdedef 命令,這樣在代碼里就不用每次新建一個(gè)此類型的變量時(shí)都要在開頭寫 struct 關(guān)鍵字了。
很多程序員都會(huì)這么做。
因?yàn)橐粋€(gè)好的程序員是懂得如何“偷懶”的程序員,這和一個(gè)懶惰的程序員是有區(qū)別的。
我們要使代碼"write less,do more"(用盡量少的代碼做更多的事)。
當(dāng)然,上面的代碼塊可以簡(jiǎn)寫為:
typedef?struct?struct的名字{
??//?struct?的內(nèi)容
}?別名;
所以上面 Coordinate 的代碼塊可以簡(jiǎn)寫為:
typedef?struct?Coordinate{
????int?x;
????int?y;
}?Coordinate;
注意:之后我們的示例代碼,有時(shí)會(huì)出現(xiàn)例如
Person?player1;這樣的形式,那就是假定我們之前已經(jīng)用了 typedef 了:
typedef?struct?Person?Person;這樣就可以省略開頭的 struct 關(guān)鍵字,不需要再寫成:
struct?Person?player1;修改 struct 的成員變量
既然我們的 point 變量(是 Coordinate 類型的,希望大家還沒暈)已經(jīng)創(chuàng)建好了,那我們就可以修改它的成員的值了。
我們?nèi)绾卧L問 point 的兩個(gè)成員 x 和 y 呢?如下所示:
int?main(int?argc,?char?*argv[]){????Coordinate?point;
????point.x?=?10;
????point.y?=?20;
????return?0;
}
這樣,我們就順利地修改了 point 的兩個(gè)成員的值,使其 x 坐標(biāo)為 10,y 坐標(biāo)為 20。
因此我們的點(diǎn)就位于坐標(biāo)系的(10, 20)處了。
所以,為了能訪問到結(jié)構(gòu)體的某個(gè)成員,我們可以這樣做:
結(jié)構(gòu)體實(shí)例名稱.成員名中間的點(diǎn)(.)表示“從屬”關(guān)系。
如果有面向?qū)ο缶幊袒A(chǔ)的朋友,就會(huì)覺得:這與“類和對(duì)象”也太像了吧。
是的,其實(shí)我們可以用 struct 來“模擬”類。
如果我們用之前創(chuàng)建的 Person 這個(gè)結(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;
}
運(yùn)行輸出:
你姓什么?王你名叫什么?小明
原來你的名字是?王小明,失敬失敬
我們把 user.lastName 傳給 scanf,使得用戶輸入的值直接修改 user 的 lastName 成員;我們對(duì) user.firstName 也是如此。
當(dāng)然我們也可以再添加對(duì) address,age,boy 的賦值。
當(dāng)然了,你也許會(huì)說:“我不知道結(jié)構(gòu)體的使用,我用兩個(gè)單獨(dú)的字符串變量 lastName 和 firstName 不是也可以做到和上述程序相同的事么?”
是的,但是用結(jié)構(gòu)體的好處就是我們可以創(chuàng)建此結(jié)構(gòu)體的變量,將很多相關(guān)聯(lián)的數(shù)據(jù)封裝在一起,成為一個(gè)整體,而不是零散地定義。
比如定義了 Person 這個(gè)結(jié)構(gòu)體之后,凡是用 Person 來創(chuàng)建的變量,里面都自動(dòng)包含了 lastName,firstName,address,age 和 boy 這五個(gè)變量,非常方便。
比如我們可以這樣創(chuàng)建:
Person?player1,?player2;??//?之前已經(jīng)用?typedef(?typedef?struct?Person?Person;?)在 player1 和 player2 中都包含 lastName,firstName,address,age 和 boy 這五個(gè)變量。
我們也可以更“偷懶”一些:創(chuàng)建結(jié)構(gòu)體數(shù)組。例如:
Person?players[2];這樣,我們就可以很方便的訪問 players[1] 當(dāng)中的變量了,例如:
players[1].lastName?=?"xiaoming";用結(jié)構(gòu)體數(shù)組的好處是可以方便地使用循環(huán),等等。
自測(cè)小練習(xí)
創(chuàng)建一個(gè)名叫 CoderHub(「程序員聯(lián)盟」公眾號(hào))的結(jié)構(gòu)體,在定義里放入你想創(chuàng)建的變量。然后創(chuàng)建此結(jié)構(gòu)體的一個(gè)數(shù)組,用循環(huán)的方式給變量賦值,再用循環(huán)的方式打印出其中變量的信息。
結(jié)構(gòu)體的初始化
之前的課程里,我們建議對(duì)于基本變量,數(shù)組和指針,最好在創(chuàng)建的時(shí)候?qū)ζ涑跏蓟?。結(jié)構(gòu)體也不例外。
初始化有一個(gè)很大的好處,就是避免此變量里存放“任意數(shù)據(jù)”。
事實(shí)上,一個(gè)變量在創(chuàng)建時(shí),如果沒有初始化,那么它會(huì)取當(dāng)時(shí)在內(nèi)存那個(gè)位置所存的值,所以這個(gè)值的隨機(jī)性是很大的。
我們來回憶一下,不同變量的初始化應(yīng)該怎么做:
基礎(chǔ)變量(int,double,char,等):初始化為 0。
指針:初始化為 NULL。事實(shí)上,NULL 位于 stdlib.h 標(biāo)準(zhǔn)庫頭文件中,是用 #define 預(yù)處理命令定義的一個(gè)常量。它的值通常是 0。雖然是 0,但是有多種定義形式,例如:
#define?NULL?0L
#define?NULL?((void?*)?0)
但是我們只要每次用 NULL 就好了,為了清楚表明這是指針變量,而不是一般變量。
數(shù)組:將每一個(gè)成員變量初始化為 0。
那么對(duì)于我們的“朋友” 結(jié)構(gòu)體,我們?cè)趺闯跏蓟?#xff1f;
其實(shí)結(jié)構(gòu)體的初始化也很簡(jiǎn)單,與數(shù)組的初始化很類似。我們可以像下面這樣定義:
Coordinate?point?=?{0,?0};這樣,我們就依照順序?qū)?point.x 和 point.y 都初始化為 0 了。
對(duì)于像 Person 這樣的結(jié)構(gòu)體,里面的變量類型有 char 型數(shù)組和 int,那么我們可以將 char 型數(shù)組初始化為 ""(雙引號(hào)中間為空)。
我們可以像這樣初始化一個(gè)字符串,在 C語言探索之旅 | 第二部分第四課:字符串 那一課忘記提了。不過,我想現(xiàn)在提還不算晚吧。
所以我們就可以這樣來初始化我們的 Person 結(jié)構(gòu)體變量:
Person?player?=?{"",?"",?"",?0,?0};然而,我們也可以這樣來初始化一個(gè)結(jié)構(gòu)體變量:創(chuàng)建一個(gè)函數(shù),比如叫 initializeStruct,可以為每一個(gè)傳遞給它的結(jié)構(gòu)體做初始化,這樣就方便很多,特別是當(dāng)結(jié)構(gòu)體中的變量很多時(shí)。
之前指針那一章我們也已經(jīng)學(xué)了,如果我們對(duì)函數(shù)傳遞普通變量,那么因?yàn)?C語言的函數(shù)參數(shù)傳遞方式是值傳遞,所以它會(huì)對(duì)傳給它的函數(shù)參數(shù)做一份拷貝,這樣函數(shù)里面修改的其實(shí)是那一份拷貝,真正的實(shí)參并沒有被改變。
為了讓實(shí)參實(shí)實(shí)在在被修改,我們需要用到指針,也就是傳遞此變量的地址。
對(duì)于結(jié)構(gòu)體,也需要這樣。因此,接下來我們就來學(xué)習(xí)如何使用結(jié)構(gòu)體指針。開始難起來咯,準(zhǔn)備好了嗎?
4. 結(jié)構(gòu)體指針
結(jié)構(gòu)體指針的創(chuàng)建其實(shí)和普通的指針變量創(chuàng)建沒什么區(qū)別。例如:
Coordinate?*point?=?NULL;上面的代碼就創(chuàng)建了一個(gè)叫做 point 的 Coordinate 結(jié)構(gòu)體指針變量(Coordinate 是我們上面定義的表示坐標(biāo)的一個(gè)結(jié)構(gòu)體)。
我們?cè)賮硖嵝岩淮?#xff1a;
一般推薦寫成:
Coordinate?*point?=?NULL;?//?星號(hào)挨著指針變量名字而不推薦寫成:
Coordinate*?point?=?NULL;??//?星號(hào)挨著結(jié)構(gòu)體名,這種寫法不好!在指針的創(chuàng)建中,我們推薦第一種寫法。
因?yàn)橛玫诙N寫法,如果你在一行上創(chuàng)建好幾個(gè)指針變量時(shí),會(huì)容易忘記在第二個(gè)之后的變量前加 * 號(hào)。例如,容易寫成這樣:
Coordinate*?point1?=?NULL,?point2?=?NULL;???//?編譯會(huì)出錯(cuò)但這樣編譯會(huì)出錯(cuò),因?yàn)?point2 其實(shí)是 Coordinate 結(jié)構(gòu)體變量,而不是 Coordinate 結(jié)構(gòu)體指針變量!
所以我們建議這樣寫:
Coordinate?*point1?=?NULL,?*point2?=?NULL;在以前的課程中,對(duì)于基礎(chǔ)類型的指針變量,我們也是這樣建議:
int?*number1?=?NULL,?*number2?=?NULL;特別是 int 型的指針,還很不容易察覺到錯(cuò)誤,如果寫成:
int*?number1?=?NULL,?number2?=?NULL;編譯器是不會(huì)報(bào)錯(cuò)的。因?yàn)?NULL 的值就是 0,可以賦給 number2 這個(gè) int 型變量(注意:上面的 number2 不是 int 指針)。
回顧總是很好的(“傷心總是難免的…”)。
結(jié)構(gòu)體作為函數(shù)參數(shù)
這里,我們主要來學(xué)習(xí)如何將一個(gè)結(jié)構(gòu)體指針(為什么是傳結(jié)構(gòu)體指針而不是傳結(jié)構(gòu)體,可以看之前的解釋)傳給一個(gè)函數(shù)(作為參數(shù)),使得函數(shù)內(nèi)部可以真正修改此結(jié)構(gòu)體。
我們來看一個(gè)實(shí)例:
#include?typedef?struct?Coordinate
{
????int?x;??//?橫坐標(biāo)值
????int?y;??//?縱坐標(biāo)值
}?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)體初始化的代碼
}
上面的 initializeCoordinate 函數(shù)體內(nèi),我們將放置初始化結(jié)構(gòu)體的成員變量的代碼。
我們按順序來看一下這段代碼:
首先,我們定義了一個(gè)結(jié)構(gòu)體,叫做 Coordinate,里面包含兩個(gè)變量,x 和 y。
我們?cè)?main 函數(shù)中創(chuàng)建了Coordinate 結(jié)構(gòu)體的變量,名字叫 myPoint。
我們將 myPoint 的地址傳遞給 initializeCoordinate 這個(gè)函數(shù)。
接下來,我們就在 initializeCoordinate 函數(shù)中添加初始化 x 和 y 變量的代碼吧:
????*point.x?=?0;
????*point.y?=?0;
}
point 前面的 * 號(hào)是必不可少的噢。因?yàn)?#xff0c;傳進(jìn)函數(shù)的參數(shù)是一個(gè)結(jié)構(gòu)體指針,我們要取到此結(jié)構(gòu)體,就需要用到“解引用”符號(hào):星號(hào)(*)。
但是,認(rèn)真的讀者看出上面這個(gè)函數(shù)中的錯(cuò)誤了嗎?
我們的初衷是想要:先用 * 號(hào)解引用 point 這個(gè)結(jié)構(gòu)體指針,取到結(jié)構(gòu)體,然后再用 . 號(hào)取到其中的變量 x 和 y。但是如果按上面的寫法,其實(shí)效果相當(dāng)于如下:
*(point.x)?=?0;*(point.y)?=?0;
因?yàn)?. 號(hào)的優(yōu)先級(jí)是高于 * 號(hào)的。
有興趣可以看一下 C語言運(yùn)算符的優(yōu)先級(jí),不過之前的課我們也說過了,記不清怎么辦呢?加括號(hào)就解決啦。
上面的代碼編譯是通不過的,因?yàn)榻Y(jié)構(gòu)體指針 point 并沒有成員叫 x 和 y,而且,對(duì)于結(jié)構(gòu)體指針我們也不能用 . 號(hào)來取到什么值。
因此,我們需要修改一下。改為如下就可以了:
void?initializeCoordinate(Coordinate?*point)?{????(*point).x?=?0;
????(*point).y?=?0;
}
這樣就對(duì)了。用括號(hào)去掉了運(yùn)算符優(yōu)先級(jí)的影響。
但是,之前也說過:程序員是懂得偷懶的一群人。
如果每次要取結(jié)構(gòu)體的成員變量都要這么麻煩,先用 * 號(hào),還要加括號(hào),再用 . 號(hào)。想想都要讓 Denis Ritchie(C語言的作者)老爺子醉了。他是決不允許這種事發(fā)生的,因此,他就定義了一個(gè)新的符號(hào): ->(一個(gè)箭頭。是的,就是這么“霸氣側(cè)漏”)。
用法如下:
point->x?=?0;就相當(dāng)于:
(*point).x?=?0;是不是簡(jiǎn)便了很多?
記住:這個(gè)符號(hào),只能用在指針上面。
因此,我們的函數(shù)可以改寫為:
void?initializeCoordinate(Coordinate?*point)?{????point->x?=?0;
????point->y?=?0;
}
我們?cè)?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;
}
結(jié)構(gòu)體是 C語言中一個(gè)非常好用且很重要的概念,希望大家好好掌握!
當(dāng)然,還有不少知識(shí)細(xì)節(jié),就要大家自己去看 C語言的經(jīng)典教材了,例如《C程序設(shè)計(jì)語言》(不是譚浩強(qiáng)那本《C語言程序設(shè)計(jì)》!而是 C語言作者寫的經(jīng)典之作),《C和指針》,《C專家編程》,《C語言深度解剖》,《C陷阱和缺陷》,等等。
5. union
union 是“聯(lián)合”的意思,是 C語言的關(guān)鍵字,也有的書上翻譯為“共用體”。
我們可以來寫一個(gè) union 的例子。
union?CoderHub{
????char?character;
????int?memberNumber;
????double?rate;
};
乍看之下,和 struct 沒什么區(qū)別么。但是真的沒有區(qū)別嗎?
假如我們用 C語言的 sizeof 關(guān)鍵字(size 表示“尺寸,大小”,of 表示“…的”)來測(cè)試此 union 的大小(大小指的是在內(nèi)存中所占的字節(jié)(byte)數(shù),一個(gè)字節(jié)相當(dāng)于 8 個(gè) bit(二進(jìn)制位)):
#include?typedef?union?CoderHub
{
????char?character;??//?大小是?1?個(gè)字節(jié)
????int?memberNumber;??//?大小是?4?個(gè)字節(jié)
????double?rate;??//?大小是?8?個(gè)字節(jié)
}?CoderHub;
int?main(int?argc,?char?*argv[]){
????CoderHub?coderHub;
????printf("此?union?的大小是?%lu?個(gè)字節(jié)\n",?sizeof(coderHub));
????return?0;
}
運(yùn)行程序,輸出:
此?union?的大小是?8?個(gè)字節(jié)假如我們對(duì)結(jié)構(gòu)體也做一次測(cè)試,對(duì)比一下:
#include?typedef?struct?CoderHub
{
????char?character;??//?大小是?1?個(gè)字節(jié)
????int?memberNumber;??//?大小是?4?個(gè)字節(jié)
????double?rate;??//?大小是?8?個(gè)字節(jié)
}?CoderHub;
int?main(int?argc,?char?*argv[]){
????CoderHub?coderHub;
????printf("此?struct?的大小是?%lu?個(gè)字節(jié)\n",?sizeof(coderHub));
????return?0;
}
運(yùn)行程序,輸出:
此?struct?的大小是?16?個(gè)字節(jié)為什么我們自定義的 union 的大小是 8 個(gè)字節(jié),而 struct 是 16 個(gè)字節(jié)呢?
這就涉及到 union(共用體)和 struct(結(jié)構(gòu)體)的區(qū)別了。
struct 的大小是其中所有變量大小的總和。
但是你會(huì)說:“不對(duì)啊, 1 + 4 + 8 = 13,為什么 sizeof(coderHub) 的值為 16 呢?”
好問題!這個(gè)有點(diǎn)復(fù)雜,涉及到內(nèi)存對(duì)齊的問題,我們以后再說。如果你一定要知道,那是因?yàn)閮?nèi)存對(duì)齊使得第一個(gè) char 變量對(duì)齊了第二個(gè) int 變量的空間,也變成了 4,如此一來:4 + 4 + 8 = 16。
有興趣的讀者可以去參考《C語言深度解剖》的解釋。
在嵌入式編程等內(nèi)存有限的環(huán)境下,需要考慮內(nèi)存對(duì)齊,以節(jié)省空間。
union 的大小等于其中最大(sizeof() 得到的值最大)的那個(gè)變量的大小。所以我們就知道了,其實(shí) union 的儲(chǔ)存是這樣的:其中的每個(gè)變量在內(nèi)存中的起始地址是一樣的,所以 union 同一時(shí)刻只能存放其中一個(gè)變量,union 的大小等于其中最大的那個(gè)變量,以保證可以容納任意一個(gè)成員。
union 適合用在很多相同類型的變量集,但是某一時(shí)刻只需用到其中一個(gè)的情況,比較節(jié)省空間。
6. enum
看完了 struct(結(jié)構(gòu)體)和 union(聯(lián)合),我們最后來學(xué)習(xí)很常用的一個(gè)自定義變量類型:enum。
enum 是 enumeration(表示“枚舉”)的縮寫,也是一個(gè) C語言關(guān)鍵字。
枚舉是一個(gè)比較特別的自定義變量類型。當(dāng)初我學(xué) C語言時(shí),一開始還真有點(diǎn)不理解。但用得好,卻非常實(shí)用。
我們之前學(xué)了:結(jié)構(gòu)體里面包含了多個(gè)可以是不同類型的成員變量(一說“成員”就有點(diǎn)面向?qū)ο蟮母杏X :P)。
但是 enum(枚舉)里面是一系列可選擇的值。也就是說每次只能取其中一個(gè)值,聽著和 union 有點(diǎn)類似啊。但是 enum 和 union 還是有區(qū)別的。
我們來舉一個(gè)例子就知道區(qū)別了:
typedef?enum?Shape?Shape;enum?Shape???//?shape?表示“身材、體型”
{
????THIN,???//?thin?表示“瘦”
????MEDIUM,???//?medium?表示“中等”
????FAT???//?fat?表示“胖”
};
所以,我們定義了一個(gè)名叫 Shape 的 enum 變量。其中有三個(gè)值,分別是 THIN,MEDIUM 和 FAT(身材有瘦,中等和胖之分)。不一定要大寫,只是習(xí)慣。
那我們?cè)趺磥韯?chuàng)建 enum 變量呢?如下:
Shape?shape?=?MEDIUM;shape 這個(gè)變量,我們?cè)诔绦蚶镆部梢栽賹⑵湫薷臑?THIN 或者 FAT。
將數(shù)值賦給 enum 的成員
大家看到 enum 和 union 以及 struct 的區(qū)別了嗎?是的,enum 的定義里,每個(gè)成員沒有變量類型(int,char,double,之類)!
很奇怪吧。想起來為什么 enum 的成員習(xí)慣用大寫了嗎?
對(duì),就是因?yàn)?enum 的每個(gè)成員都不是變量,而是常量!但是 enum 的機(jī)制和常量定義以及 #define 還是有些區(qū)別:
像上面的代碼:
typedef?enum?Shape{
????THIN,
????MEDIUM,
????FAT
}?Shape;
編譯器會(huì)自動(dòng)為其中的每一個(gè)成員綁定一個(gè)常量值,我們寫程序測(cè)試一下:
#include?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;
}
運(yùn)行程序,輸出:
THIN?=?0MEDIUM?=?1
FAT?=?2
看到了嗎?編譯器自動(dòng)給這三個(gè)成員賦值 0,1 和 2。如果沒有指定 enum 成員的值,那么它們的值是從 0 開始,依次加 1。
我們也可以自己來定義 enum 成員的值,不一定要每次讓編譯器給我們自動(dòng)分配。
我們可以這樣寫:
typedef?enum?Shape{
????THIN?=?40,
????MEDIUM?=?60,
????FAT?=?90
}?Shape;
這樣,我們就自己給每個(gè)成員定義了值。
我們也可以讓編譯器為我們自動(dòng)分配幾個(gè)值,再自己定義幾個(gè)值,例如:
typedef?enum?Shape{
????THIN,
????MEDIUM,
????FAT?=?90
}?Shape;
上面,我們沒有為 THIN 和 MEDIUM 賦值,那么編譯器會(huì)將他們賦值為 0 和 1。
而 FAT,因?yàn)槲覀円呀?jīng)指定了其值為 90,所以 FAT 就等于 90。
enum 和 #define 的區(qū)別
是不是覺得 enum 和用 #define 來定義的常量是有些類似呢?
其實(shí),還是有些不同的:
#define 宏常量(或預(yù)處理常量)是在預(yù)處理階段進(jìn)行簡(jiǎn)單替換,枚舉常量則是在編譯的時(shí)候確定其值。
一般在編譯器里,可以調(diào)試枚舉常量,但是不能調(diào)試宏常量。
枚舉可以一次定義大量相關(guān)的常量,而 #define 宏一次只能定義一個(gè)。
7. 總結(jié)
結(jié)構(gòu)體(struct)是一種自定義的變量類型,完全由我們自由發(fā)揮,自己定制(走的是“高級(jí)定制”的路線啊),與 int,double 等基礎(chǔ)變量類型有所區(qū)別。結(jié)構(gòu)體的使用可以讓我們的 C語言程序更加靈活,可以做更多事。
結(jié)構(gòu)體里包含成員變量,通常是基礎(chǔ)變量類型的變量,如 int,double 等變量,但也可以有指針變量,數(shù)組,甚至其他的結(jié)構(gòu)體變量。
為了訪問到結(jié)構(gòu)體的成員變量,我們可以用普通的結(jié)構(gòu)體方式訪問:結(jié)構(gòu)體變量名稱.成員變量名(中間用一個(gè)“點(diǎn)”連接)。
我們也可以用特別簡(jiǎn)便的結(jié)構(gòu)體指針的方式來訪問結(jié)構(gòu)體的成員變量:結(jié)構(gòu)體指針變量名->成員變量名(中間用一個(gè)“箭頭”連接)。
union(“共用體”,或“聯(lián)合”)和 struct 的最大不同就是:union 的大小是其中容量最大的那個(gè)成員變量的大小,而結(jié)構(gòu)體的大小是每一個(gè)成員變量的總和(還要考慮內(nèi)存對(duì)齊)。union 一次只能取其中一個(gè)變量。
enum(枚舉)一次只能取其中的一個(gè)成員的值,這一點(diǎn)和 union 有些類似。但是 enum 的成員都是常量,而不是變量。而且 enum 的成員如果沒有指定數(shù)值,編譯器會(huì)按照遞增順序?yàn)槊恳粋€(gè)變量賦值,從 0 開始。
8. 第二部分第七課預(yù)告
今天的課就到這里,一起加油吧!
下一課:C語言探索之旅 | 第二部分第七課:文件讀寫
作者:謝恩銘
出處:公眾號(hào)「程序員聯(lián)盟」
原文鏈接:https://www.jianshu.com/p/39b41aa5cca7
轉(zhuǎn)載請(qǐng)注明出處,謝謝合作!轉(zhuǎn)載授權(quán)請(qǐng)加我微信 frogoscar
喜歡本文的朋友,歡迎關(guān)注公眾號(hào)?程序員聯(lián)盟,收看更多精彩內(nèi)容
點(diǎn)個(gè)[在看],是對(duì)我最大的支持!總結(jié)
以上是生活随笔為你收集整理的c语言 如何创建adt_C语言探索之旅 | 第二部分第六课:创建你自己的变量类型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人工智能用python还是java_学会
- 下一篇: 基带工程师是做什么的_【思唯网络学院】网