本節我們來講一些資料型別的使用,涵蓋enum
, struct
, union
, 以及一個好用的搭配關鍵字typedef
讓命名更方便、程式碼更易讀。
enum
enum 裡面是一組具名的的整數常數,透過enum 具名的整數常數來表達數值,適合用來定義有意義的狀態。
enum action{Up, Down, Left, Right, Stop}; //預設第一個具名的常數即Up為數字0,後面的常數為Down=1, Left=2, Right=3, Stop=4
若要宣告並定義一個enum
型別的變數,語法如下:
action act(變數名稱) = Up;
或者是一開始宣告完enum
後直接添加變數名稱,語法如下:
enum action2{Up2=1, Down2, Left2, Right2, Stop2}act2;
act2 = Stop;
若是像下面這樣,裡面的數值常數為 {0, 1, 100, 101, 102}
enum action2{Up2, Down2, Left2=100, Right2, Stop2}act2;
act2 = Stop;
接著就可以輸出數值測試了:
#include <stdio.h>
int main() {
// 定義 action 枚舉,名稱不變
enum action {Up, Down, Left, Right, Stop};
enum action act = Up; // 使用 action 中的 Up
// 改變 action2 中的枚舉名稱,避免與 action 中的名稱重複
enum action2 {Up2=1, Down2, Left2, Right2, Stop2}act2;
act2 = Stop2; // 使用 action2 中的 Stop2
printf("act = %d\n", act); // 輸出 Up (0)
printf("act2 = %d\n", act2); // 輸出 Stop2 (5)
return 0;
}
#include<stdio.h>
int main(){
// 定義了顏色列舉類型 (red = 1, green = 2, blue = 3)
enum color { red=1, green, blue };
// 宣告一個變數來儲存使用者選擇的顏色
enum color favorite_color;
// 宣告一個無號整數變數,用來暫存輸入的選項值
unsigned int ui;
// 提示使用者選擇顏色
printf("Color you like: (1. red, 2. green, 3. blue)?\n");
//scanf("%u",&favorite_color); // It's not recommoned
// 使用 scanf 讀取整數輸入到 ui
scanf("%u", &ui);
// 將使用者的輸入轉換為列舉類型
favorite_color = (enum color)ui;
// 再使用 switch 來判斷選擇的顏色
switch (favorite_color)
{
case red:
// 如果選擇的是 red (索引 0)
printf("What you like is red.\n");
break;
case green:
// 如果選擇的是 green (索引 1)
printf("What you like is green.\n");
break;
case blue:
// 如果選擇的是 blue (索引 2)
printf("What you like is blue.\n");
break;
default:
// 輸入的選項無效 (非 1, 2, 3)
printf("Invalid choice. Please enter 1, 2, or 3.\n");
break;
}
// 結束程式
return 0;
}
switch
中,以red、green、blue來代替單純使用數字1、2、3,這樣顯得更視覺化。struct
struct 是一種內含不同型別的數據結構,適合用來描述複合數據,例如一個學生包含了姓名、年紀、學分數、平均成績等資訊。
struct 結構名稱{
成員1;
成員2;
...
};
struct student{
int id;
char name[10];
};
宣告後建立變數:
struct 結構名稱 結構變數名稱;
struct student s1;
可以同時指定成員的初始值:
struct student s1 = {136, "Michael"} //利用{}賦值
也可以:
struct student{
int id;
char name[10];
}s1 = {136, "Michael"};
使用.
存取結構成員:
s1.id = 137
strcpy(s1.name, "Bob") //s1.name = "Bob"不行喔
不難理解,就是一個陣列裡面,其元素是結構,語法如下:
struct student students[3];
存取方式如下:
//假設三位學生都有了資料,現在要輸出學生們的id
for (int i = 0; i<3; i++)
{
printf("學生%d的id是%d",i+1,students[i]);
}
語法如下:
struct data{
int year;
int month;
int day;
};
struct data hisBirthday;
struct data *ptr;
ptr = &hisBirthday;
圖示:
存取方式:
(*ptr).year = 2005;
(*ptr).month = 10;
(*ptr).day = 17;
->
ptr -> year = 2005;
ptr -> month = 10;
ptr -> day = 17;
利用以下方式宣告結構,我們還是稍嫌麻煩,所以我們使用typedef 簡化。
struct student students[3];
使用 typedef
簡化:
typedef struct {
int id;
char name[50];
} Student;
//接著我們就可以這樣宣告
Student students[3]; // 更簡潔
如果利用typedef
簡化結構指標的名稱,方法如下 :
typedef struct {
int id;
char name[50];
} *Studentptr;
//Student是結構指標的別名。
範例 :
#include <stdio.h>
#include <string.h>
// 使用 typedef 簡化結構體和指標名稱
typedef struct Student {
int id;
char name[10];
} Student, *StudentPtr;
int main() {
// 使用簡化後的結構體型別
Student s1 = {101, "Alice"};
printf("ID: %d, Name: %s\n", s1.id, s1.name);
// 使用簡化後的指標型別
StudentPtr sptr = &s1;
// 透過指標操作結構體
sptr->id = 102;
strcpy(sptr->name,"Bob");
printf("ID: %d, Name: %s\n", sptr->id, sptr->name);
return 0;
}
請你猜一下底下這個結構的記憶體空間為多少?
#include <stdio.h>
struct Example {
char a; // 1 byte
int b; // 4 bytes
char c; // 1 byte
};
int main() {
printf("Size of struct Example: %lu\n", sizeof(struct Example));
return 0;
}
你可能會說是1+4+1等於6Bytes,但實際上輸出會是12Bytes,為什麼呢?
int
最大,所以結構內的記憶體,通常要求 4 字節對齊,即地址必須是 4 的倍數)。所以上面程式碼實際上會輸出1+3(填充字節)+4+1+3(填充字節)。總共等於12。你可能會說,那這樣不就浪費了記憶體空間了嗎? 所以為了改善這點,我們要改變排序的策略:
先說結論: 將變數由小到大或由大到小擺放,可以改善這個情況,減少記憶體浪費。請看以下程式碼:
#include <stdio.h>
struct Example {
char a; // 1 byte
char c; // 1 byte
int b; // 4 bytes
};
int main() {
printf("Size of struct Example: %lu\n", sizeof(struct Example));
return 0;
}
這次輸出變為了8Bytes
,為什麼呢?
因為現在其記憶體空間變為1+1+2(填充字節)+4,總共等於8。讀者可以記住這個原則,將變數排序,減少記憶體空間的浪費。
union
union 是一種節省空間的數據結構,變數共享相同的記憶體空間。
union UnionName {
int field1;
float field2;
char field3[20];
};
特性:
範例:
#include <stdio.h>
#include <string.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
printf("%d\n",sizeof(data)); // 輸出20,總共20Bytes共用
data.i = 10; // 前4Bytes被使用,賦值為10
printf("Integer: %d\n", data.i);
data.f = 220.5; // 前4Bytes被使用,賦值為220.5
printf("Float: %.2f\n", data.f);
strcpy(data.str,"Hello"); // 前5Bytes被使用,賦值為Hello
printf("String: %s\n", data.str);
return 0;
}
注意:data.i
的值會被覆蓋,因為共用體共用內存。
這裡又不得不提union
在記憶體中的樣子了,不過這裡我們要先談談位元組順序,位元組順序是指資料在記憶體中的放置順序,不同的 CPU 可能會採用不同的放置規則,若遇到需要在不同機器或是網路之間交換低階的二進位資料時,就必須注意這個問題。
而現今主流的CPU中,最常見的位元放置順序有兩種,分別為Little-Endian(指把最高位的位元組放在最高的記憶體位址上。)和Big-Endian(指資料放進記憶體中的時候,最高位的位元組會放在最低的記憶體位址上)。
請看圖示:
現在下面有一段程式,請觀察一下這是哪一種位元組順序,並觀察數值覆蓋的情況:
#include <stdio.h>
int main()
{
// 定義一個 union,內部包含一個 4-byte 的整數和一個陣列
union tag {
int a; // 4 bytes 整數
char b[4]; // 字元陣列
} data; // 宣告一個名為 data 的 union 變數
// 將整數欄位初始化為 0x12345678 (十六進位表示法)
data.a = 0x12345678;
// 輸出 union 的大小,以及各欄位的大小
printf("%d\n", sizeof(data)); // union 的大小,取最大成員的大小
printf("%d\n", sizeof(data.a)); // 整數欄位的大小
printf("%d\n", sizeof(data.b)); // 字元陣列的大小
printf("\n");
// 輸出整數欄位和第一個字元的值,觀察記憶體儲存順序 (little endian)
printf("0x%x\n", data.a); // 輸出整數 a 的值
printf("0x%x\n", data.b[0]); // 輸出 b[0] 的值 (低位元組)
printf("\n");
// 輸出整個整數的值,並分別輸出每一個位元組的值
printf("0x%x\n", data.a); // 顯示整數 a
printf("0x%x\n", data.b[0]); // 第一個位元組 (最低位)
printf("0x%x\n", data.b[1]); // 第二個位元組
printf("0x%x\n", data.b[2]); // 第三個位元組
printf("0x%x\n", data.b[3]); // 第四個位元組 (最高位)
printf("\n");
// 修改 b[0] 的值,觀察對整個整數的影響
data.b[0] = 0x99; // 修改最低位元組為 0x99
printf("0x%x\n", data.a); // 再次輸出整數 a,觀察修改後的變化
data.b[1] = 0x99; // 修改第二個位元組為 0x99
printf("0x%x\n", data.a); // 再次輸出整數 a,觀察修改後的變化
data.b[2] = 0x99; // 修改第三個位元組為 0x99
printf("0x%x\n", data.a); // 再次輸出整數 a,觀察修改後的變化
data.b[3] = 0x99; // 修改第四個位元組為 0x99
printf("0x%x\n", data.a); // 再次輸出整數 a,觀察修改後的變化
return 0;
}
/* 輸出
4
4
4
0x12345678
0x78
0x12345678
0x78
0x56
0x34
0x12
0x12345699
0x12349999
0x12999999
0x99999999
*/
本次教學到這邊結束,另外,讀者也可以回顧我在指標那一章節所畫的圖示,觀察一下那張圖是哪一種位元組順序。