C語言自定義類型詳解(結(jié)構(gòu)體、枚舉、聯(lián)合體和位段)
前言

一、結(jié)構(gòu)體
1、結(jié)構(gòu)體類型的聲明
當(dāng)我們想要描述一個復(fù)雜變量——學(xué)生,可以這樣聲明。
✒️代碼展示:
struct Stu
{
char name[20];//名字
int age;//年齡
char sex[5];//性別
char id[20];//學(xué)號
}s1;//分號不能丟
int main()
{
struct Stu s2;
return 0;
}
🔖解釋說明:
- struct是結(jié)構(gòu)體的關(guān)鍵字
- Stu是結(jié)構(gòu)體標簽名
- struct Stu是結(jié)構(gòu)體的類型
- 大括號內(nèi)包圍的是結(jié)構(gòu)體成員變量的列表
- 變量s1是類型為struct Stu的全局變量,變量s2是該類型的局部變量
在聲明結(jié)構(gòu)時,也有特殊的聲明,比如不完全聲明——匿名結(jié)構(gòu)體類型,省略掉了結(jié)構(gòu)體標簽。
✒️代碼展示:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
那么,此時,問題來了!
在上面的代碼基礎(chǔ)上,p = &x,這樣的代碼合理嗎?

而且,像這樣的匿名結(jié)構(gòu)體類型只能使用一次,因為沒有標簽名。
2、結(jié)構(gòu)體的自引用
眾所周知,函數(shù)可以自己調(diào)用自己,叫做函數(shù)的遞歸,那么結(jié)構(gòu)體是否也有自己引用自己呢?如果有又是如何實現(xiàn)的呢?
✒️代碼展示:
//代碼一:
struct N
{
int data;
struct N next;
};
//代碼二:
struct Node
{
int data;
struct Node* next;
};
//代碼三:
typedef struct
{
int data;
Node* next;
}Node;
//代碼四:
typedef struct Node
{
int data;
struct Node* next;
}Node;
🔖解釋說明:
代碼一:
這樣自引用是不正確的。當(dāng)想要計算struct N類型所占空間大小時,就會出現(xiàn)瘋狂套娃現(xiàn)象,無法計算結(jié)果,因此是不可取的
代碼二:
這才是自引用的正確打開方式。data中存放的數(shù)據(jù),next中存放著下一個struct Node類型數(shù)據(jù)的地址
代碼三:
該代碼想要實現(xiàn)匿名結(jié)構(gòu)體的自引用,但這樣做是不可取的。因為需要完整的定義了該結(jié)構(gòu)體才可以重新命名為Node。然而定義的成員列表中又有Node*,先后問題產(chǎn)生了。
代碼四:
可以通過這種重定義方式實現(xiàn)自引用。
3、結(jié)構(gòu)體變量的定義和初始化
既然已經(jīng)有了結(jié)構(gòu)體類型,那么對其定義和初始化就變得非常的簡單
✒️代碼展示:
struct Point
{
int x;
int y;
}p1; //聲明類型的同時定義變量p1
struct Point p2; //定義結(jié)構(gòu)體變量p2
//初始化:定義變量的同時賦初值。
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)體嵌套初始化
4、結(jié)構(gòu)體內(nèi)存對齊
掌握了結(jié)構(gòu)體的基本使用,還應(yīng)當(dāng)重點了解結(jié)構(gòu)體內(nèi)存對齊問題從而計算結(jié)構(gòu)體的大小,這是一個關(guān)于結(jié)構(gòu)體的重點考點

結(jié)構(gòu)體的對齊規(guī)則:
- 第一個成員在與結(jié)構(gòu)體變量偏移量為0的地址處。
- 其他成員變量需要對齊到對齊數(shù)的整數(shù)倍的地址處。
對齊數(shù) = 編譯器默認的一個對齊數(shù)與該成員大小的較小值。
VS中默認的值為8,Linux沒有默認對齊數(shù)- 結(jié)構(gòu)體總大小為最大對齊數(shù)的整數(shù)倍。
- 當(dāng)嵌套結(jié)構(gòu)體時,嵌套的結(jié)構(gòu)體對齊需要到自己的最大對齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對齊數(shù)的整數(shù)倍(包含嵌套結(jié)構(gòu)體的對齊數(shù))。
✒️代碼展示:
//練習(xí)1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
//練習(xí)2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//練習(xí)3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//練習(xí)4-結(jié)構(gòu)體嵌套問題
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
👁效果展示:

🔖解釋說明:
結(jié)構(gòu)體類型struct S1和struct S2兩者的成員組成是一樣的,但是定義順序有所差別,后者與前者相比將占用空間小的變量集中在了一起,導(dǎo)致兩者在遵循結(jié)構(gòu)體對齊條件下,所占內(nèi)存大小不一樣。做個對比吧!

