1. 程式人生 > >C語言系列(六)結構體和聯合體

C語言系列(六)結構體和聯合體

結構體

在C語言中,可以使用結構體(Struct)來存放一組不同型別的資料。結構體的定義形式為:

struct 結構體名{
    結構體所包含的變數或陣列
};

結構體是一種集合,它裡面包含了多個變數或陣列,它們的型別可以相同,也可以不同,每個這樣的變數或陣列都稱為結構體的成員(Member)。請看下面的一個栗子:

struct stu{
    char *name;  //姓名
    int num;  //學號
    int age;  //年齡
    char group;  //所在學習小組
    float score;  //成績
};

stu 為結構體名,它包含了 5 個成員,分別是 name、num、age、group、score。結構體成員的定義方式與變數和陣列的定義方式相同,只是不能初始化。

注意大括號後面的分號“;”不能少哦~

結構體也是一種資料型別,它由我們自己來定義,可以包含多個其他型別的資料。
像int、float、char 等是由C語言本身提供的資料型別,不能再進行分拆,我們稱之為基本資料型別;而結構體可以包含多個基本型別的資料,也可以包含其他的結構體。

結構體變數

既然結構體是一種資料型別,那麼就可以用它來定義變數。例如:

struct stu stu1, stu2;

定義了兩個變數 stu1 和 stu2,它們都是 stu 型別,都由 5 個成員組成。注意關鍵字struct不能少。

還可以在定義結構體的同時定義結構體變數:

