第十八講:聯合和枚舉
- 1.聯合體(共用體)
- 1.1聯合體的聲明
- 1.2聯合體大小的計算
- 1.3聯合體的特點
- 1.4聯合體的使用
- 1.4.1聯合體的直接使用
- 1.4.2聯合體直接使用的優化方法
- 1.4.3聯合體成員中含有數組的使用
- 1.4.4使用聯合體判斷當前機器是大端排序,還是小端排序
- 1.5聯合體的具體使用例子
- 2.枚舉類型
- 2.1枚舉類型的聲明
- 2.2枚舉成員值的討論
- 2.2.1原順序
- 2.2.2自己進行賦值
- 2.3枚舉的優點
- 2.4枚舉類型的使用
- 2.4.1使用1
- 2.4.2使用2
1.聯合體(共用體)
1.1聯合體的聲明
聯合體和結構體相似,聯合體也是由一個或多個成員構成,這些成員可以是不同的類型,但是與結構體不同的是,聯合體只為最大的成員分配足夠的內存空間,而聯合體的成員公用同一塊內存空間,所以聯合體也叫共用體,聯合體的聲明如下:
//1.1聯合體的聲明
//聯合體的聲明與結構體類似
union Book
{char name[10];int id;
};int main()
{//聯合體變量的定義union Book b1 = { 0 };return 0;
}
1.2聯合體大小的計算
對于下面的一串代碼,聯合體變量b1的大小是多少,為什么:
union Book
{char name;int id;
};int main()
{//聯合體變量的定義union Book b1 = { 0 };//聯合體變量的大小printf("%zd\n", sizeof(b1));return 0;
}
b1的大小為4,這就是因為:
1.聯合體的大小至少是最大成員的大小
2.當最大成員的大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍
例如:
//計算聯合體的大小
union Un1
{char c[5];//對齊數:1//占五個字節int i;//對齊數:4//占4個字節//所以最大為5個字節,不是4的整數倍,補齊,解果為8
};
union Un2
{short c[7];//14個字節int i;//4個字節//一共為14個字節,不是4的倍數,補齊為16
};
int main()
{printf("%zd\n", sizeof(union Un1));//8printf("%zd\n", sizeof(union Un2));//16return 0;
}
1.3聯合體的特點
既然聯合體的成員共用同一塊內存空間,那我們不妨來分析一下:
//聯合類型的聲明
union Un
{char c;int i;
};
int main()
{//聯合變量的定義union Un un = { 0 };printf("%p\n", &(un.i));printf("%p\n", &(un.c));printf("%p\n", &un);return 0;
}
它們的結果為:
它們的地址是相同的,所以它們指向同一塊內存空間的地址,那既然指向同一塊空間的地址,對于其中的一個成員進行賦值,其他成員會受到影響嗎:
//聯合類型的聲明
union Un
{char c;int i;
};
int main()
{//聯合變量的定義union Un un = { 0 };un.i = 0x11223344;//先給i賦值,內存中存儲的是:44332211(小端排序)un.c = 0x55;//給c賦值,此時內存中存儲的是:55332211,顯然會影響i的值1printf("%x\n", un.i);//i在內存中的存儲被改變為:55332211,所以輸出的結果為11223355return 0;
}
我們可以畫圖來理解:
1.4聯合體的使用
1.4.1聯合體的直接使用
//1.4.1聯合體的直接使用
union Score
{int score1;int score2;
};int main()
{//因為score1和score2占用同一塊內存空間,所以在使用時盡量一個一個進行使用:union Score s1;s1.score1 = 20;//使用score1...(例如:進行打印)printf("%d\n", s1.score1);//使用完s1之后,在使用s2:s1.score2 = 30;//使用score2...(例如:進行打印)printf("%d\n", s1.score2);//這樣才能正確使用score1和score2,否則在使用score1時對score2進行操作很可能改變score1的值return 0;
}
1.4.2聯合體直接使用的優化方法
上面的代碼中,如果想要在一定時間內多次使用多種成員時,無法實現,所以我們探討解決方法:
//1.4.2聯合體直接使用的優化方法
union Score
{int score1;int score2;
};int main()
{union Score s1;s1.score1 = 20;//可以先創建一個保存score1值的變量int pscore1 = s1.score1;//這時我們就可以使用score2了:s1.score2 = 30;//再創建一個保存score2值的變量int pscore2 = s1.score2;//這時想要使用score1,就要將pscore1的值再傳給score1:s1.score1 = pscore1;//使用score1...(例如:進行打印)printf("%d\n", s1.score1);return 0;
}
1.4.3聯合體成員中含有數組的使用
當聯合體成員中包含數組時,情況就有所不同:
如果我們這樣進行賦值的話,可以看到它報錯了,正確的使用方法如下:
- 使用strcpy函數
union Book
{char name[20];int price;
};int main()
{union Book b1;//先確定書的價錢b1.price = 20;//再確定書的名字//先對結構體進行清空memset(&b1, 0, sizeof(b1));//再使用strcpy函數進行賦值strcpy(b1.name, "tangjiasnashao");return 0;
}
- 使用循環
union Book
{char name[20];int price;
};int main()
{union Book b1;//先確定書的價錢b1.price = 20;//再確定書的名字memset(&b1, 0, 20);const char arr[20] = "tangjiasnashao";for (int i = 0; i < strlen(arr); i++)b1.name[i] = arr[i];return 0;
}
- 使用標準I/O函數
union Book
{char name[20];int price;
};int main()
{union Book b1;//先確定書的價錢b1.price = 20;//再確定書的名字memset(&b1, 0, 20);scanf("%s", b1.name);return 0;
}
使用fegts函數時換行符也會被存儲,需要額外處理:
union Book
{char name[20];int price;
};int main()
{union Book b1;//先確定書的價錢b1.price = 20;//再確定書的名字memset(&b1, 0, 20);fgets(b1.name, 10, stdin);return 0;
}
1.4.4使用聯合體判斷當前機器是大端排序,還是小端排序
//1.4.4使用聯合體判斷當前機器是大端排序,還是小端排序
union Test
{char a;int b;
};int main()
{union Test t1;t1.b = 1;if (t1.a == 0)printf("大端排序\n");if(t1.a == 1)printf("小端排序\n");return 0;
}
1.5聯合體的具體使用例子
看到聯合體的使用如此繁瑣,我們是不是感覺:還沒有結構體好用,那么就讓我們看一下聯合體的具體使用:
?如,我們要搞?個活動,要上線?個禮品兌換單,禮品兌換單中有三種商品:圖書、杯?、襯衫。
每?種商品都有:庫存量、價格、商品類型和商品類型相關的其他信息:
我們對它們進行分析:
struct gift_list
{//公共屬性int stock_number;//庫存量double price; //定價int item_type;//商品類型//特殊屬性,也就是單獨屬性char title[20];//書名char author[20];//作者int num_pages;//?數char design[30];//設計int colors;//顏?int sizes;//尺?
};
如果我們直接創建結構體的話,會占用很多內存,所以我們可以這樣使用:
struct gift_list
{int stock_number;//庫存量double price; //定價int item_type;//商品類型union {struct{char title[20];//書名char author[20];//作者int num_pages;//?數}book;struct{char design[30];//設計}mug;struct{char design[30];//設計int colors;//顏?int sizes;//尺?}shirt;}item;
};
2.枚舉類型
2.1枚舉類型的聲明
枚舉其實就是??列舉
比如:
1.?周的星期?到星期?是有限的7天,可以??列舉
2.性別有:男、?、保密,也可以??列舉
3.?份有12個?,也可以??列舉
4.三原?,也是可以??列舉
這些數據的表示就可以使用枚舉:
//枚舉的聲明
enum Day //星期
{Mon, //每個成員后邊需要加逗號Tues,Wed,Thur,Fri,Sat,Sun //最后一個成員后邊不用加逗號
};enum Sex//性別
{MALE,FEMALE,SECRET
};enum Color//顏?
{RED,GREEN,BLUE
};
2.2枚舉成員值的討論
對于枚舉成員,它們其實是有值的,我們現在對它們的值進行討論:
2.2.1原順序
//2.2.1原順序
enum Day //星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};int main()
{printf("%d\n", Mon); //0printf("%d\n", Tues); //1printf("%d\n", Wed); //2printf("%d\n", Thur);//3printf("%d\n", Fri); //4printf("%d\n", Sat); //5printf("%d\n", Sun); //6return 0;
}
可見它們的值是從0開始,往后加的
如果我們想要自己給枚舉成員賦值,也是可行的,但是只能在創建枚舉類型時進行賦值,不能在main函數或其他函數中進行更改
2.2.2自己進行賦值
//2.2.2情況1
enum Day //星期
{Mon,Tues,Wed = 7,Thur,Fri,Sat,Sun
};int main()
{printf("%d\n", Mon); //0printf("%d\n", Tues); //1printf("%d\n", Wed); //7printf("%d\n", Thur);//8printf("%d\n", Fri); //9printf("%d\n", Sat); //10printf("%d\n", Sun); //11return 0;
}
2.3枚舉的優點
當然我們可以使用define定義常量,那為什么要使用枚舉呢
枚舉的優點:
- 增加代碼的可讀性和可維護性
- 和#define定義的標識符比較,枚舉有類型檢查,更加嚴謹。
- 便于調試,預處理階段會刪除 #define 定義的符號
- 使??便,?次可以定義多個常量
- 枚舉常量是遵循作?域規則的,枚舉聲明在函數內,只能在函數內使?
2.4枚舉類型的使用
2.4.1使用1
enum Color//顏?
{RED=1,GREEN=2,BLUE=4
};int main()
{enum Color clr = GREEN;//使?枚舉常量給枚舉變量賦值//后續可以通過這個值進行判斷,比如:if (clr == GREEN)printf("YES");return 0;
}
2.4.2使用2
比如當我們想要寫一個簡單的計算器時,會這樣寫:
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("請選擇:");scanf("%d", &input);switch (input){case 1:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("輸?操作數:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("選擇錯誤\n");break;}} while (input);return 0;
}
這樣寫可讀性低,此時我們可以使用枚舉: