目錄
?
一、結構體
為什么會有結構體呢?
結構體的宣告
結構的基礎知識
結構體的宣告
結構體的自參考
結構體變數的定義和初始化
結構體成員的訪問
1. 結構體物件.結構體成員
2.結構體指標->結構體成員
結構體傳參
結構體記憶體對齊
對齊規則
為什么存在記憶體對齊?
修改默認對齊數
二、位段
什么是位段
位段的記憶體分配
位段的跨平臺問題
位段的應用
三、列舉常量
什么是列舉?
列舉型別的定義
值是多少?
列舉型別的優點
四、聯合(共用體)
聯合型別的定義
聯合型別的宣告
一、結構體
為什么會有結構體呢?
我們前面所學到的那些資料型別:char,int,double,還有指標都是不足以去表達物件,如果我們要去表示一個人,能用一個數字去表示嗎?肯定是不行的,要知道人是屬于復雜物件,不能簡單的用某個數來表示,要表示一個人,需要很多方面,比如姓名,性別,年齡等,要表示人,我們就得創造一種復雜型別,C語言里面就有了結構體型別
結構體是C語言中特別重要的知識點,結構體使得C語言有能力描述復雜型別
結構體型別也是需要字己創建的
結構體的宣告
結構的基礎知識
結構是一些值的集合,這些值稱為成員變數,結構的每個成員可以是不同型別的變數
結構體的宣告
描述一個人:
方式1:
struct Peo //宣告了一個結構體型別
{
char name[20];
int age;
};
這是對結構體的宣告,struct是結構體關鍵字,Peo是結構體標簽(結構體型別名),花括號里面的是結構體成員變數
結構體成員可以是標量,陣列,指標,結構體
方式2:
struct Peo
{
char name[20];
int age;
}p1, p2 //全域變數
方式2是在結構體宣告的時候就創建了兩個struct Peo型別的結構體變數,但要注意的是這種創建方式得到的p1,p2都是全域變數,作用域比較廣,一般不建議這樣寫
方式3(匿名結構體型別)
struct
{
char name[20];
int age;
}p1, p2;
struct
{
char name[20];
int age;
}p1;
struct
{
char name[20];
int age;
}*p;
第一個代碼這里直接將結構體標簽(結構體型別名)省略掉,這樣寫的話就只能在生命完結構體之后就創建p1,p2結構體變數,不能再在后面的main函式中創建,比較局限,不建議用
第二個代碼在后面創建了指標p,但是如果在第二個代碼的基礎上,下面寫p = &p1;這就是非法的操作,編譯器會把上面的兩個宣告當成完全不同的兩個型別, 所以是非法的
結構體的自參考
我們在用結構體的時候能不能在宣告結構體的時候在結構體里面寫這個結構體
如下代碼:
struct Peo
{
char name[20];
int age;
struct Peo next;
};
要注意這樣是不行的,因為如果這樣寫的話,那么這個結構體的大小應該是多少呢? 肯定就算不出來了
正確寫法
struct Peo
{
int data;
struct Peo* next;
};
在該結構體里面放同型別的結構體指標
結構體變數的定義和初始化
如何定義結構體變數,請看如下代碼:
代碼1:
#include <stdio.h>
#include <string.h>
struct Peo
{
char name[20];
int age;
};
int main()
{
struct Peo p = { "張三",18 }; //通過結構體型別來創建結構體變數并初始化
printf("%s %d\n", p.name, p.age);
//修改里面的內容
strcpy(p.name, "李四");
p.age = 20;
printf("%s %d\n", p.name, p.age);
return 0;
}
通過這段代碼創建了一個描述人的結構體并初始化,在后面對它里面的內容進行了修改
類比:
其實使用結構體就相當于是我們蓋房子,前面的宣告結構體就是畫的圖紙,后面的創建結構體變數就是照著圖紙蓋房子
所以我們在宣告結構體的時候系統是不會給它分配空間的,只有在創建了結構體變數之后系統才會給他分配空間
代碼2(創建出全域結構體變數)
struct Peo
{
char name[20];
int age;
}p1,p2,p3;
在結構體宣告的時候直接就在花括號后面寫上需要創建的結構體變數名/標簽,但要注意這里創建的是全域變數,建議少用這種創建 方式
結構體成員的訪問
1. 結構體物件.結構體成員
如下代碼,通過結構體變數名/標簽對結構體成員進行訪問
struct Peo
{
char name[20];
int age;
};
int main()
{
struct Peo p = { "張三",18 };
printf("%s %d\n", p.name, p.age);
return 0;
}
運行結果:

