1. 程式人生 > >結構體的基礎知識,位段,聯合+列舉

結構體的基礎知識,位段,聯合+列舉

一、。結構體基礎知識

結構體屬於聚合資料型別,C語言提供了兩種聚合資料型別,陣列和結構體。
數組裡面儲存的是同類型的元素的集合,它的每個元素是通過下標引用或者指標間接訪問來選擇的。
結構也是一些值的集合,這些值稱為它的成員,但一個結構的各個成員可能具有不同的型別,他們需要通過名字去訪問,那個成員都有自己的名字。

1.結構宣告

舉個例子:

struct SIMPLE
{
    int a;
    char b;
    flaot c;
};  //注意這裡必須有一個分號

這個結構體包含三個成員變數,分別為int型的a,char型的b,float型的c,SIMPLE為一個標籤。這個宣告把標籤SIMPLE和這個成員列表聯絡在一起。注意這只是一個宣告,並沒有提供變數列表,所以它並未建立任何變數。已經宣告之後的結構體struct SIMPLE 類似於int,double,它相當於一個結構體型別的識別符號,可以用它來建立結構體變數。比如:

struct SIMPLE x;
struct SIMPLE y[20],*z;

這裡的x,y和z都是同一型別的結構體,可以互相賦值,比如z=&x。
需要注意的是,如果不使用標籤去宣告結構體,必須在結構體後面直接建立結構體變數。

struct{
    int a;
    char b;
    float c;
}x;
    struct{
    int a;
    char b;
    float c;
}y[20],*z;

這裡雖然聲明瞭兩個結構體型別,而且他們的成員變數一摸一樣,但是它們並不是同一型別的結構體變數,沒有標籤系統會認為這是兩個不同型別的結構體,因此z=&x是非法的。而且這種結構體只能建立一次,無法建立第二個變數。

2.結構成員

結構體成員可以是標量、陣列、指標甚至是其他結構體。
這裡有一個更為複雜的例子:

struct COMPLEX{
    float f;
    int a[20];
    long *lp;
    struct SIMPLE s;
    struct SIMPLE sa[10];
    struct SIMPLE *sp;
};

一個結構體的成員的名字可以和其他結構的成員名字相同,所以這個結構的成員a並不會與struct SIMPLE s 的成員a衝突。

3.結構成員的直接訪問和間接訪問

結構變數的成員是通過點操作符(.)訪問的。點操作符接受兩個運算元,左運算元解釋結構變數的名字,有運算元就是需要訪問的成員的名字。這個表示式的結果就是指定的成員。例如:

struct COMPLEX comp;
//假設使用上面的結構體
comp.a;  //訪問comp結構體的a成員變數,a是一個數組,所以comp.a代表它的陣列名
(comp.s).a;  //訪問s結構體的a成員,因為(.)的結合性是從左向右的所以括號可以取消comp.s.a
((comp.sa)[4]).c;  //

當你有一個指向結構體地址的指標p時,要取出結構體成員變數,首先想到的是對指標進行解引用操作,拿出結構體名,然後用點操作符找到結構體成員變數。因為*操作的優先順序低於(.)操作,所以必須加括號(*p).a。但是為了方便起見有一個更方便的操作符就有了,稱為箭頭操作符:

p->a;
p->b;
p->c;

結構體指標可以這樣訪問結構體成員變數。

4.結構的自引用

在一個結構內部包含一個型別為該結構本身的成員:

struct SELF_REF1{
    int a;
    struct SELF_REF1 b;
    int c;
};

這種型別的自引用是非法的,因為成員b裡面又包含著一個同類型的結構體,這樣下去就永無止境,更不知道這個結構體有多大。但如果用指標的方法宣告卻是合法的:

strcut SELF_REF2{
    int a;
    struct SELF_REF2 *b;
    int c;
};

這個宣告和前面哪個宣告的區別在於成員b是一個指標而不是一個結構,編譯器在結構的長度確定之前就已經知道指標的長度。

5.不完整宣告

看下面這個例子:

