前面兩篇基本把指針給介紹完了,相信你們對指針已經不是那麼陌生了。也不會由於指針和數組之間的關係而致使混淆了。你們可能也火燒眉毛想了解下後來的知識。今天咱們就介紹下結構體。數組
對於結構體,既然叫結構體,形象上咱們能夠理解其就是一堆數據集合在一塊兒造成一個結構。就好比一個學生的信息包括:學號、姓名、班級、年齡等等。這些信息都是屬於這個學生的,所以咱們就能夠將這些信息統一綁定在一塊兒。造成一個學生實體,這裏有點C++的味道。咱們學C也仍是有必要這樣思考。在咱們周圍幾乎每同樣東西都有它本身的信息或者組成。好比藥品,它有什麼功效,有什麼成分等等都能統一綁定在一塊兒造成一個實體,咱們在程序中就能方便的訪問這些實體的每個信息或組成。所以,當咱們在設計一個程序的時候,咱們就能把一些具備共同特性或者組成元素集合到一塊兒構成一個結構體。好比咱們的學生就能夠寫成:ide
struct SStudent模塊化
{函數
char name[ 13 ]; // 姓名工具
char className[ 16 ]; // 班級名spa
char age; // 年齡設計
....指針
};調試
這樣一來,學生這個活生生的實體就把全部關於他的信息集中在一塊兒了。這樣就能集中管理了,裏面的每個信息就能經過結構體變量來訪問。先看看怎麼訪問:對象
C語言:
struct SStudent student;
student.age = 22;
C++:
SStudent student;
student.age = 22;
從上面能夠看出要訪問一個結構體成員是很方便的,同時也體現了實體的概念。咱們將學生實體的年齡信息取出來賦值爲22歲。就好像在使用某個東西的某個功能同樣。這也是衆多面向對象語言的一種思想。就是將程序數據封裝話、結構化,咱們要操做一個數據就跟現實生活中的使用某個工具的某個功能同樣。咱們看到上面C和C++版本訪問惟一不一樣的就是C++版本在聲明結構體變量的時候不須要在前面加上struct關鍵字,我的以爲後來C++以爲struct沒有必要再寫了吧,麻煩!省略了不是更好!在語法和意義上兩個版本是相同的。
結構體還能夠不須要名字,好比:
struct
{
char age;
char name[ 16 ];
}student, stu[ 10 ];
這裏這個結構體就省略了名字,後面的student並非名字,而是結構體變量。這種就是匿名結構體。跟普通的沒有什麼區別,後面的stu就是一個結構體數組,普通結構體定義也能夠在聲明結構體的時候緊跟着就聲明變量的。只是這樣你要定義其它變量就麻煩了,呵呵!這種通常用得比較固定或者就用這麼一次就能夠不要名字。
再來看看結構體別名。所謂別名就是可使用另一個名字。
typedef struct SStudent
{
char age;
char sex;
char class;
}STU, *PSTU;
這裏的STU就是SStudent結構體的別名,就至關因而另一個名字,使用的時候就能夠不用加可惡的struct標識符了。
STU student;
PSTU pStuednt; // 別名爲指針類型
好了,結構體就這麼簡單,就是把不一樣類型或者同類型的一些數據集中到一塊兒管理,構成一個實體。這個實體也能夠理解爲結構體。一般這樣設計是爲了程序的模塊化結構化,這樣理解起來更容易更接近於現實,計算機原本就是服務於現實的。再好比咱們的鏈表(將一組數據串聯成一個鏈,咱們能夠經過指針訪問到這個鏈中的每個結點,形象的叫着是一個鏈,本質其實就是一組數據經過指針連接在一塊兒,一般存放在內存中是不連續的),舉個簡單的例子:
struct Node
{
Node* pNext;
char name[ 16 ];
};
這裏也是一個結構體,裏面包含一個指針和一個名字。假如咱們這個名字就是某個學生的名字,這個結構體咱們就形象當作是一個結點,什麼是結點?結點你能夠想象我有一條很漂亮的珍珠項鍊,項鍊上有不少顆珍珠串聯在一塊兒,那麼每一顆珍珠就能夠想象成是一個結點。項鍊就是由不少個結點串聯在一塊兒造成的。可能有的讀者以爲這樣比喻卻是很容易理解,可是聯想到程序裏面仍是感受有點抽象。其實也不能說是抽象,我們就想成它就是這麼回事。就比如咱們要安裝一個工具,注意到這句話裏面出現了兩個現實生活中的詞:「安裝」「工具」。在計算機裏咱們使用的所謂工具其實都是虛擬化的,這些名字只是爲了形象一點,再說安裝,也是如此,在現實生活中咱們會在組裝或者安裝某個零件的時候纔會使用這個詞,在計算機裏使用這個詞也是爲了你們可以更容易理解形象化罷了。因此咱們沒必要太拘泥於叫法。
好,咱們這裏定義了一個結構體做爲結點,咱們的目的是想把全班全部學生的名字所有串聯在一塊兒,假如全班有50我的,那麼就有50個結點。所以咱們必須的有50個結構體結點來保存這50個學生的名字,並且咱們這50個學生的名字還可以經過循環遍歷可以找到其中任意一個。那麼咱們就得這樣作:
struct Node root; // 根結點(第一個學生結點)
struct Node secSt; // 第2個學生結點
上面咱們定義了2個學生結點,如今把這兩個結點連接在一塊兒。
strcpy( root.name, "masefee" );
strcpy( secSt.name, "Tim" );
root.pNext = &secSt;
secSt.pNext = NULL;
上面咱們已經把這兩個結點連接起來了。root結點的next指針指向的就是secSt,secSt的next指針這裏賦值爲NULL,若是還想指向下一個學生結點同理。再看看層級關係:
root---|----name ("masefee")
|----pNext---|----name ("Tim")
|----pNext---|----name ... ...
|----pNext ... ...
上面的層級關係很清晰的描述了這些結點的關係,這樣就可以成一個鏈,咱們能夠經過遍歷找到其中任何一個結點。咱們也稱這種存儲在內存中爲鏈式存儲。其本質就是經過指針將一個一個數據塊連接在一塊兒。這裏我只列舉了兩個結點。
問題一:咱們怎麼將50個結點連接在一塊兒?(提示:每一個結點能夠malloc申請內存空間)
經過上面的描述,咱們對結構體的用法和概念上有了初步的認識了。再來看看結構體指針(怎麼老是離不開指針,呵呵,沒辦法指針在CC++裏原本就是個永恆的主題)。
struct Node stuNode; // struct Node
struct Node* pNode = &stuNode;
strcpy( pNode->name, "masefee" );
上面,咱們定義了一個Node結構體指針,該指針指向了stuNode,最後咱們將stuNode結構體的name拷貝成了「masefee」。一樣咱們可使用庫函數給申請空間,大小爲Node結構的大小:
struct Node* pNode = ( struct Node* )malloc( sizeof( struct Node ) );
這裏咱們使用malloc函數給申請了Node結構大小的一塊內存,而後讓pNode指針指向這塊空間。所以咱們就能夠向這塊內存中寫入值了。
strcpy( pNode->name, "masefee" );
pNode->pNext = NULL;
這裏的pNext也能夠指向下一塊申請的內存空間(能夠用來回答問題一),這裏就不寫了,你們要本身摸索才行。
說到這裏,不得不說說結構體的對齊問題,什麼是結構體對齊,爲何要對齊。咱們都知道計算機的內存單位換算都是以2的多少次方來計算的,這樣計算是有目的性的。固然是爲了計算機的執行效率,你們能夠想象一下,假如咱們一個變量的類型佔用3字節,一個5字節,一個1字節。計算機在尋址的時候對於這種良莠不齊的內存會下降它的效率。因此一般默認狀況下,結構體採用4字節對齊,意思就是說一些不足4字節的變量會可能被擴充到4字節大小或者與其它結構體成員變量進行合併成4字節。這樣浪費小小的一點內存效率上會提升不少。這裏說到4字節,固然就有8字節,16字節,1字節,2字節對齊了。咱們這裏就默認談談4字節對齊,其它都是同理的。先舉個例子:
struct Align
{
char age;
int num;
};
sizeof( struct Align ) = ?
這裏求sizeof的結果咱們獲得的確是8,而不是咱們想要的5。這裏是8的緣由是默認爲4字節對齊,這裏char佔用1字節,int佔用4字節,首先編譯器編譯的時候遇到char會去尋找周圍有沒有更多的能夠合併的字節,一共合併成4字節,或者合併一部分而後擴充一部分構成4字節,可是這裏沒有找到,那麼age將被擴充到4字節,加上int的4字節,一共被擴充到了8字節。
struct Align align;
align.age = 0xff;
align.num = 0xeeeeeeee;
咱們覺得在內存中分佈爲:
age num
ff ee ee ee ee
然而:
age num
ff cc cc cc ee ee ee ee
age多出來了3個字節,這裏未初始化時填充的是0xcc。假如咱們定義成:
struct Align
{
char age;
char age1;
char age2;
int num;
};
那麼age2將被擴充爲2字節,age age1 age2合併成3字節再擴充一個字節就組成4字節了。這裏sizeof仍是爲8字節。再好比:
struct Align
{
char age;
int num;
char age1;
};
這樣sizeof結果出來將是12字節,緣由也很簡單,首先在編譯age的時候,查找挨着沒有能合併成4字節的成員,那麼就會擴充成4字節,age1同理,假如age爲0xff,num爲0xeeeeeeee,age1爲0xaa,內存分佈就爲:
ff cc cc cc ee ee ee ee aa cc cc cc
問題二:這裏爲何不將age和age1分別擴充爲2字節而後再合併成4字節,結構體一共8字節?
再舉個例子。
struct Align
{
char age;
double num;
char age1;
};
這裏的sizeof將是24字節,緣由就是結構體對齊仍是有標準的,假如默認是4字節對齊,常理這裏徹底能夠將age和age1分別擴充成4字節,整個結構體16字節。可是編譯器並無這麼作,而是都擴充成了8字節,這是由於結構體在處理對齊問題的時候,都是以最大的基本類型數據成員爲標準進行對齊(注意這裏是基本數據類型)。假如:
struct SStudent
{
int a[ 2 ];
}
struct Align
{
char age1;
struct SStudent stu;
};
這個Align結構體一樣仍是12字節,而不是16字節。
再好比:
struct SStudent
{
char a[ 13 ];
};
struct Align
{
char age1;
struct SStudent stu;
};
問題三:上面程序中Align結構體的大小是多少?爲何?
一樣再來看看結構體指針和任意指針強制類型轉換。
typedef unsigned char byte;
struct SStudent
{
byte age;
byte sex;
byte class;
};
byte array[ 99 ];
struct SStudent* pStu;
array[ 0 ] = 0xaa;
array[ 1 ] = 0xbb;
array[ 2 ] = 0xcc;
pStu = ( struct SStudent* )array;
上面這段程序,咱們將array的前3個元素賦值爲0xaa,0xbb,0xcc。這樣作的目的是想看看咱們強制類型轉換事後,pStu結構體指針的pStu[ 0 ]三個成員是否就是array數組的前3個成員。答案是確定的,你們能夠本身調試監視看。這個array數組強制類型轉換過去後,pStu[ 0 ], pStu[ 1 ], ... , pStu[ 31 ], pStu[ 32 ]。一共就有33個結構體數據塊。一樣pStu++相似的加減及累加都會跳躍SStudent結構體大小個字節。跟前面一篇提到的原理同樣。
在C++中結構體發生了翻天覆地的變化,跟C的結構體很大差異,這裏暫時不說了。等咱們說了函數的時候再談C++的結構體。不過本文提到的結構體相關在C++中一樣有效。
在結構體中不少時候會用到位域,這裏暫時不說,先留個思路在這裏。等咱們專門談位運算的時候再來詳細說明。
好了,本文就介紹到這裏,仍是一些比較初級的問題。只爲了你們加深理解。與更多的東西結合着用。才能使用除更靈活的方法。加油!
【C++語言入門篇】系列: