一篇文章讓你由淺入深去感受四種自定義型別的魅力——結構體型別,位段,列舉型別,聯合體(共用體)型別
老規矩,筆記放在目錄前自取
自定義型別筆記—————歡迎自取
文章目錄
- 一篇文章讓你由淺入深去感受四種自定義型別的魅力——結構體型別,位段,列舉型別,聯合體(共用體)型別
- 一、結構體型別
- 1. 創建結構體變數
- 1.1 創建隱藏結構體變數
- 1.2 結構體變數的重命名
- 2. 結構體的自參考
- 3. 結構體型別對齊
- 4. 改變結構體變數默認對齊數
- 5. 結構體型別每個變數的偏移量
- 6. 結構體傳參
- 二、位段
- 1. 位段的記憶體空間的分配
- 2. 位段跨平臺問題
- 三、列舉
- 1. 列舉型別的定義
- 2. 列舉的優點
- 四、聯合體(共用體)
- 1. 聯合體變數的定義
- 2. 聯合體變數的優點
- 3. 聯合體大小的計算
一、結構體型別
1. 創建結構體變數
組合型別-創建自定義型別
typedef struct Stu
{
int x;
}mine;
struct Book//內部型別定義
{
char name[30];
float price;
char id[20];
mine a;
}b1, b2;//這里可以直接寫b1 = {"c語言",63.2f,"A1", {1}};
//注意分號不能漏掉
//此處b1,b2可以省略,b1,b2為全域變數
struct Book b3;//也是全域變數
int main()
{
struct Book b4={"c語言",63.2f,"A1", {1}};//內部成員初始化
printf("%d", b4.a.x);//列印1
return 0;
}
//簡單訪問其中成員
1.1 創建隱藏結構體變數
將變數名舍去
如果用指標指向它會怎么樣
struct //直接省略變數名
{
char name[30];
float price;
char id[20];
}b1, b2;
struct
{
char name[30];
float price;
char id[20];
}*p;
int main()
{
*p = &b1;//這樣的寫法是錯誤的
//編譯器會認為,上面兩種是不同型別,雖然型別的內容相同
return 0;
}
1.2 結構體變數的重命名
利用typedef
typedef struct Stu { int x; }mine;//重新命名 int main() { struct Stu a; mine b; //以上兩種寫法均可 return 0; }
2. 結構體的自參考
先了解一下,資料結構里面的鏈表結構
鏈表中,元素是沒有順序的,但可以通過鏈表鏈接
假設:如果像函式中嵌套回圈的方式,每個節點都可以傳送到下一個節點,可否實作鏈表的參考
struct Node { int ID; struct Node n; }但是,問題出現了,如果這樣回圈下去,什么時候停止,它不像普通的變數的大小可以通過條件陳述句停止
所以,這個假想不成立
那么,一個節點是肯定要有個東西,讓我們可以訪問到下一個節點,但不會死回圈
這個東西,就是指標
struct Node { int ID; struct Node* n;//指向下一個節點的地址就好了 }上面就實作了,結構體的自參考
typedef 重命名結構體 自參考
typedef struct Node { int ID; struct Node* n; }Node;
3. 結構體型別對齊
結構體變數型別的大小
struct s { int x; int y; char a; }; int main() { printf("%d", sizeof(struct s)); return 0; }那么是否是 int(4) + int(4) + char(1) 一共九個位元組的大小呢?
列印出來結果是12
顯然,不是簡單的組成結構體中各型別相加
那么,具體的規則到底是什么?
結構體變數對齊規則
第一個成員在與結構體變數偏移量為0的地址處
其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處
對齊數 = 編譯器默認的一個對齊數 與 該成員大小中 較小值
- VS 默認的值為8
- Linux 沒有默認值(成員自身大小就是對齊數)
結構體總大小為最大對齊數(每個成員變數都有一個對齊數)的整數倍
如果嵌套了結構體的情況,
嵌套的結構體對齊到自己的最大對齊數的整數倍處
結構體的整體大小(位元組數)就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍
圖解:
為什么要記憶體對齊?
- 平臺原因(移植原因):不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則拋出硬體例外,
- 有些硬體只能在某種規律下訪問,所以資料最好在特定的位置上
- 性能原因: 資料結構(尤其是堆疊)應該盡可能地在自然邊界上對齊, 原因在于,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問
- 一次訪問4個位元組,如果不對齊,則需要訪問兩次(比如一個char和一個int型別連在一起)
- 而對齊則只需要訪問一次,且是完整的資料
結構體型別為了對齊,浪費那么多空間怎么辦
- 我們只能將小的變數放在一起
- 這樣就能盡可能節省空間
4. 改變結構體變數默認對齊數
利用 #pragma pack進行修改和恢復
#pragma pack(1) //相當于沒有對齊,空間小,但效率可能沒那么高了 struct stu { int x; int y; char a; }; #pragma pack() //用完結構體后,恢復默認對齊數 int main() { struct stu a; printf("%d", sizeof(a));//列印9 }
5. 結構體型別每個變數的偏移量
利用 offsetof 這個函式
offsetof
頭檔案:<stddef.h>
size_t offsetof( structName, memberName );
引數:結構體型別名,結構體成員名
回傳值:回傳指定成員起始位置的位元組偏移量
#include <stdio.h> #include <stddef.h> struct s { int x; int y; char a; }; int main() { printf("%d", offsetof(struct s, x));//0 printf("%d", offsetof(struct s, y));//4 printf("%d", offsetof(struct s, a));//8 return 0; }
6. 結構體傳參
參考符號 ->
訪問符號 .
struct Book { char name[30]; float price; char id[20]; }; void Print(struct Book* b) { printf("書名:%s\n",(*b).name); //結構體指標 -> 成員名/x->name與上面等價 printf("價格:%f\n",(*b).price); printf("書名:%s\n",(*b).id); } int main() { struct Book b1 = { "c語言",63.2f,"A1" };//內部成員初始化 Print(&b1); }為了節省函式創建臨時變數的空間大小,創建一個相同大小的結構體接受值也是可以的,但為了節省空間,我們將指標傳進去,更好的節省空間大小,我們操作通過指標去操作
函式傳參的時候,引數是需要壓堆疊,會有時間和空間上的系統開銷, 如果傳遞一個結構體物件的時候,結構體過大,引數壓堆疊的的系統開銷比較大,所以會導致性能的下降
二、位段
位段是什么:
位段和結構體類似
但又有區別:
位段的成員必須是int、unsigned int 或signed int 和 char(char屬于整型家族的)
位段的成員名后邊有一個冒號和一個數字, _ 不是必要的
位段可以節省空間
位段的位 —— 二進制位(1bit)
struct s { int _a:2;//_a分配2個bit位 int _b:4;//_b分配4個bit位 int _c:10;//_c分配10個bit位 }; int main() { printf("%d", sizeof(s));//16個bit位--4個位元組 }
但事實上,有時不是正好是整位元組,位段其實還是會浪費一點空間
所以位段到底是怎么分配記憶體空間的?
1. 位段的記憶體空間的分配
位段空間的需要,是按照char(1個位元組)或者 int(4個位元組)形式去開辟的
位段涉及很多不確定因素,位段是不跨平臺的,不同編譯器編譯出來的結果可能不同,注重可移植的程式應該避免使用位段
但c語言標準里面沒有說明,開辟的位元組中,從低位到高位存盤,還是從高位到低位存盤
我們首先假設一個方向,每個位元組從高位向地位存盤
通過一個例子來看:
struct stu
{
char a:6;//分配6bit
char b:5;//分配5bit
char c:4;//分配4bit
char d:2;//分配2bit
};
int main()
{
struct stu s = { 0 };//初始化結構體
s.a = 10;
s.b = 15;
s.c = 20;
s.d = 3;
return 0;
}
分析:
a : 因為只分配了6個bit位,所以10的二進制存盤時發生了截斷,只存盤了001010這幾個數
其他變數分析同a,詳細見圖

最后我們運行一下,看是否如同我們假設一樣

除錯起來,等變數賦值之后,查看記憶體,和我們分析的一樣,是0a0f34
但再次強調,每個編譯器不同,不同的編譯器可能會得出不同的結果,此結果只支持VS2019
2. 位段跨平臺問題
- 創建int位段時候,有無符號是未知
- 位段中最大數目不確定(32位機器最大32,寫成40,會出問題)
- 位段中的成員在記憶體中從左向右分配,還是從右向左分配標準尚未定義
- 當一個結構包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,未知
在網路傳送資訊包裝的時候,用位段節省空間,可以使得效率變高
三、列舉
描述生活中可以列舉的東西
1. 列舉型別的定義
列舉型別的取值只能是正數,列舉型別的大小也是4個位元組
//定義一個性別列舉常量
enum sex
{
//默認從0開始,向下遞增,但也可以位元組定義數字
male,
female,
secret
};
int main()
{
enum sex a = male;
//錯誤操作
//enum sex a = 4;//整型和列舉型別不匹配
//male = 4; //列舉常量是常量不能賦值,但可以在enum sex中賦值
printf("%d\n", male);//printf列印出0
printf("%d\n", female);//printf列印出1
printf("%d\n", secret);//printf列印出2
return 0;
}
2. 列舉的優點
列舉和define相似,但效果截然不同,列舉的優點有以下幾點
- 增加代碼的可讀性和可維護性,比如某些情境下選擇選項的數字,換成字符,更容易理解
- 和#define定義的識別符號比較列舉有型別檢查,更加嚴謹
- 防止了命名污染(封裝),相比較define全域變數要好
- 便于除錯,除錯器在檢驗列舉變數時,可以顯示符號值
- 使用方便,一次可以定義多個常量,而#define宏一次只能定義一個
舉個栗子吧
只是個大概的模型,并不是完整代碼,簡單感受一下
enum menu
{
START,
SAVE,
EXIT
};
void print_menu()
{
printf("1.開始游戲 2.保存游戲 0.退出游戲");
}
int main()
{
int input = 0;
scanf("%d", &input);
//switch(input)
//{
//case 1 : printf("開始游戲");break;
//case 2 : printf("保存游戲");break;
//case 0 : printf("退出游戲");break;
//}
//當我們選擇0,1,2的時候,還需要看一下每個選項對應的數字
switch(input)
{
case START: printf("開始游戲");break;
case SAVE: printf("保存游戲");break;
case EXIT: printf("退出游戲");break;
}
//但此時我們就不需要了,直接輸入我們想要選擇的選項就行
}
四、聯合體(共用體)
1. 聯合體變數的定義
先用一串代碼感受一下,為什么它叫共用體
union U
{
char a;
int b;
};
int main()
{
union U u = { 0 };//初始化
printf("%p", &u);
printf("%p", &(u.a));
printf("%p", &(u.b));
//讓我們看看聯合體型別變數記憶體的分配是如何的?
return 0;
}
結果是

居然是相同的地址!到底是怎么實作的?

a和b共同利用這塊空間,所以聯合體變數大小至少是成員中最大成員的大小
2. 聯合體變數的優點
使用聯合體巧妙使用聯合體計算計算機大小端存盤
我們先用之前的知識實作一下
int main()
{
int a = 1;
char* p = (char*)a;
if(*p == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
我們還可以使用結構體來實作此功能
這樣我們就可以不用指標了
union U
{
char a;
int b;
};
int main()
{
union U u = { 0 };
u.b = 1;
if(u.b == 1)
{
printf("小端");
}
else
{
printf("大端");
}
}
3. 聯合體大小的計算
前面說,聯合體型別大小至少是成員中最大成員的大小
但還有一個要求,就是當聯合體成員中最大成員大小不是對齊數的整數倍時,
對齊到對齊數的整數倍
舉個栗子
union U
{
char a[3];
int b;
};
union UU
{
char a[13];
int b;
};
int main()
{
printf("%d\n", sizeof(union U));//4
printf("%d\n", sizeof(union UU));//16
return 0;
}
我們來分析:
關于union U

關于union UU

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/305505.html
標籤:其他
下一篇:第一個入門級小程式


