自定義結構體型別:struct使用詳解

2021-03-14 12:00:28

自定義結構體型別:struct使用詳解



一、結構體(struct)

在程式設計時,最重要的步驟之一就是選擇表示資料的方法。在許多的情況下,簡單變數甚至是陣列還不足以表示一個事物的屬性。為此,C語言提供了結構體變數(structure variable)提高表示資料的能力。
結構體是一些值的集合,這些值稱為成員變數,把一些基本型別的資料組合在一起,形成一個新的複雜資料型別。結構體中的每個成員可以是不同型別的變數。
掌握:
1、為結構體建立一個格式或樣式
2、宣告一個合適的結構體變數
3、存取結構變數的各個部分

1、結構體的宣告方法

結構體宣告常用形式:

struct  tag
{
	member-list;	//成員變數,
}variable-list;		//;不可省略

定義結構體的三種形式 (推薦使用第一種)

例如:描述一本書(包括:書名、作者、價格等)
第一種:只定義一個新的資料型別,沒有定義變數

struct book		//struct book 所起的作用相當於一般宣告中的 int 或 float 
{
	char title[30];
	char author[30];
	float price;	
};	//分號不能省略,沒有定義變數

struct book Jane_Eyre;	//在使用時再定義結構體變數 Jane_Eyre(英國文學名著《簡·愛》作家夏洛蒂·勃朗特)

第二種:定義新的資料型別同時定義變數

struct book
{
	char title[30];
	char author[30];
	float price;	
}Jane_Eyre;		//分號不能省略,同時定義變數
						//宣告的右花括號跟變數名

第三種:不完全宣告的結構體

struct 			//匿名結構體型別,省略了結構體標籤book
{
	char title[30];
	char author[30];
	float price;	
}Jane_Eyre;		//分號不能省略,同時定義變數

但是,如果打算多次使用結構模板,就要使用帶標記的形式;或者,使用typedef函數。

2、結構體的賦值

我們知道初始化變數和陣列的方法:

int sum =0;
int fibo[ 5 ] = { 1,1,2,3,5};

結構變數能不能這樣初始化呢?答案是 :yes
初始化結構變數與初始化陣列的語法語法類似:

方法一:定義變數的同時賦初始值

struct book
{
	char title[30];
	char author[30];
	float price;	
};

struct book Jane_Eyre = {"Jane_Eyre","Charlotte Bronte",35.5};

***************************************************************
struct book
{
	char title[30];
	char author[30];
	float price;	
} Jane_Eyre = {"Jane_Eyre","Charlotte Bronte",35.5};
***********************************************************************

使用一對{ }來對結構體進行初始化,各個初始化項用逗號隔開。
為了讓初始化項與結構體體中的各個成員關聯更加明顯,我們可以讓每個成員單獨佔一行。這樣做的目的只是為了提高可讀性,對於編譯器本身而言並沒有區別;

struct book masterwork ={
						"the Old Man and the Sea",
						"Hemingway",
						32.4
						};

方法二:逐個成員賦值

struct book Jane_Eyre = {
						.title = "Jane_Eyre",
						.price = 35.5;
						.author="Charlotte Bronte"
						};
*********************************************************
struct book Jane_Eyre = {
						.price = 35.5;
						};

1. 逐個成員賦值時,可以只初始化其中的某一個成員;
2. 逐個成員賦值時,賦值的順序可以是任意的;
3. 如果有多次賦值,最後一次賦值才是該成員變數真實的值;

方法三:整體賦值

struct book Jane_Eyre = {
						.title = "Jane_Eyre",
						.price = 35.5;
						.author="Charlotte Bronte"
						}//定義了一本書:烏托邦
struct book Utopia  =  Jane_Eyre;

1)相同資料型別結構體變數可以直接賦值

結論:

1)凡是基本資料型別,既可以定義時初始化,也可以先定義,再賦值
2)凡是構造型別,要麼在定義時初始化(定義的時候可以整體賦值);
不可以,先定義再以初始化的方式初始化;(如果定義完之後,只能單個的賦值)

3、結構體的指標

在講述結構體成員的存取之前,我們先來講述一下結構體的指標,因為在結構體成員存取時也可以通過結構體指標來存取;
喜歡用指標的人一般都樂於使用結構體指標,因為:

1. 就像指向陣列的指標比陣列本身更容易操控;
2. 傳遞給函數時,傳遞結構體指標效率更高;
3. 在表示資料的結構體中可能包含指向其他結構的指標

//可以宣告的同時定義
struct book	
{
	char title[30];
	char author[30];
	float price;	
} * book1,book2;
//也可以先宣告,後定義
struct book * book3 = & book2

