C 語言自學攻略-enum、struct、union 與 typedef 的深入探討

更新於 發佈於 閱讀時間約 17 分鐘

本節我們來講一些資料型別的使用,涵蓋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中,以redgreenblue來代替單純使用數字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;

圖示:

raw-image

存取方式:

  1. 一般方式
(*ptr).year = 2005;
(*ptr).month = 10;
(*ptr).day = 17;
  1. 使用->
ptr -> year = 2005;
ptr -> month = 10;
ptr -> day = 17;

typedef 的使用

利用以下方式宣告結構,我們還是稍嫌麻煩,所以我們使用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(指資料放進記憶體中的時候,最高位的位元組會放在最低的記憶體位址上)。

請看圖示:

raw-image

現在下面有一段程式,請觀察一下這是哪一種位元組順序,並觀察數值覆蓋的情況:

#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
*/

本次教學到這邊結束,另外,讀者也可以回顧我在指標那一章節所畫的圖示,觀察一下那張圖是哪一種位元組順序。

在當今數位時代,電資領域人才需求爆發式成長,不論是前端網頁設計、嵌入式開發、人工智慧、物聯網還是軟硬體整合,這些技術都在改變世界。而掌握 C/C++、Python、數位邏輯、電路學與嵌入式開發等大學電資領域的課程,正是進入這個高薪、高需求產業的關鍵!
留言
avatar-img
留言分享你的想法!

































































這篇文章深入淺出地解釋了 C 語言中指標陣列的概念,並透過範例程式碼和練習題幫助讀者理解指向指標的指標(char **)、指標陣列(char *[])等等的觀念與用法。文章還包含了指標常數(char * const)與常數指標(const char * const)的練習題,提升讀者的學習成效。
本章將介紹 C 語言的字串指標,讓讀者對於此概念有正確的理解。
本章將介紹 C 語言的字元陣列 (char array[]) 與字串函式 (string.h),這是處理 文字資料 的核心技術。C 語言以 字元陣列或指標 (char *) 來表示字串,並透過 標準函式 (strlen(), strcpy(), strcat(), strcmp()) 進行字串操作。
本章將深入探討 C 語言的三維陣列與指標,這是更難理解的概念,不過掌握到核心,其實並不會太複雜。 本文詳細剖析這一主題並給出實際執行範例,在搭配演示下,將更容易學習這一進階知識與與其使用方式。
本章將深入探討 C 語言中的二維陣列與指標,這是很多人學習C語言上會遇到的難題。也是大學教授喜歡考的內容,更是以後進階主題與應用的基石,二維陣列的元素在記憶體中是連續存放的,而指標可以靈活存取這些元素,提供更高效的操作方式。本章將詳細帶你學會如何使用,提升你的 C 語言開發能力。
這篇文章探討 C 語言中 sizeof 運算符在不同情況下的輸出結果,包括陣列、指標以及指標運算。文章詳細解釋了每個例子中 sizeof 運算符的行為,並說明瞭陣列名稱在不同語境下的含義。
這篇文章深入淺出地解釋了 C 語言中指標陣列的概念,並透過範例程式碼和練習題幫助讀者理解指向指標的指標(char **)、指標陣列(char *[])等等的觀念與用法。文章還包含了指標常數(char * const)與常數指標(const char * const)的練習題,提升讀者的學習成效。
本章將介紹 C 語言的字串指標,讓讀者對於此概念有正確的理解。
本章將介紹 C 語言的字元陣列 (char array[]) 與字串函式 (string.h),這是處理 文字資料 的核心技術。C 語言以 字元陣列或指標 (char *) 來表示字串,並透過 標準函式 (strlen(), strcpy(), strcat(), strcmp()) 進行字串操作。
本章將深入探討 C 語言的三維陣列與指標,這是更難理解的概念,不過掌握到核心,其實並不會太複雜。 本文詳細剖析這一主題並給出實際執行範例,在搭配演示下,將更容易學習這一進階知識與與其使用方式。
本章將深入探討 C 語言中的二維陣列與指標,這是很多人學習C語言上會遇到的難題。也是大學教授喜歡考的內容,更是以後進階主題與應用的基石,二維陣列的元素在記憶體中是連續存放的,而指標可以靈活存取這些元素,提供更高效的操作方式。本章將詳細帶你學會如何使用,提升你的 C 語言開發能力。
這篇文章探討 C 語言中 sizeof 運算符在不同情況下的輸出結果,包括陣列、指標以及指標運算。文章詳細解釋了每個例子中 sizeof 運算符的行為,並說明瞭陣列名稱在不同語境下的含義。
你可能也想看
Google News 追蹤
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
內容涵蓋資料型別、型別轉換、自訂型別、元組型別、集合型別和字典型別等主題。文章首先詳述內建型別如bool、byte、char等的定義和使用,接著討論型別轉換,包括隱含轉換和明確轉換。之後文章介紹自訂型別的建立,以及元組、集合、陣列和字典型別的操作與例子。
Thumbnail
C#程式由一或多個檔案組成,包含命名空間、類別、結構、介面、列舉和委派等型別。Main方法是C#應用程式的進入點。在C#中,註解用於在程式碼中添加說明,有單行和多行兩種類型。變數的定義需要指定變數的類型和名稱,可以一次為多個變數賦值。
Thumbnail
C#是一種開源、跨平台、面向對象的編程語言,具有類型安全、泛型、模式匹配等特性。廣泛應用於桌面和Web應用程序、遊戲開發、移動應用、雲計算等領域。全球數十萬家公司像微軟、Unity Technologies、Stack Overflow等使用C#支持其業務。C#還提供豐富的進階學習資源和主題。
Thumbnail
完成了Debug.log()的測試,接著還是要跟各位簡單講一下C#的一些規則,之後看程式會(比較)看得懂。 又講到變數? 在Unity中,變數是重要的工具,用來儲存和管理資料。讓開發者能夠靈活調整遊戲的行為和性能,減少代碼的重複性,使得遊戲開發更加高效和簡潔。透過使用變數,開發者可以輕鬆修改資料
Thumbnail
介紹C++ 語法 資料型態,架構說明 程式語言為人類與電腦溝通的工具 程式設計流程: 定義問題 -> 問題分析 -> 撰寫演算法 ->程式撰寫 -> 程式執行及維護
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
內容涵蓋資料型別、型別轉換、自訂型別、元組型別、集合型別和字典型別等主題。文章首先詳述內建型別如bool、byte、char等的定義和使用,接著討論型別轉換,包括隱含轉換和明確轉換。之後文章介紹自訂型別的建立,以及元組、集合、陣列和字典型別的操作與例子。
Thumbnail
C#程式由一或多個檔案組成,包含命名空間、類別、結構、介面、列舉和委派等型別。Main方法是C#應用程式的進入點。在C#中,註解用於在程式碼中添加說明,有單行和多行兩種類型。變數的定義需要指定變數的類型和名稱,可以一次為多個變數賦值。
Thumbnail
C#是一種開源、跨平台、面向對象的編程語言,具有類型安全、泛型、模式匹配等特性。廣泛應用於桌面和Web應用程序、遊戲開發、移動應用、雲計算等領域。全球數十萬家公司像微軟、Unity Technologies、Stack Overflow等使用C#支持其業務。C#還提供豐富的進階學習資源和主題。
Thumbnail
完成了Debug.log()的測試,接著還是要跟各位簡單講一下C#的一些規則,之後看程式會(比較)看得懂。 又講到變數? 在Unity中,變數是重要的工具,用來儲存和管理資料。讓開發者能夠靈活調整遊戲的行為和性能,減少代碼的重複性,使得遊戲開發更加高效和簡潔。透過使用變數,開發者可以輕鬆修改資料
Thumbnail
介紹C++ 語法 資料型態,架構說明 程式語言為人類與電腦溝通的工具 程式設計流程: 定義問題 -> 問題分析 -> 撰寫演算法 ->程式撰寫 -> 程式執行及維護
函式跟資料結構一樣都有類型,它不只是特定於函式的概念,而是跟int, tuple<bool,float>等類型同等的概念。在c++函式的類型可以寫做如std::function<int(float,float)>,它可以放在tuple, array等容器裡,當然也可以作為函式的參數或是傳回值,如st