【C語言】自定義類型:結構體,枚舉,聯合小結

結構體

結構是一些值的集合,這些值被稱爲成員變量。結構的每個成員可以是不同類型的變量。

  • 結構的聲明

struct tag
{
	member-list;
}variable-list;
//描述一個學生
struct Stu
{
	//成員變量、成員列表
	char name[20];//名字
	int age;//年齡
	char sex[5];//性別
	char id[20];//學號
};//分號不能丟

特殊的聲明

在聲明結構的時候,可以不完全的聲明
比如:
匿名結構體類型

struct
{
	int a;
	char b;
	float c;
}x;

struct
{
	int a;
	char b;
	float c;
}a[20], *p;

上面的兩個結構體在聲明的時候省略掉了結構體標籤(tag)。


在上面代碼的基礎上,下面的代碼合法嗎?

p = &x;

警告:
編譯器會把上面的兩個聲明當成兩個完全不同的兩個類型。
所以是非法的

  • 結構的成員

結構的成員可以是標量、數組、指針,甚至是其他的結構體。

結構體成員的訪問

1.結構體變量的成員是通過點操作(.)訪問的。點操作符接受兩個操作數
例如:

我們看到s有成員name和age;
訪問s的成員

struct S s;
strcpy(s.name, "zhangsan");//使用.訪問name成員
s.age = 20;//使用.訪問age成員

結構體訪問指向變量的成員,有時候我們得到的不是一個結構體變量,而是指向一個結構體的指針,那該如何訪問成員,如下:

struct S
{
	char name[20];
	int age;
}s;

void print(struct S* ps)
{
	printf("name = %s age = %d\n", (*ps).name, (*ps).age);
	printf("name = %s age = %d\n", ps->name, ps->age);
}
  • 結構的自引用

在結構體中包含一個類型爲該結構本身的成員

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

注意:

typedef struct Node
{
	int data;
	struct Node* next;
}Node;
  • 結構體的不完整聲明
struct B;
struct A
{
	int _a;
	struct B* pb;
};
struct B
{
	int _b;
	struct A* pa;
};
  • 結構體變量的定義和初始化

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

注意:
typedef struct Node
{
	int data;
	struct Node* next;
}Node;

結構體的不完整聲明
struct B;
struct A
{
	int _a;
	struct B* pb;
};
struct B
{
	int _b;
	struct A* pa;
};

結構體變量的定義和初始化
struct Point
{
	int x;
	int y;
}p1;                   //聲明類型的同時定義變量p1
struct Point p2;       //定義結構體變量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 };           //結構體嵌套初始化

struct Node n2 = { 20, { 5, 6 }, NULL };//結構體嵌套初始化
  • 結構體內存對齊

//練習1
struct S1
{
	char c1;
	int i;
	char c2;
};
printf("%d\n", sizeof(struct S1));
//練習2
struct S2
{
	char c1;
	char c2;
	int i;
};
printf("%d\n", sizeof(struct S2));
//練習3
struct S3
{
	double d;
	char c;
	int i;
};
printf("%d\n", sizeof(struct S3));
//練習4-結構體嵌套問題
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
printf("%d\n", sizeof(struct S4));

如何計算

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

爲什麼存在內存對齊?

大部分參考資料都如下所說:
1.平臺原因(移植原因):
不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

2.性能原因:
數據結構(尤其是棧)應該儘可能地在自然邊上對齊。
原因在於,爲了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。

  • 總體來說:

結構體的內存對齊是拿空間來換取時間的做法。

那在設計結構體的時候,我們既要滿足對齊,又要節省空間,如何做到:

讓佔用空間小的成員儘量集中在一起。

//例如:
struct S1
{
	char c1;
	int i;
	char c2;
};

struct S2
{
	char c1;
	char c2;
	int i;
};

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

修改默認對齊數

#include <stdio.h>
#include <stdlib.h>

#pragma pack(8)     //設置默認對齊數爲8
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()      //取消設置的默認對齊數,還原爲默認

#pragma pack(1)     //設置默認對齊數爲8
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()      //取消設置的默認對齊數,還原爲默認
int main()
{
	//輸出的結果是什麼?
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	system("pause");
	return 0;
}

 

  • 結構體傳參

struct S
{
	int data[1000];
	int num;
};
struct S s = { { 1, 2, 3, 4 }, 1000 };
//結構體傳參
void print1(struct S s)
{
	printf("%d\n", s.num);
}
//結構體地址傳參
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s); //傳結構體
	print2(&s); //傳地址
	return 0;
}

上面的print1和print2函數哪個好些?
很明顯首選print2函數。

原因:

講解函數棧幀的時候,我們講過函數傳參的時候,參數是需要壓棧的。
如果傳遞一個結構體對象的時候,結構體過大,參數壓棧的系統開銷比較大,所以會導致性能的下降。

  • 小結:

結構體傳參的時候,要傳結構體的地址。

 

  • 位段

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

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

比如:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

A就是一個位段類型

那位段A的大小是多少呢?

#include <stdio.h>
#include <stdlib.h>

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

int main()
{
	printf("%d\n", sizeof(struct A));
	system("pause");
	return 0;
}

位段的內存分配

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

位段的跨平臺問題:

1. int位段被當成有符號數還是無符號數是不確定的。
2.位段中最大位的數目不能確定。(16位機器 最大16,32位機器最大32,寫成27, 在16位機器會出問題。)
3.位段中的成員在內存中從左向右分配,還是從右向左分配標準尚未定義。
4.當一個結構包含兩個位段,第二個位段成員比較大,無法容納於第 - 個位段剩餘的位時,是捨棄剩餘的位還是利用,這是不確定的。

  • 總結:

跟結構相比,位段可以達到同樣的效果,可以很好的節省空間,但是有跨平臺的問題存在。

 

枚舉

枚舉顧名思義就是一一例舉。

把可能的取值一一例舉。

  • 枚舉類型的定義

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
};

枚舉的優點

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

枚舉的使用

enum Color//顏色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};
enum Color clr = GREEN;//只能拿枚舉常量給枚舉變量賦值,纔不會出現類型的差異。
clr = 5;

 

聯合(共用體)

聯合類型的定義

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

比如:

//聯合類型的聲明
union Un
{
	char c;
	int i;
};

//聯合變量的定義
union Un un;
//計算連個變量的大小
printf("%d\n", sizeof(un));

聯合的特點

聯合的成員是共用同一塊內存空間的,這樣一個聯合變量的大小,至少是最大成員的大小(因爲聯合至少得有能力保存最大的那個成員)。

  • 判斷當前計算機的大小端存儲:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	system("pause");
	return 0;
}
#include <stdio.h>
#include <stdlib.h>

int CheckSys()
{
	int a = 1;
	return *(char *)&a;
}

int main()
{
	if (CheckSys() == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	system("pause");
	return 0;
}

聯合大小的計算

聯合的大小至少是最大成員的大小
當最大成員的大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍

例如:

#include <stdio.h>
#include <stdlib.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));
	system("pause");
	return 0;
}

聯合和結構體的巧妙使用:

#include <stdio.h>
#include <stdlib.h>

union ip_addr
{
	unsigned long addr;
	struct
	{
		unsigned char c1;
		unsigned char c2;
		unsigned char c3;
		unsigned char c4;
	}ip;
};

int main()
{
	union ip_addr my_ip;
	my_ip.addr = 176238749;
	printf("%d.%d.%d.%d\n", my_ip.ip.c4, my_ip.ip.c3, my_ip.ip.c2, my_ip.ip.c1);
	system("pause");
	return 0;
}

 

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