C語言自定義類型

在學習C語言的時候,它有很多的自定義類型,例如:結構體,枚舉,聯合。。。
這些類型在我們的日常使用的時候,或多或少的都會遇見到,下面就係統的介紹一下這幾種類型。

一.結構體

1.結構體類型的聲明:通俗點來說,結構就是一些值的集合,這些值稱爲成員變量,結構體的每個成員可以是不同類型的成員變量。

如下所示:

struct goods
{
	char name[20]	//商品的名字
	int size;		//商品的大小
	char id[20];	//商品的編號
};

結構體使用的時候也可以省略掉結構體的標籤

struct 
{
	int a;
	char c;
	float f;
}name;

2.結構體自引用的書寫形式

struct Node
{
	int data;
	Node* next; //這裏必須加上*因爲這是一個指針
};

也可以寫成下面這樣的代碼

typedef struct Node
{
	int data;
	struct Node* next;
}Node;

3.結構體變量的定義和初始化

struct point
{
	int x;
	int y;
}p1;

struct point p1 = {x,y};

struct Node
{
	int data;
	struct point p;
	struct Node* next;
}n1 = {10,{4,5},nullptr};	//結構體嵌套初始化
strcut Node n2 = {20,{5,6},nullptr};

4.結構體內存對齊
當我們計算結構體的大小的時候,就必須要運用內存對齊規則

struct S3
{
	double d;  
	char c;   
	int i;
}; 

struct S4 
{ 
	char c1;  
	struct S3 s3;   
	double d; 
};

int main()
{
	printf("%d\n", sizeof(struct S3));	//16
	printf("%d\n", sizeof(struct S4));	//32
	system("pause");
	return 0;
}

5.結構體傳參

struct S
{
	int data[1000];
	int num;
};
struct S s = {{1,2,3,4},10000};

void Print(struct S* ps)
{
	std::cout << printf("%d\n",ps->num);
}
int main()
{
	Print(&s); //傳地址
}
//在這裏爲什麼要傳地址呢?
//因爲在函數傳參的時候,參數是需要進行壓棧的,如果傳遞給一個結構體對象的時候,
//結構體過大,參數壓棧的系統開銷比較大,所有會導致性能的下降。
//所以結構體傳參的時候要傳地址。

6.位段

  • 1.什麼是位段?
    位段的聲明和結構體是類似的,但是也有不同之處,主要有兩點。
  • (1).位段的成員必須是int,unsigned int 或者 signed int。
  • (2).位段的成員名後面有一個冒號和一個數字。
    如下所示:
struct A
{
	int a:2;
	int b:5;
	int c:10;
	int d:30;
};
//A就是一個位段,那麼位段A的大小是多少呢?
printf("%d\n",sizeof(struct A));
  • 2.位段的內存分配
  • (1)位段的成員可以是int,unsigned int ,signed int 或者是char類型。
  • (2)位段的空間是按照需要以4個字節(int)或者1個字節(char)的方式來進行開闢的。
  • (3)位段涉及很多不確定因素,位段是不跨平臺的,主要可移植程序應該避免使用位段。

C語言標準規定,位域的寬度不能超過它所依附的數據類型的長度。通俗地講,成員變量都是有類型的,這個類型限制了成員變量的最大長度,:後面的數字不能超過這個長度。
C語言標準還規定,只有有限的幾種數據類型可以用於位域。在 ANSI C 中,這幾種數據類型是 int、signed int 和 unsigned int(int 默認就是 signed int);到了 C99,_Bool 也被支持了。
但編譯器在具體實現時都進行了擴展,額外支持了 char、signed char、unsigned char 以及 enum 類型,所以上面的代碼雖然不符合C語言標準,但它依然能夠被編譯器支持。

  • 3.位段的跨平臺問題
  • (1)int 位段被當成有符號數還是無符號數是不確定的。
  • (2)位段中最大位的數目不能確定(16位機器最大爲16,32位機器最大爲32)
  • (3)位段中的成員在內存中是從左往右分配,還是從右往左分配不能確定。
  • (4)當一個結構包含兩個位段的時候,第二個位段比第一個位段要大,無法容納第一個位段的時候,是捨棄第一個位段,還是利用,這是不能確定的。

二.枚舉

1.枚舉類型的定義

enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fir,
	Sat,
	Sun
};
//上述定義的就是一個枚舉類型,{}中的內容是枚舉類型的可能用值,也叫做枚舉常量
//這些在取值都是有值的,默認是從1開始,一次遞增1,當然在定義的時候可以副賦初值。
enum Color
{
	Red = 1,
	Green = 2,
	Blue = 4
};

2.枚舉的優點

  • 1.增強代碼的可讀性和可維護性
  • 2.和#define定義的標識符比較枚舉有類型檢查,更加嚴謹
  • 3.防止了命名污染
  • 4.便於調試
  • 5.使用方便,一次可以定義多個常量。

3.枚舉的使用

enum Color
{
	Red = 1;
	Green = 2;
	Blue = 4
};
enum Color clr = Green;//只能拿枚舉常量給枚舉變量賦值,纔不會出現類型的差異

三.聯合(共用體)

1.聯合的定義
聯合是一種特殊的自定義類型,這種類型定義的變量也包含一系列的成員,特徵是這些成員共用一塊空間(所以聯合也叫共用體)
例如:

union Un
{
	char c;
	int i;
};
union Un u;
printf("%d\n",sizeof(u));//4

2.聯合的特點
聯合的成員是公用同一塊內存空間的,這樣一個聯合變量的大小,至少是最大成員的大小。

union Un
{
	char c;
	int i;
};
union Un u;

printf("%d\n", &(u.i));
printf("%d\n", &(u.c));

u.i = 0x11223344;
u.c = 0x55; 
printf("%x\n", u.i); 

3.聯合大小的計算

  • 1.聯合的大小至少是最大成員的大小
  • 2.當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍。
union Un1 
{ 
	char c[5];  
	int i; 
}; 
union Un2 
{ 
	short c[7];  
	int i;
}; 
//下面輸出的結果是什麼?
int main()
{
	printf("%d\n", sizeof(union Un1));//8
	printf("%d\n", sizeof(union Un2));//16
	system("pause");
}

面試題:
1.結構體的內存對齊規則:

  • 1.第一個成員在與結構體變量偏移量爲0的地址處。
  • 2.其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處
    對齊數 = 編譯器默認的一個對齊數與該成員大小的較小值
    vs中默認的值是8,Linux中默認的值是4
  • 3.結構體總大小爲最大對齊數(每個成員變量都有一個對齊數)的整數倍。
  • 4.如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數的整數倍。(包含嵌套結構體的對齊數)

2.爲什麼存在內存對齊?

  • 1.平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數據,某些硬件平臺只能在某些地址處取某些特定類型的數據,否則會拋出硬件異常。
  • 2.性能原因:數據結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,爲了訪問未對齊的內存,處理器需要做兩次內存訪問,而對齊的內存訪問僅需要一次訪問。

總體來說:結構體的內存對齊是拿空間換時間的做法。
我們在設計結構體的時候,既要滿足內存對齊,又要節省空間,所以應該讓佔用空間小的成員集中在一起。

3.設計結構體的時候,既要滿足內存對齊,又要節省空間,又該如何?

  • 1.讓佔用空間小的成員儘量集中在一起。
struct S1		//12
{
	char c1;
	int i;
	char c2;
};			
struct S2		//8
{
	char c1;
	char c2;
	int i;
}

S1和S2類型的成員一模一樣,但是S1和S2所佔空間的大小有了一些區別

  • 2.修改默認對齊數
#pragma pack(5)
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()

#pragma pack(1)
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()

int main()
{
	std::cout << sizeof(struct S1) << std::endl;
	std::cout << sizeof(struct S2) << std::endl;
	system("pause");
	return 0;
}

4.寫一個宏,計算結構體中某變量相對於首地址的偏移,並給出證明

#define FIND(student, e) (size_t)&(((student*)0) - >e)

struct student
{
	int a;
	int b;
	char c[30];
	int d;
};

int main()
{
	FIND(student, a); //等於0
	FIND(student, b);//等於4
}
  • (student*)0 //表示將常量0強制轉化爲student*型指針所指向的地址,當然也可以x,只是到最後還要減去這個x的值
  • ((student*)0)->e 表示在0地址處的結構體student的成員e
  • &(((student*)0)- >e)//表示取結構體指針(student*)0的成員e的地址,因爲該結構體的首地址爲0,所以其實就是得到了成員e距離結構體首地址的偏移量.
  • (size_t)//是一種數據類型,爲了便於不同系統之間移植而定義的一種無符號型數據,一般爲unsigned int
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章