和陣列不同,結構名並不是結構體的地址,因此要在結構名前加上 & 運運算元

4、結構體的存取

結構體的存取有兩種形式,一種是:結構體變數名 . 成員名 ;二種是:指標變數名 ->成員名

方法一:結構體變數名 . 成員名

struct book	
{
	char title[30];
	char author[30];
	float price;	
};

int main()
{
	struct book	book2 =  {"Jane_Eyre","Charlotte Bronte",35.5}printf("%s\n",book2.title);
	printf("%s\n",book2.author);
	printf("%f\n",book2.price);
	return 0;
}

方法二:指標變數名 ->成員名

int main()
{
	struct book	* book1 =  &book2;
	printf("%s\n",book1->title);
	printf("%s\n",book1->author);
	printf("%f\n",book1->price);
	return 0;
}

//  book1->title  == (*book1) . title == book2.title

第二種方法是最為常用的方法:使用 針變數名 ->成員名 ,在計算機內部會被轉化成 (*針變數名).成員名 的方式來執行,所以 book1->title 等價於 (*book1) . title 等價於 book2.title;
所以說這兩種方式是等價的

5、結構體記憶體對齊

首先掌握結構體記憶體的對齊規則:

  1. 第一個成員在與結構體變數偏移為0的地址處;
  2. 其他成員變數要對齊到 對齊數的整數倍;(對齊數:編譯器預設的對齊數與該成員大小的較少值)
  3. 結構體總大小為最大對齊數(每個成員變數都有一個對齊數)的整數倍
  4. 如果巢狀了結構體的情況,巢狀的結構體對齊到自己最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數的整數倍(包括巢狀結構體的對齊數);

知道了理論,下面開始舉例
以下面為例:

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

第一步:第一個成員在與結構體變數偏移為0的地址處;![在這裡插入圖片描述](https://i

在這裡插入圖片描述
第二步:其他成員變數要對齊到 對齊數的整數倍;(對齊數:編譯器預設的對齊數與該成員大小的較少值)
在這裡插入圖片描述
第三步: 結構體總大小為最大對齊數(每個成員變數都有一個對齊數)的整數倍
在這裡插入圖片描述
如果巢狀了結構體的情況,巢狀的結構體對齊到自己最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數的整數倍(包括巢狀結構體的對齊數)。

記憶體對齊的意義:

1、平臺原因(移植原因):各個硬體平臺對儲存空間的處理上有很大的不同。一些平臺對某些特定型別的資料只能從某些特定地址開始存取。比如有些架構的CPU在存取一個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證位元組對齊

2、效能原因: 資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。 按照適合其平臺要求對資料存放進行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設為32位元系統)如果存放在偶地址開始的地方,那 麼一個讀週期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該32bit資料。顯然在讀取效率上下降很多。

設計結構體變數時:將佔用空間小的成員集中在一起,在一定程度可以避免記憶體的浪費。

在這裡插入圖片描述
可見,相同的成員變數,不同的順序,也會影響結構體的大小;

6、結構體位元欄

位元欄的宣告和結構體的宣告基本相同,但有兩個不同;
1、位元欄的成員必須是整形家族的(int 、unsigned int、singned int、char)
2、位元欄的成員後有一個冒號和一個數位;

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

一個int 型別佔用4個位元組(共32個位元位),int _a:2 申請了一個int型的變數,:6 表示儲存這個資料的空間只佔用了6個位元位。所以位元欄的使用可以使得結構體的空間變小,但伴隨而來的就是每個成員變數儲存空間的改變,和平臺可移植性的問題。

正常情況下每個int型變數可以儲存的資料是2^32個,但int _a:2實際上只使用了兩個位元位,實際可以表示的資料為2^2個;
在這裡插入圖片描述
位元欄的跨平臺問題:
1、int位元欄被當成有符號數還是無符號數是不確定的;
2、位元欄中最大位的數目不能確定(16位元機器最大16,32位元機器最大32,如果在32位元機器上上使用一個超過16位元的資料,移植到16位元機器中時,就有可能會出現問題)
3、位元欄中的成員在記憶體中是從左往右分配,還是從右往左分配沒有一個標準的定義(因此只畫出記憶體使用位元位數的示意圖,具體使用情況視平臺而定)
4、當一個結構博包含兩個位元欄,第二個位元欄成員比較大時,無法容納於第一個位元欄剩餘位時,剩餘的位是捨棄還是利用,是不確定的。

結論:

跟結構體相比,位元欄可以達到相同的效果。可以在一定程度上節省空間,但是會有跨平臺的問題出現。