結構體的基礎知識,位段,聯合+列舉
一、。結構體基礎知識
結構體屬於聚合資料型別,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);