C語言中常用的自定義數據類型(結構體、枚舉、聯合)

前言

在程序編寫的過程中,我們難免會遇到一些複雜的元素(例如學生:姓名、性別、學號)無法用單一的內置數據類型表示,於是就引入了自定義數據類型來描述這些複雜的元素。

C語言中常見的自定義數據類型主要有:結構體、枚舉、聯合(結構體中主要解釋位段

一:位段

數據的存取一般以字節爲單位,但某種情況下存儲一個數據不必用一個或多個字節(例如:“真”或“假”用0或1表示只需要1個bit位),正是基於這種考慮,C語言又提供了一種叫做位段的數據結構。

C語言允許在一個結構體中以位爲單位來指定其成員所佔內存長度, 這種以位爲單位的結構稱爲位段(位域), 位段可以把數據以位的形式緊湊的儲存,用較少的位數存儲數據,達到節省空間的目的。

1.1 位段的聲明

位段的聲明和結構體是類似的,但是有兩點不同:

  1. 位段的成員必須是int 、unsigned int、signed int、char
  2. 位段的成員名後有一個冒號和一個數字

舉個栗子:

struct S{
	// _a、_b、_c 一共佔用17個bit 存入一個整型(32bit)的空間中
	int _a:2;	
	int _b:5;
	int _c:10;
	// _d 佔用30bit(17+30>32) 得存儲在另一整型的空間空
	int _d:30;
};
// S中的成員需要佔用2個int的空間
printf("%d\n", sizeof(struct A)):輸出結果爲 8

注意:位段的寬度不能超過它所依附的數據類型的長度。通俗地講,成員變量都是有類型的,這個類型限制了成員變量的最大長度,冒號後面的數字不能超過這個長度。

1.2 位段的存儲

  1. 相鄰成員的類型相同時:

如果它們的位寬之和小於類型的sizeof 大小,那麼後面的成員緊鄰前一個成員存儲,直到不能容納爲止。

如果它們的位寬之和大於類型的sizeof 大小,那麼後面的成員將從新的存儲單元開始存儲

#include <stdio.h>
int main(){
    struct bs{
        unsigned m: 6;
        unsigned n: 12;
        unsigned p: 4;
    };
    
    printf("%d\n", sizeof(struct bs));
    return 0;
}

運行結果:4
  1. 相鄰成員的類型不同時:

不同的編譯器有不同的實現方案,GCC 會壓縮存儲,而 VC/VS 不會壓縮存儲

#include <stdio.h>
int main(){
    struct bs{
        unsigned m: 12;
        unsigned char ch: 4;
        unsigned p: 4;
    };
    
    printf("%d\n", sizeof(struct bs));
    return 0;
}
GCC運行結果:4	VS運行結果:12(三個成員按照各自的類型存儲)
  1. 成員之間穿插非位域成員:

如果成員之間穿插着非位域成員,那麼不會進行壓縮。

#include<stdio.h>
int main(){
	struct bs{
	    unsigned m: 12;
	    unsigned ch;
	    unsigned p: 4;
	};
	
	printf("%d\n", sizeof(struct bs));
	return 0;
}
運行結果:12

注:我們發現位域成員往往不佔用完整的字節,有時候也不處於字節的開頭位置,因此使用&獲取位域成員的地址是沒有意義的,C語言也禁止這樣做。地址是字節(Byte)的編號,而不是位(Bit)的編號。

1.3 位段的內存分配

  1. 位段的成員可以是int、unsigned int、signed int、char類型
  2. 位段的空間上是按照4個字節(int)或1個字節(char)的方式開闢的
  3. 位段涉及很多不確定因素,位段不跨平臺,注重可移植的程序應該避免使用位段

在這裏插入圖片描述
總結: 和結構體相比,位段可以達到同樣的效果,雖然可以很好的節省空間,但是有跨平臺問題的存在。

二:枚舉

枚舉顧名思義就是一一列舉,把可能的取值列舉出來,比如:星期、性別、月份、顏色等都可以使用枚舉類型。

1.1 枚舉的定義

1.枚舉的定義與結構體的聲明十分類似,不過我們要謹記中間的每個常量要用逗號隔開

enum Sex{
	// 以下都是枚舉常量
	MALE,
    FEMALE,
    UNKNOWN,
};
// 默認從0開始,一次增長1的進行賦值
// 如果我們對枚舉常量進行賦值,則後面的常量會從賦的值開始一次增長1
printf("MALE = %d,FEMALE = %d, UNKNOWN = %d\n", MALE, FEMALE, UNKNOWN);

輸出結果:0 1 2 

2.還可以使用枚舉類型來定義變量,但是定義的變量的值就必須是枚舉常量中出現的常量。

enum Sex{
	MALE,
	FEMALE,
	UNKNOWN,
};
int main(){
	// MALE是枚舉類型中的常量
    enum Sex sex = MALE;
    printf("sex = %d\n", sex);
}

運行結果:sex = 0

1.2 枚舉的優點

我們可以使用#define定義常量,爲什麼還要使用枚舉呢?

  • 枚舉的優點:

1.增加代碼的可讀性和可維護性
2.枚舉有類型檢查,更加嚴謹
3.使用方便,一次可以定義多個常量
4.防止命名污染(封裝)

三:聯合(共用體)

聯合體也叫共用體,是一種較爲特殊的自定義類型,這種類型定義的變量共用同一塊空間

3.1 聯合類型的聲明與使用

聯合體與結構體定義的語法完全一致,聯合大小是根據其中最大成員的大小決定的,它也可以有效節省空間。

注:最大成員大小若不是最大對齊數的整數倍時,就要對齊到最大對齊數的整數倍。

union Un{
	// 聯合體c和i共用int的四個字節
	char c;
    int i;
};
int main(){
    union Un un;
    printf("%lu\n",sizeof(un));  輸出:4
    un.i = 4;
    un.c = 'a';
    printf("%d\n", un.i);	輸出:97
}

由此可見:共用體所佔空間的確與結構體是不一樣的,並且當我們同時給兩個成員變量賦值時另一個成員變量會擾亂其他成員變量的賦值,由此可見它們確實是存儲在同一塊內存空間上的。

3.2 聯合的應用

  • IP地址兩種形式的轉換
union Ip{
    uint32_t a;
    struct{
        char d1;
        char d2;
        char d3;
        char d4;
    };
};
int main(){
    union Ip ip;
    ip.a = 0x1;
    printf("%d.%d.%d.%d\n", ip.d1,ip.d2,ip.d3,ip.d4);
}

輸出結果:1.0.0.0
  • 字節序的判斷

詳情點擊博客:大端存儲模式和小端存儲模式https://blog.csdn.net/Outtch_/article/details/105613040

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章