2.結構體指標->結構體成員
如下代碼:通過結構體指標來訪問結構體成員
#include <stdio.h>
struct Peo
{
char name[20];
int age;
};
int main()
{
struct Peo p = { "張三",18 };
struct Peo* ps = &p;
printf("%s %d\n", ps->name, ps->age);
return 0;
}
運行結果:

結構體傳參
結構體傳參分為傳值和傳地址
1.傳值
如下代碼:
struct Peo
{
char name[20];
int age;
};
void print1(struct Peo p)
{
printf("%s %d\n", p.name, p.age);
}
int main()
{
struct Peo p = { "張三",18 };
print1(p); //傳值,傳整個結構體過去
return 0;
}
這是直接將整個結構體傳了過去
2.傳址
struct Peo
{
char name[20];
int age;
};
void print2(struct Peo* ps)
{
printf("%s %d\n", ps->name, ps->age);
}
int main()
{
struct Peo p = { "張三",18 };
print2(&p);
return 0;
}
將結構體的地址傳過去
這兩種傳參方法我們首選第二種方法
原因就是:
函式傳參的時候,引數是需要壓堆疊的, 如果傳遞一個結構體物件的時候,結構體過大,引數壓堆疊的的系統開銷比較大,所以會導致性能的下降
結構體記憶體對齊
如果我們要計算一個結構體的記憶體大小,該怎么算呢? 是直接將所有的成員變數的記憶體大小相加嗎?
首先請看如下代碼:
#include <stdio.h>
struct P
{
char a;
int b;
};
int main()
{
struct P p;
printf("%d\n", sizeof(p));
}
這里創建結構體變數p之后求這個結構體的大小,是1+4==5嗎
運行結果:

列印結果是8,那顯然說明結構體的大小并不是簡單的成員變數相加,那么這個8是怎么來的呢?這就涉及到了結構體記憶體對齊
對齊規則
- 第一個成員在與結構體變數偏移量為0的地址處(起始位置的0偏移量處)
- 剩下的其他成員變數要對齊到某個數字(對齊數)的整數倍處
- 對齊數:每個成員自身的大小和所用編譯器的默認對齊數的較小值
- 結構體總大小為最大對齊數(每個成員變數都有一個對齊數)的整數倍
- 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍
提示:VS編譯器的默認對齊數是8,linux下的gcc編譯器無默認對齊數
那么現在就可以利用對齊規則來求結構體大小了
上面的代碼中,結構體中第一個為char型別,直接放在0偏移量處
第二個型別為int型別,自身大小為4,默認對齊數為8,所以對齊數取較小值4
結構體總大小為最大對齊數的整數倍
通過畫圖來看

所以這個結構體的大小就是8
為什么存在記憶體對齊?
- 平臺原因(移植原因): 不是所有的硬體平臺都能訪問任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則拋出硬體例外
- 性能原因: 資料結構(尤其是堆疊)應該盡可能地在自然邊界上對齊, 原因在于,為了訪問未對齊的記憶體,處理器需要作兩次記憶體訪問;而對齊的記憶體訪問僅需要一次訪問,
所以結構體記憶體對齊就是拿空間來換取時間
那在設計結構體的時候,我們既要滿足對齊,又要節省空間,讓占用空間小的成員盡量集中在一起
修改默認對齊數
剛才我們在前面說過VS編譯器的默認對齊數是8,但其實這個默認對齊數是可以修改的
結構在對齊方式不合適的時候,我么可以自己更改默認對齊數
#pragma pack() //括號內放想達到的默認對齊數
如下代碼:
#include <stdio.h>
#pragma pack(4) //修改默認對齊數為4
struct P
{
char a;
int b;
};
#pragma pack() //取消設定的默認對齊數,還原為默認值
int main()
{
struct P p;
printf("%d\n", sizeof(p));
}
注意:這個默認對齊數不能隨意修改,所修改的值必須是2^n (n==0,1,2,3……)
二、位段
什么是位段
C語言允許在一個結構體中以位為單位來指定其成員所占記憶體長度,這種以位為單位的成員稱為“位段”或稱“位域”( bit field) ,利用位段能夠用較少的位數存盤資料
如下:
struct A
{
int a : 2;
int b : 3;
int c : 5;
int d : 10;
};
從代碼可以看出位段的宣告和結構體很類似,冒號后面的數字表示該成員的大小(單位bit),有兩個不同:
- 位段的成員必須是int、unsigned int 或signed int
- 位段的成員名后邊有一個冒號和一個數字
那么這個位段的大小是多少呢? 是2+3+5+10個bit嗎?肯定不是的,通過代碼來測驗一下
struct A
{
int a : 2;
int b : 3;
int c : 30;
int d : 10;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}
運行結果:

