最新C語言自定義類型詳解
前言
隨著學習的進一步深入,我們不再滿足于做簡單的計算題,想著做一些像樣的東西,如學生管理系統(tǒng)、酒店入住系統(tǒng)等等......而C語言提供的基礎數(shù)據類型已經不足以支撐我們的想法,于是便要學習自定義類型,根據自己的需求來創(chuàng)建類型。
結構體
生活當中有很多物品是不能簡單的用整型、浮點型、字符型來區(qū)分,它們常常是復雜的集合,比如人,一個人擁有年齡,身高、體重、學歷......等信息。我們可以用結構體來實現(xiàn)準確描述人這種復雜集合。
結構體的基礎知識
結構和數(shù)組的區(qū)別
結構是一些值的集合,這些值稱為成員變量,每個成員變量可以是不同類型。而數(shù)組是同類型值得集合
結構體的聲明
對于結構體的聲明,需要有結構體名、成員列表、變量列表(可省略)。
struct tag { member_list;//成員列表 }variable_list;//變量列表
我們以描述一個學生為例。
struct student { int id;//學號 char name[20];//姓名 char sex;//性別 int age;//年齡 }s1,s2,s3;
結構體的特殊聲明
在聲明結構體的時候,我們可以以不完全聲明(匿名結構體類型),也就是省略掉結構體的標簽。
就像這樣:
struct { int a; char b; float c; }d;
但這種聲明并不安全。
例如:
struct { int a; char b; float c; }d; struct { int a; char b; float c; }*p;
我們令*p=&d 是非法的。因為編譯器會將這兩個聲明當成完全不同的兩個類型。
結構體的自引用
我們能不能結構體套結構體呢?
是可以的,比如我們描述一輛車的構造,它的成員變量分別是車門、輪子、發(fā)動機、玻璃......等,而這些成員也是復雜的集合,比如車門的內部也是復雜的,這就需要結構體套結構體。我們稱為結構體的自引用。
那么怎么實現(xiàn)結構體自引用呢?
先看一段錯誤示范:
我們結構體變量的創(chuàng)建,是要在結構體變量聲明過后,這種寫法屬于是在這個結構體類型還沒有創(chuàng)建完成的時候就使用了該類型的結構體變量。
struct Node { int data; struct Node next; };
正確示例:
將該類型的結構體變量改成該類型的結構體指針就能解決這一問題了~。
struct Node { int data; struct Node* next; };
結構體變量的定義和初始化
我們嘗試一下初始化上面的學生結構體變量。
#include<stdio.h> struct student { int id;//學號 char name[20];//姓名 char sex;//性別 int age;//年齡 }s1, s2, s3; int main() { s1 = { 1,"aa",'M',18 }; printf("%d %s %c %d", s1.id, s1.name, s1.sex, s1.age); }
是不是發(fā)現(xiàn)和數(shù)組的初始化差不多? 定義一個結構體變量和定義一個整型變量的方式差不多,這里就不列舉了。
結構體內存對齊
我們已經掌握了結構體的基本使用,現(xiàn)在我們進入到下一個問題——結構體的大小怎么計算?
猜想:結構體的大小就是結構體內的成員大小加起來
還是以學生結構體類型為例:
struct student { int id;//學號 char name[20];//姓名 char sex;//性別 int age;//年齡 }s1,s2,s3;
我們按照猜想一計算一下,計算出該結構體類型大小為29個字節(jié),事實果真如此?
用代碼驗證一下:
#include<stdio.h> struct student { int id;//學號 char name[20];//姓名 char sex;//性別 int age;//年齡 }s1, s2, s3; int main() { printf("%d", sizeof(struct student)); }
我們發(fā)現(xiàn),結果是32個字節(jié),說明猜想一錯誤。
結構體的對齊規(guī)則
一、第一個成員在于結構體變量偏移量為0的地址處。
二、其他成員變量要對齊到某個數(shù)字(對齊數(shù))的整數(shù)倍的地址處。(ps:對齊數(shù)=編譯器默認的一個對齊數(shù)與改成員大小的較小值,vs編譯器的默認對齊數(shù)為8)
三、結構體總大小為最大對齊數(shù)的整數(shù)倍。
四、如果是嵌套了結構體的情況,嵌套的結構體對齊到自己最大對齊數(shù)的整數(shù)倍處,結構體整體大小就是所有最大對齊數(shù)(含嵌套結構體的對齊數(shù))的整數(shù)倍。
對結構體對齊規(guī)則的解析
id變量便是學生結構體變量的第一個成員,它對齊的位置應該在于該結構體變量的地址偏移量為0處,又因為id是int類型,占4個字節(jié)。
看圖,紅色所占空間就是id所占空間:
第二個成員name變量每個元素所占大小為1個字節(jié),與默認值對齊數(shù)8個字節(jié)相比,1<8,所以name要對齊到1的整數(shù)倍數(shù)處,也就是4的位置,又因為name占20個字節(jié),其所占空間如圖中綠色區(qū)域所示。
第三個成員sex,類型所占字節(jié)數(shù)為1,1<8同樣是對齊到1的整數(shù)倍處,且sex占一個字節(jié),sex所占區(qū)域便是黃色位置。
第四個成員為age,為int類型,所占大小為4個字節(jié),4<8,所以age要對齊到4的整數(shù)倍出,也就是28處,又因為它本身占4個字節(jié),所以age所占空間便是藍色的區(qū)域。
31位置到0位置,中間有32二個字節(jié),這就是為什么student結構體變量大小不是29個字節(jié)而是32個字節(jié)。
為什么存在內存對齊
一是平臺原因:不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據。某些硬件平臺只能在某些地址處取某些待定類型的數(shù)據,否則拋出硬件異常。
二是性能原因數(shù)據結構應該盡可能的在自然邊界上對齊,原因在于,對于訪問未對齊的內存,需要處理器訪問兩次內存,而對齊的話訪問一次就行了。以空間換取了時間。
修改默認對齊數(shù)
內存對齊是空間換時間的辦法,那么我們能不能更進一步,在特定情況下又省空間又省時間呢?修改默認對齊數(shù)便是一種辦法。
修改默認對齊數(shù)需要使用#pragma
#pragma pack(2)就是將默認對齊數(shù)修改成了2,使結構體的大小進一步精簡。我們來看看效果圖(依然是一學生結構體變量為例)
第一個成員id不變,依然是占紅色區(qū)域。
第二個成員name同樣不變,因為1個字節(jié)仍然小于2個字節(jié),占綠色區(qū)域。
第三個成員sex還是沒變,原因與第二個相同,占黃色區(qū)域。
第四個成員age改變,2<4,age一個對齊至2的整數(shù)倍處,占藍色區(qū)域
由圖我們可以發(fā)現(xiàn),這次的學生結構體類型大小只占了30個字節(jié),縮短了兩個字節(jié),我們用程序來驗證一下。
驗證
和我們所設想的一樣。
結構體傳參
直接上代碼,print1使用的是傳值,print2使用的是傳址。那種方法更好呢?
#include<stdio.h> struct A { int data[1000]; int num; }; struct A a = { {1,2,3,4},666 }; void print1(struct A a) { printf("%d\n", a.num); } void print2(struct A* a) { printf("%d\n", a->num); } int main() { print1(a); print2(&a); return 0; }
print2的方法要優(yōu)于print1,對于print1,我們需要創(chuàng)建一塊內存空間來接收傳過來的參數(shù),是需要壓棧的,則在空間和時間上會有更多的系統(tǒng)開銷,如果傳遞的結構體對象過大,會導致性能下降。但是對于print2的方法來說,不需要而外的開銷,使用的是之前的內存,不需要新創(chuàng)建。
所以結構體傳參的時候,要傳結構體的地址。
位段
上面我們了解了基礎的結構體知識,這里我們介紹一下結構體實現(xiàn)位段的能力。
什么是位段?
位段和結構類似,但多了兩條限制。
一是位段的成員必須是int、unsigned int 或者是signed int,也可以出現(xiàn)char。
二是成員名字后面要出現(xiàn)冒號和數(shù)字。
例如:
struct A { int a : 2; int b : 5; int c : 10; int d : 30; };
這就是一個位段類型,冒號后面的數(shù)字是指該成員占多少個比特位,并且該數(shù)字不能超過限制范圍,位段空間的開辟是根據類型來開辟的。
位段A的大小是多少?
我們可以看見,位段A占8個字節(jié)。
#include<stdio.h> struct A { int a : 2; int b : 5; int c : 10; int d : 30; }; int main() { printf("%d", sizeof(struct A)); return 0; }
如何算位段的大小呢?
猜想:是比特位數(shù)加起來/8,算出來字節(jié)數(shù)。
以位段A為例,位數(shù)和為47,按照猜想推算,一個占6個字節(jié),但是結果是8,說明猜想錯誤
位段的內存分配
對于位段A,先開辟4個字節(jié)(因為成分成員類型是int),將a,b,c的位數(shù)相加,得到17位,再將d的位數(shù)加上去,發(fā)現(xiàn)位數(shù)超過了開辟的空間,所以需要新開一個4個字節(jié)的空間(按照類型來開辟空間),這時能夠將位段A所有成員放進開辟的內存之中,所以是8個字節(jié)。
位段的缺點
位段不適用于需要跨平臺的程序。
一是int位段被當成有符號數(shù)還是無符號數(shù)并沒有明文規(guī)定。
二是位段中最大為的數(shù)目不確定(16位的機器是16,32位的機器是32)。
三是位段中成員在內存中從左向右分配,還是從右向左分配沒有明文規(guī)定。
四是當一個結構包含兩個位段,第二個位段成員比較大,無法容納與第一個位段剩余位的時候,是舍棄剩余的位還是利用,這是不確定的。
位段的意義
就是節(jié)約內存,省空間,在緩解網絡擁擠的時候有作用。
枚舉
枚舉類型的定義
直接看代碼吧。類型+標簽+可能的情況。
enum sex { MALE, FEMALE };
枚舉的優(yōu)點
一是增加代碼的可讀性和可維護性。
二是和#define定義的標識符比較,枚舉有類型檢查,更加嚴謹。
三是防止了命名污染(封裝)。
四是便于調試。
五是方便使用,一次可以定義多個變量,提高編程效率。
六是可以將數(shù)字換成符號(例如switch語句中,將case后面的數(shù)字換成便于理解的字符)
聯(lián)合(共用體)
聯(lián)合也是一種被特殊的自定義類型。
這種類型定義的變量也包含一系列的成員,特征是這些成員共用一塊空間(所以聯(lián)合也叫共用體)
聯(lián)合類型的定義
#include<stdio.h> //聯(lián)合體的聲明 union un { char c; int i; }; //聯(lián)合體的定義 union un u; int main() { //計算聯(lián)合體的大小 printf("%d", sizeof(u)); return 0; }
聯(lián)合體的特點
聯(lián)合的成員是共用一塊內存空間的,這樣一個聯(lián)合變量的大小,至少是最大成員的大?。ㄒ驗槁?lián)合至少要有能力保障最大的那個成員)。
當最大成員大小不是最大對齊數(shù)的整數(shù)倍時,就要對齊到最大對齊數(shù)的整數(shù)倍。
到此這篇關于C語言 自定義類型的文章就介紹到這了,更多相關C語言 自定義類型內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Qt專欄之模態(tài)與非模態(tài)對話框的實現(xiàn)
這篇文章主要介紹了Qt專欄之模態(tài)與非模態(tài)對話框的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04C++實現(xiàn)LeetCode(56.合并區(qū)間)
這篇文章主要介紹了C++實現(xiàn)LeetCode(56.合并區(qū)間),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-07-07深入學習C++智能指針之shared_ptr與右值引用的方法
智能指針的核心實現(xiàn)技術是引用計數(shù),每使用它一次,內部引用計數(shù)加1,每析構一次內部的引用計數(shù)減1,減為0時,刪除所指向的堆內存,今天通過本文給大家分享C++智能指針之shared_ptr與右值引用的方法,需要的朋友跟隨小編一起看看吧2021-07-07