響應(yīng)式網(wǎng)頁(yè)設(shè)計(jì)最方便快速seo網(wǎng)站推廣計(jì)劃
自定義類型:結(jié)構(gòu)體,枚舉,聯(lián)合
- 1.結(jié)構(gòu)體
- 1.1結(jié)構(gòu)體類的基礎(chǔ)知識(shí)
- 1.2結(jié)構(gòu)的聲明
- 1.3特殊的聲明
- 1.4結(jié)構(gòu)的自引用
- 1.5結(jié)構(gòu)體變量的定義和初始化
- 1.6結(jié)構(gòu)體內(nèi)存對(duì)齊
- 1.7修改默認(rèn)對(duì)齊
- 1.8結(jié)構(gòu)體傳參
- 2.段位
- 2.1什么是段位
- 2.2段位的內(nèi)存分配
- 2.3位段的跨平臺(tái)問(wèn)題
- 2.4位段的應(yīng)用
- 3.枚舉
- 3.1枚舉類型的定義
- 3.2枚舉的優(yōu)點(diǎn)
- 3.3枚舉的使用
- 4.聯(lián)合
- 4.1聯(lián)合類型的定義
- 4.2聯(lián)合的特點(diǎn)
- 4.3聯(lián)合大小的計(jì)算
1.結(jié)構(gòu)體
1.1結(jié)構(gòu)體類的基礎(chǔ)知識(shí)
結(jié)構(gòu)是一些值的集合,這些值稱為成員變量。結(jié)構(gòu)的每個(gè)成員可以是不同類型的變量。
1.2結(jié)構(gòu)的聲明
例如描述一個(gè)學(xué)生:
struct Stu
{
char name[20];//名字
int age;//年齡
char sex[5];//性別
char id[20];//學(xué)號(hào)
}; //分號(hào)不能丟
1.3特殊的聲明
在聲明結(jié)構(gòu)的時(shí)候,可以不完全的聲明
//匿名結(jié)構(gòu)體類型
struct
{char name[20];char author[12];float price;
}b1, b2;
上面的兩個(gè)結(jié)構(gòu)在聲明的時(shí)候省略掉了結(jié)構(gòu)體標(biāo)簽(tag)
當(dāng)我們使用匿名結(jié)構(gòu)體時(shí),以下做法合理么?
struct
{char name[20];char author[12];float price;
}b;
struct
{char name[20];char author[12];float price;
}* p;
int main()
{p = &b;//不建議這樣寫return 0;
}
警告:
編譯器會(huì)把上面的兩個(gè)聲明當(dāng)成完全不同的兩個(gè)類型。
所以是非法的。
1.4結(jié)構(gòu)的自引用
//錯(cuò)誤自引用
struct Node
{int data;struct Node next;
};
//正確自引用
struct Node
{int data;struct Node* next;
};
1.5結(jié)構(gòu)體變量的定義和初始化
struct Point
{int x;int y;
}p1; //聲明類型的同時(shí)定義變量p1,p1為全局變量
struct Point p2; //定義結(jié)構(gòu)體變量p2,p2為局部變量
//初始化:定義變量的同時(shí)賦初值。
struct Point p3 = { x, y };
struct Stu //類型聲明
{char name[15];//名字int age; //年齡
};
struct Stu s = { "zhangsan", 20 };//初始化
struct Node
{int data;struct Point p;struct Node* next;
}n1 = { 10, {4,5}, NULL }; //結(jié)構(gòu)體嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//結(jié)構(gòu)體嵌套初始化
1.6結(jié)構(gòu)體內(nèi)存對(duì)齊
我們已經(jīng)掌握了結(jié)構(gòu)體的基本使用了。
現(xiàn)在我們深入討論一個(gè)問(wèn)題:計(jì)算結(jié)構(gòu)體的大小。
這也是一個(gè)特別熱門的考點(diǎn): 結(jié)構(gòu)體內(nèi)存對(duì)齊
首先得掌握結(jié)構(gòu)體的對(duì)齊規(guī)則:
- 第一個(gè)成員在與結(jié)構(gòu)體變量偏移量為0的地址處。
- 其他成員變量要對(duì)齊到某個(gè)數(shù)字對(duì)齊數(shù)的整數(shù)倍的地址處。
對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù)與該成員大小的較小值。
VS中默認(rèn)的值為8
gcc沒(méi)有對(duì)齊數(shù),對(duì)齊數(shù)就是自身大小 - 結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)(每個(gè)成員變量都有一個(gè)對(duì)齊數(shù))的整數(shù)倍。
- 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己的最大對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
案例一:
struct S1
{char c1;int i;char c2;
};
int main()
{printf("%d\n", sizeof(struct S1));return 0;
}
運(yùn)行結(jié)果:
分析:
案例二:
struct S2
{char c1;char c2;int i;
};
int main()
{printf("%d\n", sizeof(struct S2));return 0;
}
運(yùn)行結(jié)果:
分析:
案例三:
struct S3
{double d;char c;int i;
};
int main()
{printf("%d\n", sizeof(struct S3));return 0;
}
運(yùn)行結(jié)果:
分析:
案例四:
//練習(xí)4 - 結(jié)構(gòu)體嵌套問(wèn)題
struct S3
{double d;char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};
int main()
{printf("%d\n", sizeof(struct S4));return 0;
}
運(yùn)行結(jié)果:
分析:
為什么存在內(nèi)存對(duì)齊?
大部分的參考資料都是如是說(shuō)的:
- 平臺(tái)原因(移植原因):
不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。 - 性能原因:
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。
原因在于,為了訪問(wèn)未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問(wèn);而對(duì)齊的內(nèi)存訪問(wèn)僅需要一次訪問(wèn)。
總體來(lái)說(shuō):
結(jié)構(gòu)體的內(nèi)存對(duì)齊是拿空間來(lái)?yè)Q取時(shí)間的做法。
那在設(shè)計(jì)結(jié)構(gòu)體的時(shí)候,我們既要滿足對(duì)齊,又要節(jié)省空間,如何做到:
讓占用空間小的成員盡量集中在一起
//例如:
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};
S1和S2類型的成員一模一樣,但是S1和S2所占空間的大小有了一些區(qū)別。
補(bǔ):offsetof(可以計(jì)算結(jié)構(gòu)體成員相較于結(jié)構(gòu)體起始位置的偏移量)
offsetof案例:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stddef.h>
struct S1
{char c1;//1int i;//4char c2;//1
};
int main()
{struct S1 s1 = { 0 };printf("%d\n", offsetof(struct S1, c1));printf("%d\n", offsetof(struct S1, i));printf("%d\n", offsetof(struct S1, c2));return 0;
}
運(yùn)行結(jié)果:
1.7修改默認(rèn)對(duì)齊
之前我們見過(guò)了 #pragma 這個(gè)預(yù)處理指令,這里我們?cè)俅问褂?#xff0c;可以改變我們的默認(rèn)對(duì)齊數(shù)。
#pragma pack(1)//設(shè)置默認(rèn)對(duì)齊數(shù)為1
struct S
{char c1;//1 1 1int a; // 4 1 1char c2;//1 1 1
};
#pragma pack()//取消設(shè)置的默認(rèn)對(duì)齊數(shù),還原為默認(rèn)
int main()
{printf("%d\n", sizeof(struct S));return 0;
}
運(yùn)行結(jié)果:
結(jié)論:
結(jié)構(gòu)在對(duì)齊方式不合適的時(shí)候,我么可以自己更改默認(rèn)對(duì)齊數(shù)。
1.8結(jié)構(gòu)體傳參
代碼案例:
struct S
{int data[100];int num;
};
//結(jié)構(gòu)體傳參
void print1(struct S tmp)
{printf("%d\n", tmp.num);
}
//結(jié)構(gòu)體地址傳參
void print2(const struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{struct S s = { {1,2,3}, 100 };print1(s);print2(&s);return 0;
}
運(yùn)行結(jié)果:
上面的 print1 和 print2 函數(shù)哪個(gè)好些?
答案是:首選print2函數(shù)。
原因:
函數(shù)傳參的時(shí)候,參數(shù)是需要壓棧,會(huì)有時(shí)間和空間上的系統(tǒng)開銷。
如果傳遞一個(gè)結(jié)構(gòu)體對(duì)象的時(shí)候,結(jié)構(gòu)體過(guò)大,參數(shù)壓棧的的系統(tǒng)開銷比較大,所以會(huì)導(dǎo)致性能的下降。
結(jié)論:
結(jié)構(gòu)體傳參的時(shí)候,要傳結(jié)構(gòu)體的地址。
2.段位
結(jié)構(gòu)體講完就得講講結(jié)構(gòu)體實(shí)現(xiàn)位段的能力。
2.1什么是段位
位段的聲明和結(jié)構(gòu)是類似的,有兩個(gè)不同:
1.位段的成員必須是 int、unsigned int 或signed int 。
2.位段的成員名后邊有一個(gè)冒號(hào)和一個(gè)數(shù)字。
代碼案例:
struct A
{int _a : 2;//二進(jìn)制位int _b : 5;int _c : 10;int _d : 30;
};
int main()
{printf("%d\n", sizeof(struct A));return 0;
}
A就是一個(gè)位段類型。
那位段A的大小是多少?
運(yùn)行結(jié)果:
2.2段位的內(nèi)存分配
- 位段的成員可以是 int unsigned int signed int 或者是 char (屬于整形家族)類型
- 位段的空間上是按照需要以4個(gè)字節(jié)( int )或者1個(gè)字節(jié)( char )的方式來(lái)開辟的。
- 位段涉及很多不確定因素,位段是不跨平臺(tái)的,注重可移植的程序應(yīng)該避免使用位段。
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;printf("%d\n", sizeof(s));return 0;
}
空間是如何開辟的?
運(yùn)行結(jié)果:
2.3位段的跨平臺(tái)問(wèn)題
- int 位段被當(dāng)成有符號(hào)數(shù)還是無(wú)符號(hào)數(shù)是不確定的。
- 位段中最大位的數(shù)目不能確定。(16位機(jī)器最大16,32位機(jī)器最大32,寫成27,在16位機(jī)
器會(huì)出問(wèn)題。- 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配標(biāo)準(zhǔn)尚未定義。
- 當(dāng)一個(gè)結(jié)構(gòu)包含兩個(gè)位段,第二個(gè)位段成員比較大,無(wú)法容納于第一個(gè)位段剩余的位時(shí),是
舍棄剩余的位還是利用,這是不確定的。
總結(jié):
跟結(jié)構(gòu)相比,位段可以達(dá)到同樣的效果,但是可以很好的節(jié)省空間,但是有跨平臺(tái)的問(wèn)題存在。
2.4位段的應(yīng)用
3.枚舉
枚舉顧名思義就是一一列舉。
把可能的取值一一列舉。
比如我們現(xiàn)實(shí)生活中:
一周的星期一到星期日是有限的7天,可以一一列舉。
性別有:男、女、保密,也可以一一列舉。
月份有12個(gè)月,也可以一一列舉
這里就可以使用枚舉了
3.1枚舉類型的定義
enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Sex//性別
{MALE,FEMALE,SECRET
};
enum Color//顏色
{RED,GREEN,BLUE
};
以上定義的 enum Day , enum Sex , enum Color 都是枚舉類型。
{ }中的內(nèi)容是枚舉類型的可能取值,也叫 枚舉常量 。
這些可能取值都是有值的,默認(rèn)從0開始,一次遞增1,當(dāng)然在定義的時(shí)候也可以賦初值。
enum Color//顏色
{RED = 1,GREEN = 2,BLUE = 4
};
3.2枚舉的優(yōu)點(diǎn)
為什么使用枚舉?
我們可以使用 #define 定義常量,為什么非要使用枚舉?
枚舉的優(yōu)點(diǎn):
- 增加代碼的可讀性和可維護(hù)性
- 和#define定義的標(biāo)識(shí)符比較枚舉有類型檢查,更加嚴(yán)謹(jǐn)。
- 防止了命名污染(封裝)
- 便于調(diào)試
- 使用方便,一次可以定義多個(gè)常量
3.3枚舉的使用
enum Color
{RED,//0GREEN,//1BLUE//2
};
#define RED 0
int main()
{enum Color c = GREEN;//只能拿枚舉常量給枚舉變量賦值enum Color cc = 3;//.c文件中允許,.cpp文件中不允許return 0;
}
4.聯(lián)合
4.1聯(lián)合類型的定義
聯(lián)合也是一種特殊的自定義類型
這種類型定義的變量也包含一系列的成員,特征是這些成員公用同一塊空間(所以聯(lián)合也叫共用體)。
//聯(lián)合類型的聲明
union Un
{char c;int i;
};
int main()
{//聯(lián)合變量的定義union Un un;//計(jì)算連個(gè)變量的大小printf("%d\n", sizeof(un));return 0;
}
運(yùn)行結(jié)果:
4.2聯(lián)合的特點(diǎn)
聯(lián)合的成員是共用同一塊內(nèi)存空間的,這樣一個(gè)聯(lián)合變量的大小,至少是最大成員的大小(因?yàn)槁?lián)合至少得有能力保存最大的那個(gè)成員)。
union Un
{char c;int i;
};
int main()
{printf("%d\n", sizeof(union Un));union Un un = { 0 };un.i = 0x11223344;un.c = 0x55;printf("%p\n", &un);printf("%p\n", &(un.i));printf("%p\n", &(un.c));return 0;
}
&un:
運(yùn)行結(jié)果:
面試題:
判斷當(dāng)前計(jì)算機(jī)的大小端存儲(chǔ)
int check_sys()
{union{int i;char c;}un = {.i = 1};return un.c;
}int main()
{int ret = check_sys();if (ret == 1)printf("小端\n");elseprintf("大端\n");return 0;
}
運(yùn)行結(jié)果:
4.3聯(lián)合大小的計(jì)算
聯(lián)合的大小至少是最大成員的大小。
當(dāng)最大成員大小不是最大對(duì)齊數(shù)的整數(shù)倍的時(shí)候,就要對(duì)齊到最大對(duì)齊數(shù)的整數(shù)倍。
比如:
代碼案例:
union Un1
{char c[5];//5 1 8 1int i;//4 8 4
};
union Un2
{short c[7];//14 2 8 2int i;//4 4 8 4
};
int main()
{printf("%d\n", sizeof(union Un1));//5+3 = 8printf("%d\n", sizeof(union Un2));//16return 0;
}
運(yùn)行結(jié)果:
💘不知不覺(jué),自定義類型:結(jié)構(gòu)體,枚舉,聯(lián)合以告一段落。通讀全文的你肯定收獲滿滿,讓我們繼續(xù)為C語(yǔ)言學(xué)習(xí)共同奮進(jìn)!!!