結(jié)構(gòu)體類型struct S3和struct S4是另外兩個典型例子,后者嵌套前者。

簡而言之,該做法就是為了拿空間換取時間
如果。。。

另外。。。
結(jié)構(gòu)在對齊方式不合適的時候,我么可以自己更改默認對齊數(shù)。
這里我們將使用預(yù)處理指令#pragma來改變默認對齊數(shù)
✒️代碼展示:
#include <stdio.h>
#pragma pack(8)//設(shè)置默認對齊數(shù)為8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的默認對齊數(shù),還原為默認
#pragma pack(1)//設(shè)置默認對齊數(shù)為1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的默認對齊數(shù),還原為默認
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
👁效果展示:

🔖解釋說明:

5、結(jié)構(gòu)體傳參
✒️代碼展示:
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//結(jié)構(gòu)體傳參
void print1(struct S s)
{
printf("%d\n", s.num);
}
//結(jié)構(gòu)體地址傳參
void print2(struct S* ps)
{
printf("%d\n", ps->data[2]);
}
int main()
{
print1(s); //傳結(jié)構(gòu)體
print2(&s); //傳地址
return 0;
}
👁效果展示:

🔖解釋說明:
函數(shù)傳參的時候,參數(shù)是需要壓棧,會有時間和空間上的系統(tǒng)開銷。
如果傳遞一個結(jié)構(gòu)體對象的時候,結(jié)構(gòu)體過大,參數(shù)壓棧的的系統(tǒng)開銷比較大,導(dǎo)致性能的下降。比如在這里,如果直接傳值s的話,由于結(jié)構(gòu)體中創(chuàng)建了一個很大的數(shù)組data,導(dǎo)致結(jié)構(gòu)體過大,傳參時浪費的內(nèi)存空間很大,效率低下。但是如果傳址&s的話,作為一個指針,占四個字節(jié),極大提高了運行效率。
簡而言之,結(jié)構(gòu)體傳參時,傳結(jié)構(gòu)體的地址更好
二、位段
1、位段的定義
位段,C語言允許在一個結(jié)構(gòu)體中以位為單位來指定其成員所占內(nèi)存長度,這種以位為單位的成員稱為“位段”或稱“位域” 。利用位段能夠用較少的位數(shù)存儲數(shù)據(jù)。
位段的聲明和結(jié)構(gòu)是類似的,有兩個不同:
- 位段的成員必須是 int、unsigned int 、signed int、char 。
- 位段的成員名后邊有一個冒號和一個數(shù)字(指該成員占的比特位)。
✒️代碼展示:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
2、位段的內(nèi)存分配
位段的內(nèi)存分配規(guī)則:
- 位段的成員可以是 int、unsigned int、signed int或者char (屬于整形家族)類型
- 位段的空間上是按照需要以==4個字節(jié)( int )或者1個字節(jié)( char )==的方式來開辟的。
- 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應(yīng)該避免使用位段。
✒️代碼展示:
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
}
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
🔖解釋說明:
在VS編譯器中開辟了空間以后,先使用低地址再使用高地址。并且剩余的比特位不夠下一個變量存儲時,那這一片空間將會被浪費。

簡而言之,跟結(jié)構(gòu)相比,位段可以達到同樣的效果,但是可以很好的節(jié)省空間,但是有跨平臺的問題存在。
3、位段的應(yīng)用

🔖解釋說明:
上圖是網(wǎng)絡(luò)上IP數(shù)據(jù)包的格式,當(dāng)你想要在網(wǎng)絡(luò)上發(fā)一條消息給你的好友,信息是需要進行分裝的,消息作為數(shù)據(jù)只是傳輸?shù)囊徊糠?,還有一部分傳輸?shù)氖欠盅b中的其他信息。比如4位版本號,4位首部長度,這些信息只需要4個bit,如若不使用位段,直接每個部分一個整形的給空間,就會造成空間的大量浪費。
三、枚舉
1、枚舉類型的定義
在數(shù)學(xué)和計算機科學(xué)理論中,一個集的枚舉是列出某些有窮序列集的所有成員的程序,或者是一種特定類型對象的計數(shù)。這兩種類型經(jīng)常(但不總是)重疊。枚舉在日常生活中很常見,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一個枚舉。
2、枚舉的優(yōu)點
枚舉的優(yōu)點:
- 代碼的可讀性變高和可維護性變強
- 和#define定義的標識符相比較枚舉更加嚴謹,因為有類型檢查。
- 防止命名污染的現(xiàn)象
- 方便調(diào)試,且使用方便,可以一下子定義很多常量
3、枚舉的使用
枚舉的說明與結(jié)構(gòu)和聯(lián)合相似, 其形式為:
enum 枚舉名
{
標識符[=整型常數(shù)],
標識符[=整型常數(shù)],
...
標識符[=整型常數(shù)]
} 枚舉變量;
如果枚舉沒有初始化,即省掉"=整型常數(shù)"時, 則從第一個標識符開始,順次賦給標識符0, 1, 2, …但當(dāng)枚舉中的某個成員賦值后,其后的成員按依次加1的規(guī)則確定其值。
✒️代碼展示:
//代碼1
enum Num1
{
x1,
x2,
x3,
x4
}x;
//代碼2
enum Num2
{
y1,
y2 = 0,
y3 = 50,
y4
};
int main()
{
printf("%d %d %d %d\n", x1, x2, x3, x4);
printf("%d %d %d %d\n", y1, y2, y3, y4);
return 0;
}
👁效果展示:

