自定義型別:結構體,列舉,聯合(C語言)
- 前言
- 結構體
- 結構體型別的宣告
- 結構體的自參考
- 結構體變數的定義和初始化
- 結構體記憶體對齊
- 結構體傳參
- 結構體實作位段(位段的填充&可移植性)
- 位段的跨平臺問題
- 列舉
- 列舉型別的定義
- 列舉的優點
- 列舉的使用
- 聯合
- 聯合型別的定義
- 聯合的特點
- 聯合大小的計算
前言
在C語言中有一些內置的型別:整型(int long short……),當然也有自定義型別,這次我們介紹關于常見的自定義型別(結構體、列舉、聯合),
結構體
結構體我們在前面簡單介紹過,是用來描述復雜物件的,比如:人,我們在描述人時,不能僅僅通過整型或者浮點型來描述所謂的人,我們需要通過一系列的型別來描述人這個復雜物件,比如:年齡,身高,體重,地址,電話等等,因此我們需要自己創建一個型別,這個型別就是結構體,
結構體型別的宣告
我們如何申明結構體呢?
struct tag
{
member-list;
}variable-list;
struct是結構體關鍵字,tag是標簽名,member-list是成員串列,variable-list是變數串列,我們來看例子:
struct Book
{
char name[20];
char author[20];
int price;
}B1;
我們創建了一個Book的結構體,結構體中有name[20],author[20],price三個成員,B1就是一個創建的變數,
結構體的自參考
什么是結構體的自參考?自己型別的物件,要找到自己內部同一個型別的另一個物件,就叫自參考,在資料結構中我們會學到鏈表,鏈表的節點就是采用的結構體的自參考,
struct Node
{
int date;
struct Node* next;
};
節點會分為兩個區域,資料域和指標域,資料域是用來存放資料的,指標域用來存放地址,date就是存放資料,next就是存放指標,而指標的型別就是struct Node這個型別,
結構體變數的定義和初始化
結構體變數的定義和初始化相對來說就比較的簡單:
struct Point
{
int x;
int y;
}p3 = { 5,6 }, p4 = {7,8};
struct Point p2 = {1,2};
int main()
{
struct Point p1 = {3,4};
return 0;
}
我們創建了4個變數:p1,p2,p3,p4,這4個變數都是struct Point型別的,區別就在于p1是區域變數,而p2,p3,p4是全域變數,
結構體記憶體對齊
我們先看一個題目:
#include<stdio.h>
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
char c1;
char c2;
int a;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
列印結果是什么呢?
有同學會說,char是一個位元組,int是兩個位元組,s1和s2就是int前后位置發生了變化,所以列印結果都是6,那么真的是這樣嘛?

我們看到列印結果都不是6,為什么呢?這就涉及到了結構體記憶體對齊的知識,
我們先介紹關于結構體記憶體對齊的規則:
- 結構體的第一個成員永遠放在結構體起始位置偏移量為0的位置,
- 結構體成員從第二個開始,總是放在偏移量為一個對齊數的整數倍處,
- 結構體的總大小必須是各個成員的對齊數中最大那個對齊數的整數倍,
我們先介紹一下兩個名詞:偏移量和對齊數, - 如果嵌套了結構體的情況,被嵌套的結構體對齊到自己最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數的整數倍,
偏移量:
假設這是我們的記憶體單元:

我們假設結構體變數從0開始創建,那么起始位置偏移量為0還是0位置,也就是說從0開始放入,偏移量為4也就是4的位置

對齊數:
對齊數是編譯器默認的對齊數和變數自身大小的較小值,
舉個例子:
在VS中默認對齊數是8,如果第二個成員是int型大小為4,那么較小值就是4,那么真正的對齊數就是4,
現在我們來看例題:
我們看如果創建S1型別的變數所占的記憶體:

