結構體 (C/C++)

 

前面兩篇基本把指針給介紹完了,相信大家對指針已經不是那麼陌生了。也不會因爲指針和數組之間的關係而導致混淆了。大家可能也迫不及待想了解下後來的知識。今天我們就介紹下結構體。

 

對於結構體,既然叫結構體,形象上我們可以理解其就是一堆數據集合在一起形成一個結構。就比如一個學生的信息包括:學號、姓名、班級、年齡等等。這些信息都是屬於這個學生的,因此我們就可以將這些信息統一綁定在一起。形成一個學生實體,這裏有點C++的味道。我們學C也還是有必要這樣思考。在我們周圍幾乎每一樣東西都有它自己的信息或者組成。比如藥品,它有什麼功效,有什麼成分等等都能統一綁定在一起形成一個實體,我們在程序中就能方便的訪問這些實體的每一個信息或組成。因此,當我們在設計一個程序的時候,我們就能把一些具有共同特性或者組成元素集合到一起構成一個結構體。比如我們的學生就可以寫成:

struct SStudent

{

    char name[ 13 ];                   // 姓名

    char className[ 16 ];           // 班級名

    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++中同樣有效。

 

在結構體中很多時候會用到位域,這裏暫時不說,先留個思路在這裏。等我們專門談位運算的時候再來詳細說明。

 

好了,本文就介紹到這裏,還是一些比較初級的問題。只爲了大家加深理解。與更多的東西結合着用。才能使用除更靈活的方法。加油!

轉載自: http://blog.csdn.net/masefee/article/details/5204388在這裏,感謝原創的分享!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章