注意:
- 枚舉中每個成員(標識符)結(jié)束符是==","== 不是";", 最后一個成員可省略","。
- 初始化時可以賦負數(shù), 以后的標識符仍依次加1。
- 枚舉變量只能取枚舉說明結(jié)構(gòu)中的某個標識符常量。
- 枚舉值是常量,不是變量,不能在程序中用賦值語句再對它賦值(比如上面的代碼出現(xiàn)y3 = 3; ❎)。
- 只能把枚舉值賦予枚舉變量,不能把元素的數(shù)值直接賦予枚舉變量,除非進行了強制類型轉(zhuǎn)換(比如上面的代碼出現(xiàn)x = x2✔️ x = 1❎x = (enum Num1)1✔️)
四、聯(lián)合體(共用體)
1、聯(lián)合體的定義
需要使幾種不同類型的變量存放到同一段內(nèi)存單元中。也就是使用覆蓋技術(shù),幾個變量互相覆蓋。這種幾個不同的變量共同占用一段內(nèi)存的結(jié)構(gòu),在C語言中,被稱作“共用體”類型結(jié)構(gòu),簡稱共用體,也叫聯(lián)合體。
2、聯(lián)合體的特點
聯(lián)合的成員是共用同一塊內(nèi)存空間的,一個聯(lián)合變量的大小,至少是最大成員的大?。ㄒ驗槁?lián)合至少得有能力保存最大的那個成員)
✒️代碼展示:
//聯(lián)合類型的聲明
union Un
{
char c;
int i;
};
//聯(lián)合變量的定義
union Un un;
int main()
{
//例①
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
//例②
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
return 0;
}
👁效果展示:

🔖解釋說明:
通過例①的結(jié)果,我們可以直觀發(fā)現(xiàn)成員變量c和成員變量i共用地址
例②更加證實這一點,由于大小端存儲,變量i是以44 33 22 11這樣的順序存儲的,因為變量c與其公用地址,因此55將44覆蓋,在內(nèi)存中變量i為55 33 22 11,打印出來為11 22 33 55
聯(lián)合體的相關(guān)應(yīng)用:
在之前我們已經(jīng)學(xué)會了判斷計算機大小端的方法,這里可以通過共用體的特點來實現(xiàn)
#include <stdio.h>union Un{ char c; int i;}num;int main(){ num.i = 1; if(num.c == 1) { printf("小端存儲") } else { printf("大端存儲") } return 0;}
向成員變量i中存放一個1,查看成員變量c的值,由于該變量是char類型,因此只訪問了第一個字節(jié)。
3、聯(lián)合體的大小計算
聯(lián)合體大小計算規(guī)則:
聯(lián)合的大小至少是最大成員的大小。當(dāng)最大成員大小不是最大對齊數(shù)的整數(shù)倍的時候,就要對齊到最大對齊數(shù)的整數(shù)倍。
✒️代碼展示:
#include <stdio.h>
union Un
{
char c;
int i;
}num;
int main()
{
num.i = 1;
if(num.c == 1)
{
printf("小端存儲")
}
else
{
printf("大端存儲")
}
return 0;
}
👁效果展示:

總結(jié)
到此這篇關(guān)于C語言自定義類型的文章就介紹到這了,更多相關(guān)C語言自定義類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Visual C++中Tab View的多種實現(xiàn)方法
這篇文章主要介紹了Visual C++中Tab View的多種實現(xiàn)方法,包括了CTabCtrl控件、CSheetCtrl標簽選擇窗口以及靜態(tài)分割窗口等實現(xiàn)Tab View的方法,需要的朋友可以參考下2014-10-10
C語言中的abs()函數(shù)和exp()函數(shù)的用法
這篇文章主要介紹了C語言中的abs()函數(shù)和exp()函數(shù)的用法,是C語言入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-08-08
C++算法實現(xiàn)leetcode 1252奇數(shù)值單元格數(shù)目
這篇文章為大家主要介紹了C++實現(xiàn)leetcode 1252奇數(shù)值單元格的數(shù)目題解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08