我們從0位置開始存放,第一個成員是char,char是占一個位元組,那么我們將地址“0”分配給char型別,第二個成員是int型別,那么我們用VS編譯器的對齊數跟4比較(我用的是VS編譯器),4是較小值,也就是說,int型別放在偏移量為4的倍數的位置,1不是,2不是,3不是,4是的,所以放在地址為“4”的位置并且放4個單元記憶體,第三個成員是char,跟8比較,較小值是1,也就是char放在偏移量為1的倍數的位置,8是1的倍數,所以char放在地址“8”的位置,但是并不是struct S1就占9個位元組,還有第三個條件,我們將各個成員比較,較大者為int型,也就是4個位元組,那么,總大小必須是4的倍數,也就是12個位元組,如果聽懂了的話可以自己分析一下struct S2,
結構體傳參
我們先看一個例子:
#include<stdio.h>
struct S
{
int data[1000];
int num;
};
void print1(struct S tmp)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", tmp.data[i]);
}
printf("\nnum=%d\n", tmp.num);
}
int main()
{
struct S s = { {1,2,3,4,5,6,7,8,9,10},100 };
print1(s);
return 0;
}
我們利用print1()函式列印了s的資訊,我們可以看到,我們是將s的值傳給了tmp,也就是說我們又重新創建了一個struct S型別的結構體,然后列印重新創建結構體的內容,這樣就會導致空間的浪費,那么還有什么方法呢?
#include<stdio.h>
struct S
{
int data[1000];
int num;
};
void print2(struct S *ps)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ps->data[i]);
}
printf("\nnum=%d\n", ps->num);
}
int main()
{
struct S s = { {1,2,3,4,5,6,7,8,9,10},100 };
print2(&s);
return 0;
}
print2()函式采用的是傳結構體的地址,這樣就只需要創建一個地址空間來存放目標的地址,然后在列印結構,效率就會遠大于print1()函式,其實有點類似于傳值和傳址,所以在遇到結構體時一般采用傳址操作,因為結構體包含很多型別,往往空間會比較大,
結構體實作位段(位段的填充&可移植性)
什么是位段?
- 位段的成員必須是int、unsigned int或signed int(char型別也可以),
- 位段的成員名后邊有一個冒號和一個數字,
我們來看一個例子:
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
這就是一個位段,那么位段是干什么用的呢?位段的作用其實就是用來節省空間的,“2”是分配給"_a"2個bit位,同理"5"是5個bit位,那么具體是如何分配的呢?以struct A為例:當我們創建時,編譯器先看到int _a,那么開辟4個位元組(32個bit位),_a需要2個,_b需要5個,_c需要10個,此時還剩15個bit位,無法分配給_d,因此在開辟4個位元組,給_d使用因此,此時struct A的總大小位8個位元組,
通過例子總結出位段的記憶體分配:
- 位段的成員可以是 int unsigned int signed int 或者是 char (屬于整形家族)型別
- 位段的空間上是按照需要以4個位元組( int )或者1個位元組( char )的方式來開辟的,
- 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程式應該避免使用位段,
位段的跨平臺問題
位段是不具備跨平臺的,
- int 位段被當成有符號數還是無符號數是不確定的,
- 位段中最大位的數目不能確定,(16位機器最大16,32位機器最大32,寫成27,在16位機器會出問題,
- 位段中的成員在記憶體中從左向右分配,還是從右向左分配標準尚未定義,
- 當一個結構包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,這是不確定的,
列舉
列舉顧名思義就是一一列舉,把可能的取值一一列舉,比如我們現實生活中:一周的星期一到星期日是有限的7天,可以一一列舉,性別有:男、女、保密,也可以一一列舉,月份有12個月,也可以一一列舉顏色也可以一一列舉,這里就可以使用列舉了,一般列舉是列舉有限多個,無限多個就不會采用列舉了
列舉型別的定義
我們看例子:
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 都是列舉型別, {}中的內容是列舉型別的可能取值,也叫 列舉常量 ,這些可能取值都是有值的,默認從0開始,一次遞增1,當然也可以自定義列舉常量的數值,
enum Color
{
RED=1,
GREEN=2,
BLUE=4
};
列舉的優點
我們在前面常量中學過使用#define可以定義常量,那么為什么還要有列舉常量呢?
列舉的優點:
- 增加代碼的可讀性和可維護性
- 和#define定義的識別符號比較列舉有型別檢查,更加嚴謹,
- 防止了命名污染(封裝)
- 便于除錯
- 使用方便,一次可以定義多個常量
列舉的使用
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color c = GREEN;
if (c == GREEN)
{
printf("綠色\n");
}
return 0;
}
列舉的使用就是利用列舉的常屬性進行邏輯的運算,當然這只是一方面,通過列舉的使用可以使代碼的可讀性提高,維護起來也相對方便,
聯合
聯合也是一種特殊的自定義型別 這種型別定義的變數也包含一系列的成員,特征是這些成員公用同一塊空間(所以聯合也叫共用體),
聯合型別的定義
我們看例子:
union Un
{
char c;
int i;
};
這就是聯合體的定義,我們可以看到聯合體跟結構體很像但是,在記憶體分配上是不一樣的,這就是聯合體的特點,
聯合的特點
聯合的成員是共用同一塊記憶體空間的,這樣一個聯合變數的大小,至少是最大成員的大小(因為聯合至少得有能力保存最大的那個成員),什么意思呢?我們拿上面的例子舉例:
#include<stdio.h>
union Un
{
char c;
int i;
};
int main()
{
union Un u = { 0 };
printf("%d\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
return 0;
}

通過列印我們可以看到,聯合體大小為4,并且每個成員的地址都是聯合體的地址,這就意味著每個成員是共用記憶體的,

所以當c和i相互制約,當兩者其中改變時(c所在的記憶體發生改變),會影響到另一個,
聯合大小的計算
- 聯合的大小至少是最大成員的大小,
- 當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍,
比如:
#include<stdio.h>
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
我們看Un1,char c[5]占5個位元組,int i占4個位元組,所以Un1為5個位元組,但是第一個是char型別,對齊數是1;第二個是int,對齊數是4,最大對齊數是4,所以Un1大小應為4的倍數,所以Un1大小為8,同理Un2,short c[7]占14個位元組,int i占4個位元組,所以Un2為14個位元組,但是short對齊數為2,int對齊數為4,所以Un2應為4的倍數,所以Un2為16,

關于自定義型別涉及的內容就是這么多了,希望能給各位一些幫助,如果有什么建議可以私信,我也會及時改正,謝謝大家
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302780.html
標籤:其他
上一篇:值得收藏的<計數排序>講解
