C語言自定義型別:結構體、列舉、聯合
結構體
結構是一些值的集合,這些值成為成員變數。結構的每個成員可以是不同型別的變數。
結構體的宣告
struct tag //結構體型別名
{
member-list; //成員列表
}variable-list; //變數列表
例如描述一個學生:
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}; //注意分號不能丟
結構體的自引用
結構體中可以包含該結構體本身嗎?答案是可以的。
正確的自引用方式:
struct Node
{
int data;
struct Node* next;
};
結構體變數的定義和初始化
定義變數:
struct Point
{
int x;
int y;
}p1; //宣告型別的同時定義變數p1
struct Point p2; //定義結構體變數p2
初始化:
//定義變數的同時賦初值
struct Point p3 = {x,y};
struct Stu //型別宣告
{
char name[15]; //名字
int age; //年齡
};
struct Stu s = {"zhangsan",20}; //初始化
struct Node
{
int data;
struct Point p;
struct Node *next;
}n1={10,{4,5},NULL}; //結構體巢狀初始化
struct Node n2 = {20,{5,6},NULL}; //結構體巢狀初始化
結構體成員的訪問
結構體的成員可以是標量、陣列、指標,甚至是其他結構體。
結構體變數訪問成員是通過點操作符訪問的,如果結構體變數是指標變數,則通過 ” -> ” 操作符來訪問。例如:
可以看到s有成員name和age,訪問成員:
//點操作符訪問成員
struct Stu s;
strcpy(s.name,"zhangsan"); //使用.訪問name成員
s.age = 20; //使用.訪問age成員
//->操作符訪問成員
struct Stu *p;
strcpy(p->name,"lisi"); //使用->訪問name成員
p->age = 20; //使用->訪問age成員
結構體記憶體對齊
我們先來看幾道練習題:
//練習1
struct s1
{
char c1;
int i;
char c2;
};
printf("%d\n",sizeof(struct s1));
程式的輸出結果是12。
分析一下:
//練習2
struct s2
{
double d;//對齊數為8
char c;//對齊數為1
int i;//對齊數為4
};
printf("%d\n",sizeof(struct s2));
程式的輸出結果為16。分析過程同上。
//練習3-結構體巢狀問題
struct s3
{
char c1;//對齊數為1
struct s2;//對齊數為8 因為s2中最大對齊數是8
double d;//對齊數是8
};
printf("%d\n",sizeof(struct s3));
程式的輸出結果為32。分析過程同上。
結構體對齊原因
- 平臺原因:不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。
- 效能原因:資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需要做兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問。
結構體記憶體對齊規則
- 第一個成員在與結構體變數偏移量為0的地址處。
其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處。
對齊數 = 編譯器預設的一個對齊數與該成員大小的較小值(VS中為8,Linux中為4)結構體總大小為最大對齊數的的整數倍。
- 如果嵌套了結構體,巢狀的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含巢狀結構體的對齊數)的整數倍。
修改預設對齊數
我們可以通過#pragma這個預處理指令,來改變我們的預設對齊數。
如:
#include<stdio.h>
#pragma pack(8) //設定預設對齊數為8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack() //取消設定的對齊數,還原為預設
結構體在對齊方式不合適的時候,我們可以自己更改預設對齊數。
結構體傳參
有如下程式碼:
#include <stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4},1000};
//結構體傳參
void print1(struct S s)
{
printf("%d\n",s.num);
}
//結構體地址傳參
void print2(struct S* ps)
{
printf("%d\n",ps->num);
}
int main()
{
print1(s);//傳結構體
print2(&s);//傳地址
return 0;
}
在上述的程式碼中,一個是傳結構體,一個是傳結構體地址,使用時應首選print2函式。
原因:函式傳參的時候,引數是需要壓棧的。如果傳遞一個結構體物件的時候,結構體過大,引數壓棧的系統開銷比較大,所以會導致效能的下降。
結構體在傳參的時候,要傳結構體的地址。
位段
位段的宣告和結構體是類似的,有兩個不同:
1、位段的成員必須是int、unsigned int或signed int。
2、位段的成員名後邊有一個冒號和一個數字。
如:
struct A
{
int _a:2; //給_a分配2位
int _b:5; //給_b分配5位
int _c:10; //給_c分配10位
int _d:30; //給_d分配30位
};
A就是一個位段型別。那麼位段A的大小是多少呢 ?答案是8位元組。
分析一下:
同結構體相比,位段可以達到同樣的效果,可以很好地節省空間,但是有跨平臺的問題存在。
位段的跨平臺問題
- int位段被當成有符號數還是無符號數是不確定的。
- 位段中最大位的數目不能確定。(16位機器最大16,32位機器最大32,寫成27,在16位機器上會出現問題)
- 位段中的成員在記憶體中是從左向右分配,還是從右向左分配,標準尚未定義。
- 當一個結構包含兩個位段,第二個位段成員比較大, 無法容納於第一個位段剩餘的位時,是捨棄剩餘的位還是利用,這是不確定的。
列舉
列舉顧名思義就是一一列舉,把可能的取值一一列舉。比如我們現實生活中:
一週的星期一到星期日是有限的7天,可以一一列舉。
性別有:男、女、保密,也可以一一列舉。
月份有12個月,也可以一一列舉。
列舉型別的定義
如:
enum Day //星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex //性別
{
MALE,
FAMALE,
SECRET
};
以上定義的都是列舉型別,{}中的內容是列舉型別的可能取值,也叫列舉常量。這些可能取值都是有值得,預設從0開始一次遞增1,當然在定義的時候也可以賦初值。如:
enum Sex //性別
{
MALE=1,
FAMALE=2,
SECRET=4
};
列舉的優點
- 增加程式碼的可讀性和可維護性
- 和#define定義的識別符號比較列舉有型別檢查,更加嚴謹
- 防止了命名汙染
- 便與除錯
- 使用方便,一次可以定義多個常量
可以利用列舉型別實現一個狀態機來完成C註釋到C++註釋的轉換。
聯合(共用體)
聯合也是一種特殊的自定義型別,這種型別定義的變數也包含一系列的成員,特徵是這些成員共用同一塊空間(所以聯合也叫共用體)。
如:
//聯合型別的宣告
union Un
{
char c;
int i;
};
//聯合型別的定義
union Un un;
//計算聯合的大小
printf("%d\n",sizeof(union Un));
程式的輸出結果為4。分析一下:
聯合大小的計算
- 聯合的大小至少是最大成員的大小。
- 當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍。
例如:
union Un1
{
char c[5]; //對齊數為1,大小為5
int i; //對齊數為4,大小為4
};
//Un1的大小為8,因為最大成員大小為5,不是最大對齊數的整數倍,要對齊,所以為8
union Un2
{
short c[7]; //對齊數為2,大小為14
int i; //對齊數為4,大小為4
};
//Un2的大小為16,因為最大成員大小為14,不是最大對齊數的整數倍,要對齊,所以為16
大小端問題
判斷當前計算機的大小端儲存。
大端:大端位元組序儲存,是指資料的低位儲存在記憶體的高地址中,資料的高位儲存在記憶體的低地址中。
小端:小端位元組序儲存,是指資料的低位儲存在記憶體的低地址中,資料的高位儲存在記憶體的高地址中。
根據聯合的優點,可寫出程式碼如下:
#include <stdio.h>
int check_sys()
{
union Un
{
char c;
int i;
}un;
un.i = 1;
return un.c;
}
int main()
{
int ret = 0;
ret = check_sys();
if(ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
聯合和結構體巧妙使用
將long型別的IP地址,轉換為點分十進位制的表示形式。
程式碼如下:
#include <stdio.h>
union ip_addr
{
unsigned long addr;
struct
{
unsigned char c1;
unsigned char c2;
unsigned char c3;
unsigned char c4;
}ip;
};
int main()
{
union ip_addr my_ip;
my_ip.addr = 19216801;
printf("%d.%d.%d.%d\n",my_ip.ip.c1,my_ip.ip.c2,my_ip.ip.c3,my_ip.ip.c4);
return 0;
}
以上內容均為學習過程中的總結,如有不足之處,請指正。