那么這12byte是怎么來的呢? 接下來看位段的記憶體分配
位段的記憶體分配
- 位段的成員可以是int unsigned int signed int 或者是char (屬于整形家族)型別
- 位段的空間上是按照需要以4個位元組( int )或者1個位元組( char )的方式來開辟的
- 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程式應該避免使用位段
前面的代碼中位段的宣告中有4個int型別的變數,本來應該是要占用16個位元組的空間的,但是在使用位段之后只占用了12個位元組,計算方式如下:

在申請的記憶體不夠用,,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,這是不確定的,取決于編譯器,
位段的跨平臺問題
- int 位段被當成有符號數還是無符號數是不確定的
- 位段中最大位的數目不能確定,(16位機器最大16,32位機器最大32,寫成27,在16位機器會出問題
- 位段中的成員在記憶體中從左向右分配,還是從右向左分配標準尚未定義
- 當一個結構包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,這是不確定的
位段的應用
在我們平時要去發個hello訊息給朋友的時候,不僅僅是發了hello,而是把hello和其他的資料捆綁在一起發送的,比如這訊息是誰發的,發給誰,目標ip地址資訊等,但是這些資訊有的可能只需要幾個bit就能存下,全部用int的話就太浪費了,這樣也會使影響網路狀況,這種情況就用位段
三、列舉常量
什么是列舉?
列舉就是一一列舉的意思,把可能的取值都列舉出來
比如顏色,星期,月份這些都是可以列舉出來的,C語言中就將我們想要的某一型別的值定義成列舉型別,并且讓他們的值也可以一一列舉出來
列舉型別的定義
比如現在將三原色列舉出來
enum Color
{
RED,
GREEN,
BLUE
};
{}中的內容是列舉型別的可能取值,也叫列舉常量
值是多少?
那么這里面的值都分別是多少呢? 如下代碼:
enum Color
{
RED = 3,
GREEN = 7,
BLUE = 5
};
int main()
{
printf("%d\n", RED);
printf("%d\n", GREEN);
printf("%d\n", BLUE);
}
列印結果:

這些可能取值都是有值的,默認從0開始,一次遞增1,當然在定義的時候也可以賦初值, 如下:
enum Color { RED = 3, GREEN = 7, BLUE = 5 };
可以這么使用:
enum Color
{
RED ,
GREEN,
BLUE
};
int main()
{
enum Color c = RED;
}
列舉型別的優點
- 增加代碼的可讀性和可維護性
- 和#define定義的識別符號比較列舉有型別檢查,更加嚴謹,
- 防止了命名污染(封裝)
- 便于除錯
- 使用方便,一次可以定義多個常量
四、聯合(共用體)
聯合型別的定義
聯合也是一種特殊的自定義型別 這種型別定義的變數也包含一系列的成員,特征是這些成員公用同一塊空間(所以聯合也叫共用體)
聯合型別的宣告
如下代碼:
union U
{
char i;
int a;
};
那么這里的聯合體大小是多少呢?
也是對齊數原則,稍有不同
聯合的成員是共用同一塊記憶體空間的,這樣一個聯合變數的大小,至少是最大成員的大小(因為聯合至少得有能力保存最大的那個成員)
總體大小是最大對齊數的整數倍
-----------------------------------------------------------------
-----------C語言浮點數在記憶體中的存盤完結---------
關于C語言,每個知識點后面都會單獨寫博客更加詳細的介紹
歡迎大家關注!!!
一起學習交流 !!!
讓我們將編程進行到底!!!
--------------整理不易,請三連支持------------------
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/321114.html
標籤:其他

