C語言:第19天筆記
內容提要
- 構造類型
- 結構體
- 共用體/聯合體
構造類型
數據類型
- 基本類型/基礎類型/簡單類型
- 整型
- 短整型:short – 2字節
- 基本整型:int – 4字節
- 長整型:long – 32位系統4字節/ 64位系統8字節
- 長長整型:long long 8字節(大多數現代機器,舊機器可能超過8字節),C99新增
- 注意:以上類型又分為signed(默認) 和 unsigned
- 浮點型
- 單精度型:float --4字節
- 雙精度型:double --8字節
- 長雙精度型:long double – 16字節(視平臺而定),C99新增
- 字符型: char --1字節
- 整型
- 指針類型
- 數據類型*:32位系統4字節,64位系統8字節
- void*:通用指針類型(萬能指針)32位系統4字節,64位系統8字節
- 空值類型
- void:無返回值,無形參(不能修飾變量)
- 構造類型(自定義類型)
- 結構體類型:struct
- 共用體/聯合體類型:union
- 枚舉類型:enum
結構體
結構體定義【定義類型】
-
**定義:**自定義數據類型的一種,關鍵字struct。
-
語法:
struct 結構體名 // 結構體名:自定義的數據類型名字 類似于int,double之類的 {數據類型1 成員名稱1; // 結構體中的變量叫做成員數據類型2 成員名稱2;... };
注意:結構體中定義的變量,稱之為成員變量(成員屬性)
-
格式說明:
- 結構體名:合法的標識符,建議首字母大寫(所謂的結構體名,就是自定義類型的類型名稱)
- 數據類型n:C語言支持的所有類型(包括函數,函數在這里用函數指針表示)
- 成員名稱n:合法的表示,就是變量的命名標準
- 數據類型n 成員名稱n:類似于定義變量,定義了結構體中的成員
-
注意:
-
結構體在定義的時候,成員不能賦值,示例:
struct Cat {int age = 5; // 錯誤,結構體定義的時候,并未在內存中申請空間,因此無法進行賦值double height; // 正確void (*run)(void);// 正確 };
-
-
常見的定義格式:
-
方式1:常規定義(命名結構體,只定義數據類型)
struct Student {int num; // 學號char name[20]; // 姓名char sex; // 性別int age; // 年齡char address[100]; // 籍貫void (*info)(void);// 信息輸出(函數指針) };
-
方式2:匿名結構體(常用于作為其他結構體的成員使用)
struct Dog // 命名結構體 {char *name; // 姓名int age; // 年齡struct // 匿名結構體{// 定義結構體時不能省略成員,否則編譯報錯int year; // 年int month;// 月int day; // 日} bithday; // 年齡,匿名結構體一定要提供成員名稱,否則無法訪問 };
注意:定義匿名結構體的同時必須定義結構體成員,否則編譯報錯;結構體可以作為另一個結構體的成員。
總結:
- 結構體可以定義在局部位置(函數作用域、塊作用域),也可以定義在全局位置(推薦,可以復用)
- 全局位置的結構體名和局部位置的結構體名可以相同,遵循就近原則(和變量的定義同理)
-
結構體類型的使用:
利用結構體類型定義變量、數組,也可以作為函數的返回值和參數;結構體類型的使用與基本數據類型的使用類似。
-
結構體變量定義【定義變量】
三種形式定義結構體變量
說明:結構體變量也被稱作結構體對象或者結構體實例。
-
第一種方式:
① 先定義結構體(定義數據類型)
② 然后定義結構體變量(定義變量)
示例:
// 定義結構體(定義數據類型) struct A {int a;char b; };// 定義結構體實例/變量(定義變量) struct A x; // A就是數據類型,x就是變量名 struct A y; // A就是數據類型,y就是變量名
-
第二種方式:
① 在定義結構體的同時,定義結構體變量(同時定義數據類型和變量)
語法:
struct 結構體名 {數據類型1 數據成員1;... } 變量列表;
示例:
struct A {int a;char b; } x, y; // A就是數據類型,x,y就是變量名
此時定義了一個結構體,x和y就是這個結構體類型的變量。
-
第三種方式:
① 在定義匿名結構體的同時,定義結構體變量。
示例:
struct {int a;char b; } x, y;
此時定義了一個沒有名字的結構體(匿名結構體),x,y是這個結構體類型的變量。
匿名結構體
- 優點:少寫一個結構體名稱
- 缺點:只能使用一次,定義結構體類型的同時必須定義變量。
- 應用場景:
- 當結構體的類型只需要使用一次,并且定義類型的同時定義了變量。
- 作為其他結構體的成員使用。
定義結構體同時變量初始化
說明:定義結構體的同時,定義結構體變量并初始化
struct Cat
{int age;char color[20];
} cat;
- 結構體成員部分初始化,大括號不能省略
- 結構體成員,沒有默認值,是隨機值,和局部作用域的變量一致。
案例:
#include <stdio.h>/*** 先定義結構體,再定義結構體變量(實例)*/
void fun1()
{// 定義結構體struct A{int a;char b;};// 定義結構體變量struct A x;struct A y;struct A x1,y1;}/*** 定義結構體的同時定義結構體變量*/
void fun2()
{struct A{int a;char b;} x,y;struct A z;struct A x1,y1;
}/*** 定義匿名結構體的同時定義結構體變量*/
void fun3()
{struct{int a;char b;} x,y;struct{int a;char b;} z;
}int main(int argc,char *argv[])
{fun1();fun2();fun3();return 0;
}
結構體變量的使用【變量使用】
結構體變量訪問成員
-
語法:
結構體變量名(實例名).成員名;
① 可以通過訪問成員進行賦值(存數據)
② 可以通過訪問成員進行取值(取數據)
-
結構體變量未初始化,結構體成員的值是隨機的(和局部作用域的變量和數組同理)
結構體變量定義時初始化成員
- 建議用大括號
{}
標明數據的范圍。 - 結構體成員初始化,可以部分初始化(和數組類似),部分初始化時一定要帶大括號標明數據范圍。未初始化的成員使用清零操作。
案例
#include <stdio.h>/** 定義全局的結構體,方便被多個函數訪問*/
struct Dog
{char *name; // 名字int age; // 年齡char sex; // 性別 M:公,W:母void (*eat)(void); // 吃狗糧
};void eat()
{printf("狗狗正在吃狗糧!\n");
}/*** 方式1:先定義,再初始化 ---> 結構體變量訪問成員*/
void fun1()
{// 定義結構體變量struct Dog dog;// 結構體變量成員賦值dog.name = "旺財";dog.age = 5;dog.sex = 'M';dog.eat = eat;// 結構體變量成員訪問printf("%s,%d,%c\n", dog.name, dog.age, dog.sex);// 訪問成員方法(函數)dog.eat();
}/*** 方式2:定義的同時初始化,給變量成員初始化*/
void fun2()
{// 定義結構體變量的同時,給變量成員初始化struct Dog d1 = {"旺財", 5, 'M', eat}; // 完整初始化,按照順序賦值struct Dog d2 = {.name = "萊德", .sex = 'M'}; // C99之后,支持部分成員初始化,未初始化的成員自動填充0struct Dog d3 = {"旺財"}; // C99之后,可以默認初始化第一個成員,其他成員用0填充// 結構體變量成員訪問printf("%s,%d,%c\n", d1.name, d1.age, d1.sex);printf("%s,%d,%c\n", d2.name, d2.age, d2.sex);printf("%s,%d,%c\n", d3.name, d3.age, d3.sex);}int main(int argc, char *argv[])
{fun1();printf("\n------------\n");fun2();return 0;
}
結構體數組的定義【數組定義】
什么時候需要結構體數組
比如:我們需要管理一個學生對象,只需要定義一個struct Student yifanjiao;
假如:我們需要管理一個班的學生對象,此時就需要定義一個結構體數組struct Student stus[50];
定義
存放結構體實例的數組,稱之為結構體數組。
四種形式定義結構體數組
-
第一種方式:
// 第一步:定義一個結構體數組 struct Student {char *name; // 姓名int age; // 年齡float scores[3]; // 三門課程的成績 } stus[3];// 第二步:賦值 stus[0].name = "張三"; stus[0].age = 21; stus[0].scores[0] = 89; stus[0].scores[1] = 99; stus[0].scores[2] = 87;stus[1].name = "李四"; stus[1].age = 22; stus[1].scores[0] = 66; stus[1].scores[1] = 77; stus[1].scores[2] = 88;
-
第二種方式:
// 第一步:定義一個學生結構體(定義數據類型) struct Student {char *name; // 姓名int age; // 年齡float scores[3]; // 三門課程的成績 };// 第二步:定義結構體實例(定義結構體實例) struct Student zhangsan = {"張三", 21, {89,99,87}}; struct Student lisi = {"李四", 22, {66,77,88}};// 第三步:定義結構體數組 struct Student stus[] = {zhangsan, lisi};
-
第三種方式:
// 第一步:定義一個學生結構體(定義數據類型) struct Student {char *name; // 姓名int age; // 年齡float scores[3]; // 三門課程的成績 };// 第二步:定義結構體數組并初始化成員 struct Student stus[] = {{"張三", 21, {89,99,87}},{"李四", 22, {66,77,88}} };
-
第四種方式:
// 第一步:定義一個結構體數組,并初始化 struct Student {char *name; // 姓名int age; // 年齡float scores[3]; // 三門課程的成績 } stus[] = {{"張三", 21, {89,99,87}},{"李四", 22, {66,77,88}} };
結構體數組的訪問【數組訪問】
語法:
結構體成員.成員名
結構體指針 -> 成員名 // -> 結構體指針成員訪問符
舉例:
// 方式1:結構體成員訪問
(*p).成員名
// 方式2:結構體指針訪問
p -> 成員名
案例:
#include <stdio.h>/* 定義全局的Student結構體 */
struct Student
{int id; // 編號char *name; // 姓名int age; // 年齡float scores[3]; // 三門課成績void (*info)(char*,int); // 信息輸出
};void info(char* name, int age)
{printf("大家好,我是%s,今年%d歲!\n", name, age);
}int main(int argc, char *argv[])
{// 定義結構體實例并初始化struct Student zhangsan = {1, "張三", 21, {78,88,98}, info};struct Student lisi = {2, "李四", 22, {90,98,91}, info};// lisi.info = info// 定義結構體數組并初始化struct Student stus[] = {zhangsan, lisi};// 計算數組的大小int len = sizeof(stus) / sizeof(stus[0]);// 用一個指針進行遍歷struct Student *p = stus;// 表格-表頭printf("序號\t姓名\t年齡\t語文\t數學\t英語\n");for (; p < stus + len; p++){// 結構體成員訪問,不推薦// printf("%d\t%s\t%d\t%.2f\t%.2f\t%.2f\t\n",(*p).id, (*p).name, (*p).age, (*p).scores[0], (*p).scores[1], (*p).scores[2]);// (*p).info((*p).name, (*p).age);// 結構體指針訪問,推薦printf("%d\t%s\t%d\t%.2f\t%.2f\t%.2f\t\n",p->id, p->name, p->age, p->scores[0], p->scores[1], p->scores[2]);// 函數調用p->info(p->name, p->age);}printf("\n");return 0;
}
結構體類型
結構體數組
案例
-
需求:對候選人得票的統計程序。設有3個候選人,每次輸入一個得票的候選人名字,要求最后輸出個人得票的結果。
-
案例:
#include <stdio.h> #include <string.h>#define LEN 3/* 定義一個候選人結構體 */ struct Person {char name[20]; // 名字int count; // 票數 };/*** 定義候選人數組,并初始化*/ struct Person persons[LEN] = {{"衛宇星", 0},{"焦藝凡", 0},{"楊家輝", 0} };int main(int argc, char *argv[]) {// 定義循環變量register int i, j;// 創建一個數組,用來接收控制臺錄入的候選人名字char leader_name[20];// 使用一個for循環,默認10個人參與投票printf("世紀美男投票系統!\n");for (i = 0; i < 10; i++){printf("請輸入您覺得最帥的那位哥哥的名字:\n");scanf("%s", leader_name);// 從候選人列表中匹配被投票的人 count++for (j = 0; j < LEN; j++){// 判斷兩個字符串是否相等 strcmpif (strcmp(leader_name, persons[j].name) == 0){persons[j].count++; // 票數+1}}}printf("\n");printf("\n投票結果:\n");struct Person *p = persons; // 指針p指向數組persons的第一個元素// 遍歷數組:使用指針變量來遍歷數組for (; p < persons + LEN; p++){printf(" %s:%d\n",p->name, p->count);}printf("\n");// 遍歷數組:使用指針來遍歷數組for (i = 0; i < LEN; i++){printf(" %s:%d\n",(persons + i)->name, (persons + i)->count);}printf("\n");// 遍歷數組:使用下標來遍歷數組for (i = 0; i < LEN; i++){printf(" %s:%d\n",persons[i].name, persons[i].count);}return 0; }
結構體指針
-
**定義:**指向結構體變量或者結構體數組的起始地址的指針叫做結構體指針。
-
語法:
struct 結構體名 *指針變量列表;
-
舉例:
#include <stdio.h>// 定義一個Dog結構體 struct Dog {char name[20];int age; };int main(int argc, char *argv[]) {// 創建Dog實例struct Dog dog = {"茍富貴", 5};// 基于結構體變量的結構體指針struct Dog *p = &dog;printf("%s,%d\n", p->name, p->age);// 創建Dog數組struct Dog dogs[] = {{"茍富貴", 5},{"勿相忘", 6}};// 基于結構體數組元素的結構體指針struct Dog *p1 = dogs;int len = sizeof(dogs)/sizeof(dogs[0]);for (; p1 < dogs + len ; p1++){printf("%s,%d\n", p1->name, p1->age);}return 0; }
結構體成員的訪問
結構體成員訪問
-
結構體數組名訪問結構體成員
-
語法:
結構體數組名 -> 成員名;(*結構體數組名).成員名; // 等價于上面寫法
-
舉例:
printf("%s:%d\n",persons->name, persons->count);
-
-
結構體成員訪問符
-
.
:左側是結構體變量,也可以叫做結構體對象訪問成員符,右側是結構體成員。 -
->
:左側是結構體指針,也可以叫做結構體指針訪問成員符,右側是結構體成員。 -
舉例:
struct Person *p = persons; // p就是結構體指針for(; p < persons + len; p++)printf("%s:%d\n",p->name,p->count);
-
-
訪問結構體成員有兩種類型,三種方式:
-
**類型1:**通過結構體變量(對象|示例)訪問成員
struct Stu{int id;char name[20];} stu; // 結構體變量// 訪問成員stu.name;
-
**類型2:**通過結構體指針訪問成員
-
**第1種:**指針引用訪問成員
struct Stu{int id;char name[20];} stu;struct Stu *p = &stu;// 指針引用訪問成員p -> name; // 等價于 (*p).name;
-
**第2種:**指針解引用間接訪問成員
struct Stu{int id;char name[20];} stu;struct Stu *p = &stu;// 指針引用訪問成員(*p).name; // 等價于 p -> name;
-
-
結構體數組中元素的訪問
// 結構體數組struct Stu{int id; // 編號(成員)char name[20]; // 名字(成員)float scores[3];// 三門成績(成員)} stus[3] = {{1,"張三",{90,89,78},{2,"李四",{90,88,78},{3,"王五",{77,89,78},};// 取數據 --- 下標法printf("%s,%.2f\n", stus[1].name, stus[1].scores[1]);// 李四 88// 取數據 --- 指針法printf("%s,%.2f\n", stus->name, stus->scores[2]);// 張三 78printf("%s,%.2f\n", (stus+1) -> name, (stus+1) -> scores[1]);// 李四 88printf("%s,%.2f\n", (*(stus+1)).name, (*(stus+1)).scores[1]);// 李四 88
小貼士:
結構體是自定義數據類型,它是數據類型,用法類似于基本類型的int;
結構體數組它是存放結構體對象的數組,類似于int數組存放int數據;
基本類型數組怎么用,結構體數組就怎么用—>可以遍歷,可以作為形式參數,也可以做指針等;
-
-
結構體類型的使用案例
結構體可以作為函數的返回類型,形式參數等。
舉例:
#include <stdio.h> #include <string.h>/*** 定義一個Cat結構體*/ struct Cat {char *name; // 姓名int age; // 年齡char color[20]; // 顏色 };/*** 結構體類型作為形式參數*/ void test1(struct Cat c) {printf("test1:\n%s,%d,%s\n", c.name, c.age, c.color); }/*** 結構體類型作為函數返回類型*/ struct Cat test2(struct Cat c) {c.name = "金寶";c.age = 3;strcpy(c.color, "黃色");return c; }/*** 結構體指針作為參數和返回類型* 需求:根據Cat的name,在Cat數組中匹配Cat對象*/ struct Cat *test3(struct Cat *cats, int len, char* name) {struct Cat *p = cats;for (; p < cats + len; p++){if (strcmp(name, p->name) == 0) return p; // p是指針}return NULL; }int main(int argc, char *argv[]) {struct Cat cat = {"招財", 5, "白色"};test1(cat);struct Cat c = test2(cat);printf("test2:\n%s,%d,%s\n", c.name, c.age, c.color);struct Cat cats[] = {{"招財", 5, "白色"},{"金寶", 3, "金色"}};struct Cat *c2 = test3(cats, sizeof(cats)/sizeof(cats[0]),"招財");printf("test3:\n%s,%d,%s\n", c2->name, c2->age, c2->color);return 0; }
結構體類型求大小
字節對齊
-
字節對齊的原因:
- 硬件要求 某些硬件平臺(如ARM、x86)要求特定類型的數據必須對齊到特定地址,否則會引發性能下降或硬件異常。
- 優化性能 對齊的數據訪問速度更快。例如,CPU訪問對齊的
int
數據只需一次內存操作,而未對齊的數據可能需要多次操作。
-
字節對齊規則:
- 默認對齊規則
- 結構體的每個成員按其類型大小和編譯器默認對齊數(通常是類型的自然對齊數)對齊。
- 結構體的總大小必須是最大對齊數的整數倍。
- 對齊細節
- 基本類型的對齊數:
char
(1字節)、short
(2字節)、int
(4字節)、double
(8字節)。 - 結構體成員的對齊:每個成員的起始地址必須是對齊數的整數倍。
- 結構體總大小的對齊:結構體的總大小必須是其最大對齊數的整數倍。
- 基本類型的對齊數:
#pragma pack(n)
的影響 使用#pragma pack(n)
可以強制指定對齊數為n
(n
為 1、2、4、8、16)。此時:- 每個成員的對齊數取
n
和其類型大小的較小值。 - 結構體的總大小必須是
n
和最大對齊數中的較小值的整數倍。
- 每個成員的對齊數取
- 默認對齊規則
-
對齊示例
-
默認對齊
struct S1{char c; // 1字節,偏移0int i; // 4字節,(需對齊到4,填充3字節,偏移4-7)double d;// 8字節,(需對齊到8,偏移8~15)}
struct S2{double d;char c; int i; }
struct S3{char c; double d; int i; }
內存分析:
總結,結構體中,成員的順序會影響到結構體最終的大小。
-
使用
#pragma pack(1)
自定義對齊規則,(1)
對齊的字節數#pragma pack(1) // 對齊數之前的字節數能被1整數struct S1{char c; // 1字節 (偏移0) 1int i;// 4字節 (偏移1~4)4double d;// 8字節 (偏移5~12)8}; #pragma pack()// S1 的大小為 13字節
#pragma pack(2) // 對齊數之前的字節數能被2整數struct S1{char c; // 1字節 (偏移0,填充1字節) 2int i;// 4字節 (偏移2~5)4double d;// 8字節 (偏移6~13)8}; #pragma pack()// S1 的大小為 14字節 char -1 + 1 int
#pragma pack(4) // 對齊數之前的字節數能被4整數struct S1{char c; // 1字節 (偏移0,填充3字節) 4int i;// 4字節 (偏移4~7)4double d;// 8字節 (偏移8~15)8}; #pragma pack()// S1 的大小為 16字節
-
在GNU標準中,可以在定義結構體時,指定對齊規則:
__attribute__((packed)); -- 結構體所占內存大小是所有成員所占內存大小之和——attribute__((aligned(n))); -- 設置結構體占n個字節,如果n比默認值小,n不起作用;n必須是2的次方
-
案例:
#include <stdio.h>int main(int argc, char *argv[]) {struct Cat{char sex __attribute((aligned(2))); // 設置 char按照2字節對齊int id; // 4char name[20];// 20}__attribute__((packed)); // 28 25printf("%ld\n",sizeof(struct Cat));// 28 25return 0; }
-
-
柔性數組
定義:柔性數組不占有結構體的大小。
特點:
- 柔性數組必須是結構體的最后一個成員
- 結構體大小在編譯時是未確定的
- 必須動態分配內存,指定數組的大小
語法:
struct St{...char arr[];}
案例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>/* 定義包含柔性數組的結構體 */
struct FAStruct
{int len; // 用于記錄柔性數組的長度char data[]; // 柔性數組,注意這里沒有指定大小。柔性數組只能用于結構體,并且只能是最后一個成員。
};int main(int argc, char *argv[])
{const char* str1 = "Hello, Flexible Array!";const char* str2 = "hello world!";int str_len1 = strlen(str1) + 1; // +1 為了存儲\0int str_len2 = strlen(str2) + 1;// 使用calloc分配內存,為結構頭和柔性數組部分分配內存struct FAStruct *fas1 = (struct FAStruct*)calloc(1,sizeof(struct FAStruct) + str_len1); // 結構體空間大小 + 數組空間大小struct FAStruct *fas2 = (struct FAStruct*)calloc(1,sizeof(struct FAStruct) + str_len2); // 結構體空間大小 + 數組空間大小if (!fas1 || !fas2){perror("內存申請失敗!");return -1;}fas1->len = str_len1 - 1; // 不包含字符串結束符的長度strcpy(fas1->data, str1);fas2->len = str_len2 - 1; // 不包含字符串結束符的長度strcpy(fas2->data, str2);printf("字符串1:%s,%d\n", fas1->data,fas1->len);printf("字符串2:%s,%d\n", fas2->data,fas2->len);// 釋放內存free(fas1);free(fas2);return 0;
}
課堂練習
計算以下結構體的大小
#include <stdio.h>// 定義測試結構體
struct TEST1
{char a;int b;
}; struct TEST1_1
{char a;int b;
}__attribute__((packed));// 取消字節對齊,取消之后,結構體數據類型大小就等于其所有成員的數據類型之和struct TEST1_2
{char a __attribute__((aligned(2)));int b;
};struct TEST2
{char a;short c; int b;
};struct TEST3
{int num;char name[10];char sex;int age;double score;
};struct TEST3_1
{int num;char name[10];char sex;double score;int age;
};struct TEST4
{int num;short name[5];char sex;int age;int scores[2];
};
int main(int argc,char *argv[])
{// 創建結構體變量struct TEST1 test1;struct TEST2 test2;struct TEST3 test3;struct TEST3_1 test3_1;struct TEST4 test4; struct TEST1_1 test1_1;struct TEST1_2 test1_2;// 計算大小printf("%lu\n",sizeof(test1));// 8printf("%lu\n",sizeof(test2));// 8printf("%lu\n",sizeof(test3));// 32printf("%lu\n",sizeof(test3_1));// 28printf("%lu\n",sizeof(test4));// 28printf("%lu\n",sizeof(test1_1));// 5printf("%lu\n",sizeof(test1_2));// 8return 0;
}
結構體的常見陷阱與最佳實踐
常見陷阱
- 成員訪問越界:訪問不存在的結構體成員。
struct Point p;p.z = 10; // 錯誤,Point結構體沒有z成員
- 內存泄漏:忘記釋放動態分配的結構體內存。
struct Point *p = malloc(sizeof(struct Point));p->x = 10;// 沒有free(p)導致內存泄漏
- 懸掛指針/空懸指針:使用已經釋放的內存或未初始化的指針。
struct Point *p = malloc(sizeof(struct Point));free(p); // 釋放了p指向的內存p->x = 10; // 錯誤,p現在是懸掛指針
- 結構體大小計算錯誤:忘記考慮編譯器的內存對齊。
struct {char a;int b;} s;// sizeof(s)可能是8而不是5
最佳實踐
- 使用typedef簡化語法:為結構體創建簡短的別名。
typedef struct {int x;int y;} Point;
- 將相關數據組織在一起:使用結構體封裝相關數據。
struct Student {char name[50];int age;float grade;};
- 避免過大的結構體:將大型數據結構分解為多個較小的結構體。
- 合理使用指針:對于大型結構體,使用指針而不是值傳遞。
- 注意內存管理:確保正確分配和釋放動態內存。
共用體/聯合體類型
-
定義:使幾個不同變量占用同一段內存的結構。共用體按定義中需要存儲空間最大的成員來分配存儲單元,其他成員也是使用該空間,它們的首地址是相同。
-
定義格式:
union 共用體名稱{數據類型 成員名;數據類型 成員名;...};
-
共用體的定義和結構體類似。
-
可以有名字,也可以匿名
-
共用體在定義時也可以定義共用體變量
-
共用體在定義時也可以初始化成員
-
共用體也可以作為形參和返回值類型使用
-
共用體也可以定義共用體變量
-
…
也就是說,結構體的語法,共用體都支持
-
-
注意:
-
共用體弊大于利,盡量少用,一般很少用;
-
共用體變量在某一時刻只能存儲一個數據,并且也只能取出一個數
-
共用體所有成員共享同一內存空間,同一時間只能存儲一個值,可能導致數據覆蓋
-
共用體和結構體都是自定義數據類型,用法類似于基本數據類型
- 共用體可以是共用體的成員,也可以是結構體的成員
- 結構體可以是結構體的成員,也可以是共用體的成員
-
案例:
#include <stdio.h>/*** 定義共用體*/
union S
{char a;float b;int c;
};// S的大小是4字節// 共用體作為共用體成員
union F
{char a;union S s; // 4字節
};// F的大小是4字節// 定義一個結構體
struct H
{int a;char b;
};// H的大小是8字節// 結構體作為結構體成員
struct I
{int a;int b;struct H h;
}; // I的大小是16字節// 共用體作為結構體成員
struct J
{int a; // 4char b; // 1 + 3union S s; // 4
}; // J的大小是12字節void test1()
{// 定義一個共用體(數據類型)union Obj{int num;char sex;double score;};// 定義匿名共用體union{int a;char c;} c;// 定義變量union Obj obj;// 存儲數據obj.num = 10; // 共用體空間數據:10obj.sex = 'A';// 共用體空間數據:'A' = 65 覆蓋數據// 運算obj.num += 5; // 共用體空間數據:70 覆蓋數據 'F' sizeof(數據類型|變量名)printf("%lu,%lu,%d,%c,%.2lf\n",sizeof(obj),sizeof(union Obj), obj.num, obj.sex, obj.score);
}int main(int argc,char *argv[])
{test1();return 0;
}
案例:
#include <stdio.h>union Object
{char a; // 8字節int b; // 8字節double c;// 8字節
}; // 8字節,共用體所有成員共享最大成員的內存空間。union Object fun(union Object obj)
{return obj;
}int main(int argc, char *argv[])
{union Object obj;obj.a = 65;// charprintf("%c\n", fun(obj).a); // charobj.b = 10000000;printf("%d\n", fun(obj).b);// intobj.c = 12.25;printf("%f\n", fun(obj).c);// doublereturn 0;
}
要求:共用體在使用的時候,建議存取時使用的成員是一致的。也就是使用成員a存數據,必須使用成員b取數據。
結構體與共用體的主要區別
- 內存占用:
- 結構體的成員是連續存儲的,每個成員都有自己的內存空間。
- 共用體的所有成員共享同一塊內存空間(共享所有成員中最大成員的空間)。
- 訪問方式:
- 結構體的成員可以同時訪問。
- 共用體的成員不能同時訪問,因為它們共享同一塊內存空間。
- 使用場景:
- 結構體適用于需要同時存儲多個不同類型數據的情況。
- 共用體適用于需要在同一時間存儲不同類型數據中的一種的情況,可以節省內存。