struct A {
    struct B *partner;
};
struct B {
    struct A *partner;
};

這是錯誤的,因為在宣告A的時候發現它裡面有結構體B的指標,但是因為結構體B沒有宣告,所以系統找不到B。當你把B放在前面那麼B裡面有結構體A的指標,會產生同樣的錯誤。處理的辦法是建立一個不完整宣告:

struct B;
struct A {
    struct B *partner;
};
struct B {
    struct A *partner;
};

在A的上面加上B的不完整宣告,這樣告訴計算機有一個B型別的結構的,就不會出錯了。

6.結構的初始化

結構的初始化方式和陣列的初始化很相似,一個位於一堆花括號內部、由逗號分隔的初始值列表可用於結構各個成員的初始化,這些值根據結構成員的順序寫出。如果初始列表的值不夠,剩餘的結構成員將使用預設值進行初始化。
這裡有一個例子:

struct INIT_EX{
    int a;
    short b[10];
    Simple c;
}x = {
    10,
    {1,2,3,4,5},
    {25,'x',1.9}
};

二、位段

位段的成員必須是int、unsigned int和signed int的型別,其次,在成員的後面是一個冒號和一個整數,這個整數指定該位段所佔用的位的數目。
下面是一個位段宣告的例子:

struct CHAR{    
    unsigned ch  : 7;
    unsigned font: 6;
    unsigned size: 19;
};
struct CHAR ch1;

這個位段所佔的空間大小為8個位元組,位段的空間上需要按照以四個位元組或者一個位元組(字元)的方式來開闢的,6+7=13,13+19>32,所以得再開闢4個位元組。
位段涉及很多不確定因素,所以位段是不跨平臺的。

位段不能跨平臺的因素:

1.系統的儲存方式不一樣(大小端),從左到右還是從右到左。
2.int被系統當作正號還是負號不確定。
3.32位機器定義的27位,位段,無法在16位機器中執行。
4.當第二個位段成員過大,第一個位段成員的剩餘空間無法滿足它時,是緊貼著第一個儲存,還是重新開闢空間,浪費剩餘空間。

總結

位段雖然可以很好的節省空間,但是它有跨平臺的限制。

三、列舉

列舉就是一一列舉,把可能的值一一列舉出來。例如:

enum Color
{
    RED,
    GREEN,
    BLUE
};

以上定義的enum Color是列舉型別,{}內的是列舉型別的可能取值,也叫列舉常量,它們用 “,”隔開,預設值是0、1、2。。。也可以在定義的時候賦初值:

enum Color
{
    RED=1,
    GREEN=3,
    BLUE=7
};
enum Color s = RED;   //列舉變數初始賦值只能使用列舉常量
s = 10;     //之後的賦值沒有上面這個要求

我們可以用#define來定義常量,為什麼還要使用列舉?
1.列舉的可讀性和可維護性更高。
2.列舉具有型別檢查,更嚴謹。
3.防止命名汙染。
4.便於除錯。
5.使用方便,一次可以定義多個。

四、聯合

聯合也是一種特殊的自定義型別
這種型別定義的變數也包含一些成員,特點就是這些成員使用同一塊空間(所以聯合也叫共用體)。
例如:

union S 
{
    int a;
    char b;
};
struct S c;  //定義聯合變數
printf("%d",sizeof(c));  //計算聯合變數的大小

上面計算的大小是4,因為變數公用一塊空間,大小至少是最大的成員的大小,並且要是最大對齊數的整數倍。
面試題大小端問題可以用聯合很好的解決。
聯合和結構體的巧妙使用:

//將long型別的IP地址,轉化為點分十進位制的表示形式
union ip_addr
{
    unsigned long addr,
    struct
    {
        unsigned char c1;
        unsigned char c2;
        unsigned char c3;
        unsigned char c4;
    }ip;
};
union ip_addr my_ip;
my_ip.addr = 176238749;
printf("%d.%d.%d.%d\n",my_ip.ip.c1,my_ip.ip.c2,my_ip.ip.c3,my_ip.ip.c4);