struct
stu{ char *name; //姓名 int num; //學號 int age; //年齡 char group; //所在學習小組 float score; //成績 } stu1, stu2;

如果只需要 stu1、stu2 兩個變數,後面不需要再使用結構體名定義其他變數,那麼在定義時也可以不給出結構體名,如下所示:

struct{  //沒有寫 stu
    char *name;  //姓名
    int num;  //學號
    int age;  //年齡
    char group;  //所在學習小組
    float score;  //成績
} stu1, stu2;

這樣的寫法很簡單,但是因為沒有結構體名,後面就沒法用該結構體定義新的變量了。

理論上講結構體的各個成員在記憶體中是連續儲存的,和陣列非常類似,例如上面的結構體變數 stu1、stu2 的記憶體分佈如下圖所示,共佔用 4+4+4+1+4 = 17 個位元組。但是在編譯器的具體實現中,各個成員之間可能會存在空隙,C語言中,結構體大小的記憶體分配,參考於這片文章:C語言中結構體大小計算即儲存分配

這裡我在做下總結:

運算子sizeof可以計算出給定型別的大小,對於32位系統來說,sizeof(char) = 1; sizeof(int) = 4。基本資料型別的大小很好計算,我們來看一下如何計算構造資料型別的大小。

  C語言中的構造資料型別有三種:陣列、結構體和共用體。

  陣列是相同型別的元素的集合,只要會計算單個元素的大小,整個陣列所佔空間等於基礎元素大小乘上元素的個數。

  結構體中的成員可以是不同的資料型別,成員按照定義時的順序依次儲存在連續的記憶體空間。和陣列不一樣的是,結構體的大小不是所有成員大小簡單的相加,需要考慮到系統在儲存結構體變數時的地址對齊問題。看下面這樣的一個結構體:

  struct stu1
  {
  int i;
  char c;
  int j;
  };

  先介紹一個相關的概念——偏移量。偏移量指的是結構體變數中成員的地址和結構體變數地址 的差。結構體大小等於最後一個成員的偏移量加上最後一個成員的大小。顯然,結構體變數中第一個成員的地址就是結構體變數的首地址。因此,第一個成員i的偏 移量為0。第二個成員c的偏移量是第一個成員的偏移量加上第一個成員的大小(0+4),其值為4;第三個成員j的偏移量是第二個成員的偏移量加上第二個成 員的大小(4+1),其值為5。

  實際上,由於儲存變數時地址對齊的要求,編譯器在編譯程式時會遵循兩條原則:一、結構體變數中成員的偏移量必須是成員大小的整數倍(0被認為是任何數的整數倍) 二、結構體大小必須是所有成員大小的整數倍。

  對照第一條,上面的例子中前兩個成員的偏移量都滿足要求,但第三個成員的偏移量為5,並不是自身(int)大小的整數倍。編譯器在處理時會在第二個成員後面補上3個空位元組,使得第三個成員的偏移量變成8。

  對照第二條,結構體大小等於最後一個成員的偏移量加上其大小,上面的例子中計算出來的大小為12,滿足要求。

  再看一個滿足第一條,不滿足第二條的情況:

  struct stu2
  {
  int k;
  short t;
  };

  成員k的偏移量為0;成員t的偏移量為4,都不需要調整。但計算出來的大小為6,顯然不 是成員k大小的整數倍。因此,編譯器會在成員t後面補上2個位元組,使得結構體的大小變成8從而滿足第二個要求。由此可見,大家在定義結構體型別時需要考慮 到位元組對齊的情況,不同的順序會影響到結構體的大小。對比下面兩種定義順序

  struct stu3
  {
  char c1;
  int i;
  char c2;
  }

  struct stu4
  {
  char c1;
  char c2;
  int i;
  }

  雖然結構體stu3和stu4中成員都一樣,但sizeof(struct stu3)的值為12而sizeof(struct stu4)的值為8。

  如果結構體中的成員又是另外一種結構體型別時應該怎麼計算呢?只需把其展開即可。但有一點需要注意,展開後的結構體的第一個成員的偏移量應當是被展開的結構體中最大的成員的整數倍。看下面的例子:

  struct stu5
  {
  short i;
  
  struct{
    char c;
    int j;
  } ss;
 
  int k;
  }

  結構體stu5的成員ss.c的偏移量應該是4,而不是2。整個結構體大小應該是16。

  如何給結構體變數分配空間由編譯器決定,以上情況針對的是Linux下的GCC。其他平臺的C編譯器可能會有不同的處理,看到這裡估計還是有些同學不太明白,多看幾遍,領悟領悟,就好啦!

成員的獲取和賦值

結構體和陣列類似,也是一組資料的集合,整體使用沒有太大的意義。陣列使用下標[ ]獲取單個元素,結構體使用點號.獲取單個成員。獲取結構體成員的一般格式為:

結構體變數名.成員名;

通過上面的格式就可以獲取成員的值,和給成員賦值,看下面的栗子:

#include <stdio.h>
int main(){
    struct{
        char *name;  //姓名
        int num;  //學號
        int age;  //年齡
        char group;  //所在小組
        float score;  //成績
    } stu1;
    //給結構體成員賦值
    stu1.name = "haozi";
    stu1.num = 12;
    stu1.age = 18;
    stu1.group = 'A';
    stu1.score = 136.5;
    //讀取結構體成員的值
    printf("%s的學號是%d,年齡是%d,在%c組,今年的成績是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
    return 0;
}

執行結果:
haozi的學號是12,年齡是18,在A組,今年的成績是136.5!
除了這種方式賦值外,還可以在定義的時候賦值:

struct{
    char *name;  //姓名
    int num;  //學號
    int age;  //年齡
    char group;  //所在小組
    float score;  //成績
} stu1, stu2 = { "haozi", 12, 18, 'A', 136.5 };

不過整體賦值僅限於定義結構體變數的時候,在使用過程中只能對成員逐一賦值,這和陣列的賦值非常類似。

結構體與指標

指標也可以指向一個結構體,定義的形式一般為:

struct 結構體名 *變數名;

看例子:

struct Man{
    char name[20];
    int age;
};
void main(){
    struct Man m1 = {"Jack",30};
    //結構體指標
    struct Man *p = &m1;
    printf("%s,%d\n", m1.name, m1.age);
    printf("%s,%d\n",(*p).name,(*p).age);
    //“->”(箭頭)是“(*p).”簡寫形式
    printf("%s,%d\n", p->name, p->age);
    //(*env)->

    system("pause");
}

編譯出的結果是:

Jack,30
Jack,30
Jack,30
請按任意鍵繼續. . .

上面程式碼:printf(“%s,%d\n”, m1.name, m1.age);還可以換成: printf(“%s,%d\n”, (*p).name, m1.age);或者 printf(“%s,%d\n”, p->name, m1.age);
其執行結果還是一樣的。

獲取結構體成員通過結構體指標可以獲取結構體成員,一般形式為:

(*pointer).memberName

或者:

pointer->memberName

聯合體(共用體)

結構體(Struct)是一種構造型別或複雜型別,它可以包含多個型別不同的成員。在C語言中,還有另外一種和結構體非常類似的語法,叫做共用體(Union),它的定義格式為:

union 共用體名{
    成員列表
};

共用體有時也被成為聯合體;

結構體和共用體的區別在於:結構體的各個成員會佔用不同的記憶體,互相之間沒有影響;而共用體的所有成員佔用同一段記憶體,修改一個成員會影響其餘所有成員。

結構體佔用的記憶體大於等於所有成員佔用的記憶體的總和(成員之間可能會存在縫隙),共用體佔用的記憶體等於最長的成員佔用的記憶體。共用體使用了記憶體覆蓋技術,同一時刻只能儲存一個成員的值,如果對新的成員賦值,就會把原來成員的值覆蓋掉。

共用體也是一種自定義型別,可以通過它來建立變數,例如:

union data{
    int n;
    char ch;
    double f;
};
union data a, b, c;

上面是先定義共用體,再建立變數,也可以在定義共用體的同時建立變數:

union data{
    int n;
    char ch;
    double f;
} a, b, c;

共用體 data 中,成員 f 佔用的記憶體最多,為 8 個位元組,所以 data 型別的變數(也就是 a、b、c)也佔用 8 個位元組的記憶體,請看下面的栗子:

#include<stdio.h>  
union var{  
        long j;  
        int i;  
};  
main(){  
        union var v;  
        v.j = 5;  
        printf("v.j is %d\n",v.i);  
        v.i = 6;  //最後一次賦值有效
        printf("now v.j is %ld! the address is %p\n",v.j,&v.j);  

        printf("now v.i is %d! the address is %p\n",v.i,&v.i);  

        system("pause");
}  

編譯並執行結果:

v.j is 5  
now v.j is 6! the address is 0xbfad1e2c  
now v.i is 6! the address is 0xbfad1e2c  

這段程式碼不但驗證了共用體的長度,還說明共用體成員之間會相互影響,修改一個成員的值會影響其他成員。

更多系列相關文章傳送門: