結構體
1.如何定義和使用結構體指針?
1.結構體指針的定義
首先需要定義結構體類型,例如表示學生信息的結構體:
struct Student {char name[50];int age;float score;
};
接著,使用struct 關鍵字和指針符號* 聲明結構體指針:
struct Student *pStudent; // 聲明指向Student結構體的指針)
2.結構體指針的初始化
1.可以指向現有結構體變量:
struct Student stu1 = {"Alice", 20, 90.5};
struct Student *pStu = &stu1; // 指針指向stu1的地址)
2.使用malloc 在堆上分配內存:
struct Student *pStu = (struct Student*)malloc(sizeof(struct Student));
if (pStu != NULL)
{pStu->age = 22; // 通過指針初始化成員strcpy(pStu->name, "Bob");
}
// 使用后需手動釋放內存
free(pStu); // 避免內存泄漏)
3.通過指針訪問結構體成員
1.箭頭運算符->
printf("姓名:%s\n", pStu->name); // 輸出:Bob)
2.解引用與點運算符結合
printf("年齡:%d\n", (*pStu).age); // 輸出:22)
4.結構體指針的應用場景
1.函數參數傳遞
傳遞指針可以避免復制大型結構體,提升效率:
void updateScore(struct Student *s, float newScore)
{s->score = newScore; // 直接修改原結構體成員)
}
2.動態數據結構
用于構建鏈表、樹等動態結構:
struct Node {int data;struct Node *next; // 指向下一個節點的指針)
};
3.遍歷結構體數組
通過指針操作數組元素:
struct Student stus[3] = {{"Tom", 18, 85.5}, ...};
struct Student *p = stus;
for (int i = 0; i < 3; i++)
{printf("%s的分數:%.1f\n", p->name, p->score);p++; // 指針移動到下一個元素)
}
2. 如何將結構體作為函數參數?
主要有三種方式:值傳遞、指針傳遞和數組傳遞。
1.結構體值傳遞
將結構體變量作為參數直接傳遞給函數。函數會復制整個結構體的副本。
優點:函數內部對結構體的修改不會影響外部變量。
缺點:如果結構體較大,會比較耗時間。
// 定義結構體
typedef struct {int id;char name[20];
} Student;// 值傳遞:函數接收結構體副本
void printStudent(Student stu)
{printf("ID: %d, Name: %s\n", stu.id, stu.name);
}int main()
{Student tom = {1, "Tom"};printStudent(tom); // 傳遞結構體變量return 0;
}
2.結構體指針傳遞
將結構體變量的地址作為參數傳遞給函數,函數通過指針操作結構體。
優點:無需復制整個結構體,因此效率高,可在函數中修改原始結構體的值。
缺點:需要使用指針語法->,增加代碼復雜度。
typedef struct {int id;char name[20];
} Student;// 指針傳遞:函數接收結構體指針
void updateStudent(Student *stu, int newId, char *newName)
{stu->id = newId; // 通過指針訪問成員(-> 操作符)strcpy(stu->name, newName);
}int main()
{Student tom = {1, "Tom"};updateStudent(&tom, 2, "Jerry"); // 傳遞結構體地址printf("ID: %d, Name: %s\n", tom.id, tom.name); // 輸出修改后的值return 0;
}
3.結構體數組傳遞
當函數參數是結構體數組時,可傳遞數組名。
typedef struct {int id;char name[20];
} Student;// 傳遞結構體數組(數組名作為指針)
void printStudents(Student students[], int count)
{for (int i = 0; i < count; i++) {printf("ID: %d, Name: %s\n", students[i].id, students[i].name);}
}int main()
{Student class[] = {{1, "Alice"},{2, "Bob"}};printStudents(class, 2); // 傳遞結構體數組return 0;
}
實際開發中,指針傳遞時最常用的方式,既能避免復制開銷,又能靈活操作結構體內容。
3. 結構體的內存對齊是什么?如何按指定字節對齊?
結構體的內存對齊是指編譯器在為結構體分配內存時,按照一定規則(如成員自身大小、指定對齊字數等)來調整成員的存儲位置,使得每個成員的地址滿足特定對齊要求的過程。
內存對齊的目的是提高內存訪問效率。
1.內存對齊的基本規則
1.成員對齊規則
結構體中的每個成員的偏移量(相對于結構體起始地址)必須是該成員類型大小的整數倍。
例如:
struct Example {char a; // 大小 1 字節,偏移量 0(0 是 1 的倍數)int b; // 大小 4 字節,偏移量需是 4 的倍數。由于前一個成員占 1 字節,下一個可用偏移量為 1,不是 4 的倍數,因此編譯器會在 char 后填充 3 字節,使 int 的偏移量為 4
}; // 結構體總大小為 8 字節(4 + 4,最后一個成員大小的整數倍)
2.結構體整體對齊規則
結構體的總大小必須是其成員中最大對齊數的整數倍。
2.按指定字節對齊的方法
1.使用#pragma pack()---------通用方法
// 設置對齊字節數為 n(n 通常為 1、2、4、8、16 等 2 的冪次)
#pragma pack(n) struct AlignedStruct {char a; // 偏移量 0(1 的倍數)double b; // 若 n=8,double 大小 8 字節,偏移量需是 8 的倍數。char 后需填充 7 字節,使 double 偏移量為 8
}; // 總大小為 16 字節(8 的倍數)#pragma pack() // 恢復默認對齊(或用 #pragma pack(pop))
2.GCC 編譯器 __attribute__((aligned(n)))
// 指定結構體按 n 字節對齊(n 需是 2 的冪次)
struct AlignedStruct __attribute__((aligned(8))) {char a;double b; // 偏移量 8(8 的倍數)
};
共用體
1.共用體和結構體有什么區別?
1.內存分配方式
結構體的每個成員有獨立的內存空間,結構體的總大小是所有成員大小之和。
struct Data {int a; // 占 4 字節char b; // 占 1 字節(對齊后可能補 3 字節)double c; // 占 8 字節
}; // 總大小至少為 4 + 4(對齊) + 8 = 16 字節
共用體所有成員共享一塊內存空間,同一時刻只能有一個成員有效。共用體的總大小為其最大成員的大小。
union Data {int a; char b; double c;
}; // 總大小為 8 字節(取最大成員 `double` 的大小)
2.內存訪問特性
結構體可以同時訪問所有成員,每個成員的值獨立存儲。
struct Data var;
var.a = 10; // 合法
var.b = 'x'; // 合法
var.c = 3.14; // 合法
共用體同一時間只能訪問最后一次賦值的成員,訪問其他成員會導致數據錯誤(除非成員類型兼容)
union Data var;
var.a = 10; // 此時內存存儲 `int` 類型數據
printf("%d\n", var.a); // 合法,輸出 10
printf("%f\n", var.c); // 未定義行為(強行將 `int` 當作 `double` 解析)
3.初始化方式
結構體在定義時可以初始化所有成員:
struct Data var = {10, 'x', 3.14}; // 合法
共用體只能初始化第一個成員:
union Data var = {10}; // 合法,初始化 `a`
union Data var = {.c=3.14}; // C99 及以上支持指定成員初始化
4.典型用途
結構體用于存儲同時需要存在的多個相關數據,例如學生信息(姓名、年齡、成績),坐標點(x、y、z)等。
共用體用于節省內存,當數據在不同場景下以不同類型存在時(即互斥使用),例如表示一個變量可能是整數、字符、浮點數等,常見場景包括協議解析、內存共享等。
5.語法關鍵字
結構體用struct 關鍵字來定義:
struct 結構體名 { 成員列表; };
共用體用union 關鍵字來定義:
union 共用體名 { 成員列表; };
2. 共用體在內存中的存儲特點是什么?
1.內存共享空間
共用體的所有成員共享同一塊內存區域,該區域的起始地址相同。
例如:如果要定義一個包含int 和 char 成員的共用體,這兩個成員會從同一地址開始存儲。
union Data {int i;char c;
};
union Data d; // d.i 和 d.c 共享同一塊內存
2.內存大小由最大成員決定
共用體的內存占用空間等于其最大成員的大小(需考慮內存對齊)。例如:
union Example {char c; // 1 字節int i; // 4 字節(假設 int 為 4 字節)double dbl; // 8 字節
}; // 共用體大小為 8 字節(由 double 決定)
3.同一時間僅存儲一個成員的值
每次只能向共用體的一個成員賦值,后續賦值會覆蓋之前成員的數據。例如:
d.i = 100; // 此時內存中存儲 int 類型的 100
d.c = 'A'; // 此時內存中存儲 char 類型的 'A'(覆蓋之前的 int 數據)
4.內存對齊規則
共用體的內存對齊方式遵循其成員中對齊要求最嚴格的成員。例如:
若成員包含double ,則共用體的起始地址會按 8 字節對齊。
5.訪問成員的類型安全問題
由于共用體成員共享內存,訪問時必須明確當前存儲的是哪個成員的類型,否則會導致未定義行為,產生段錯誤。
3. 如何用共用體判斷大小端?
利用共用體所有成員共享內存的特性,
1.定義一個包含 int 類型和 char?數組的共用體
2.給 int 成員賦值為1
3.檢查char 數組的第一個字節:
? ? ? ? 小端模式下最低位字節存放在低地址,所以第一個字節是1
? ? ? ? 大端模式下最高位字節存放在低地址,所以第一個字節是0
程序運行后會直接輸出當前系統的字節序類型。
union EndianCheck {int num;char bytes[sizeof(int)];
};int main()
{union EndianCheck ec;ec.num = 1;if (ec.bytes[0] == 1) {printf("小端模式(Little-Endian)\n");} else {printf("大端模式(Big-Endian)\n");}return 0;
}
位域
1.什么是結構體位域?如何定義和使用位域?
結構體位域是一種允許在結構體中直接定義以位為單位的成員的機制,主要用于優化內存,比如處理寄存器配置。
定義:
位域通過在結構體成員聲明中使用? ? ? ? 類型? ?成員名? :? 位數
struct 結構體名 {類型 成員名1: 位數1; // 位域成員類型 成員名2: 位數2;// 普通成員(非位域)類型 普通成員名;
};
類型:必須是int 、 unsigned int? 、 signed? int??
位數:表示該成員占用的二級制位數,取值為正整數。
例如:定義一個表示顏色分量(RGB)的結構體,每個分量占5位(共15位,用unsigned int 存儲)
struct Color {unsigned int red: 5; // 紅色分量,占 5 位(0-31)unsigned int green: 5; // 綠色分量,占 5 位unsigned int blue: 5; // 藍色分量,占 5 位unsigned int alpha: 7; // 透明度,占 7 位(0-127),剩余 1 位未使用
};
使用:
1.聲明變量并賦值:
struct Color c;// 像普通結構體成員一樣賦值
c.red = 20; // 20 是合法值(20 < 32,未超過 5 位范圍)
c.green = 31; // 最大值 31(2^5 - 1)
c.blue = 0;
c.alpha = 127; // 最大值 127(2^7 - 1)
2.訪問位域成員:
通過結構體變量名 + 成員運算符 .? 直接訪問:
printf("Red: %u\n", c.red); // 輸出:20
printf("Alpha: %u\n", c.alpha); // 輸出:127
2. 位域的應用場景有哪些?
位域是一種允許在結構體中定義以位為單位的字段的特性,其核心優勢是節省內存空間并直接操作二進制位。
1.硬件寄存器映射
嵌入式設備的寄存器通常由多個獨立位段組成,通過位域可直接讀寫寄存器的特定位段,無需手動進行位運算(如& 、 |):
// 寄存器地址:0x40000000
// 位31-24:保留
// 位23-16:時鐘頻率選擇(8位)
// 位15-8:使能標志(1位有效,其余保留)
// 位7-0:數據長度(8位)
struct peripheral_reg {unsigned int reserved1:8; // 位31-24(保留)unsigned int clk_freq:8; // 位23-16(時鐘頻率)unsigned int enable:1; // 位15(使能標志,其余7位保留)unsigned int data_len:8; // 位7-0(數據長度)
};
volatile struct peripheral_reg *reg = (volatile struct peripheral_reg*)0x40000000;// 使用位域操作:
reg->enable = 1; // 使能設備
reg->clk_freq = 0b1010; // 設置時鐘頻率
不過,如果需要頻繁的對字段進行位運算(異或、移位),位域不如直接操作整數來得高效。