【C/C++語言入門篇】– 結構體

來源:互聯網
上載者:User

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

 

對於結構體,既然叫結構體,形象上我們可以理解其就是一堆資料集合在一起形成一個結構。就比如一個學生的資訊包括:學號、姓名、班級、年齡等等。這些資訊都是屬於這個學生的,因此我們就可以將這些資訊統一綁定在一起。形成一個學生實體,這裡有點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位元組,這是因為結構體在處理對齊問題的時候,都是以最大的基本類型資料成員為標準進行對齊(注意這裡是基礎資料型別 (Elementary Data Type))。假如:

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/C++入門篇系列】

【C/C++語言入門篇】-- 序言

【C/C++語言入門篇】-- HelloWorld思考

【C/C++語言入門篇】-- 基礎資料型別 (Elementary Data Type)

【C/C++語言入門篇】-- 調試基礎

【C/C++語言入門篇】-- 深入指標

【C/C++語言入門篇】-- 數組與指標

【C/C++語言入門篇】-- 結構體

【C/C++語言入門篇】-- 深入函數

【C/C++語言入門篇】-- 位元運算

【C/C++語言入門篇】-- 剖析浮點數

【C/C++語言入門篇】-- 檔案操作

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.