結構體和位段

結構體:?

? ? ? ?C語言中,我們之前使用的都是C語言中內置的類型,比如整形(int)、字符型(char)、單精度浮點型(float)等。但是我們知道,我們現實世界中,還有很多其他類型。比如書,水杯,人等各種類型。

結構體的使用:?

? ? ? ?在使用結構體之前,我們先來看看結構體的基本使用語法:

? ? ? ?當內置類型無法滿足我們的我們的需求(像定義一本書),此時就會用到結構體了,它就可以自定義一個類型。比如書就是一種類型,組成它的元素就是木頭,墨水,膠水等,這些就是組成書的基本元素。而在編程語言中,我們定義一本書,它的基本元素就可以理解為C語言的內置類型,由這些內置類型組合而成。

struct Book
{//書由一下屬性(元素)組成char Book_Name[20];//書的名字char Writer_Name[20];//作者姓名int edition;//版本號
};//此時變量列表為空

? ? ? ?此時我們就自定義了一種書的類型,這時就可以定義多個書的變量。書是類,那么具體的一本書就是一個書變量(也可以理解為對象),此時我們第一定義一個書的變量,并打印。注:變量列表可以為空

struct Book
{//書由一下屬性(元素)組成char Book_Name[20];//書的名字char Writer_Name[20];//作者姓名int edition;//版本號
};int main()
{struct Book Book1 = { "大話數據結構", "張三", 20 };printf("書名是:%s\n", Book1.Book_Name);printf("作者是:%s\n", Book1.Writer_Name);printf("版本是:%d\n", Book1.edition);return 0;
}

? ? ? ?我們用 . 來訪問變量中的每一個成員(這不是使用指針的情況)。要按照順序來定義每一個變量,. 就是一個操作符,意思可以理解為“的”。

? ? ? ?每一種類型都有對應的指針,所以結構體也有指針,就是結構體指針。我們用指針訪問結構體變量時就需要用到 -> 來指定訪問變量的哪一個具體成員屬性。

struct Book* p = &Book1;//定義結構體指針指向變量Book1
//因為其他類型只需要解引用,但是結構體有多個成員
//用指針找到結構體變量的每一個成員需要用到 ->
printf("書名是:%s\n", p->Book_Name);

? ? ? ???當然也可以對其解引用之后再使用 . 操作符訪問具體成員屬性。

//通過解引用再使用 . 來訪問具體屬性
printf("書名是:%s\n", (*p).Book_Name);

? ? ? ?現在我們來舉例成員列表的使用。比如此時我們使用成員列表,聲明多個成員:

struct Book
{//書由一下屬性(元素)組成char Book_Name[20];//書的名字char Writer_Name[20];//作者姓名int edition;//版本號
}Book1, Book2, Book3;//這3個相當于全局結構體變量int main()
{struct Book Book4;//局部變量return 0;
}

? ? ? ?就相當于全局變量。但是C語言并不支持直接在主函數中直接對全局結構體變量進行賦值。

? ? ? ?此時對賦值屬性的字符串賦值也不能使用以下方法賦值:

Book1.Book_Name = "大話數據結構";

? ? ? ?我們只能使用strcpy函數賦值;但對于整數屬性的賦值可以直接賦值:

strcpy(Book1.Book_Name, "大話數據結構");
printf("書名是:%s\n", Book1.Book_Name);
Book1.edition = 20;
printf("版本是:%d\n", Book1.edition);

? ? ? ?之后有人就經常和typedef搞混,因為用法相似:

typedef struct Book
{//書由一下屬性(元素)組成char Book_Name[20];//書的名字char Writer_Name[20];//作者姓名int edition;//版本號
}book;

? ? ? ?此時我們相當于將struct Book重命名了(重命名具體用法可先查看其他文章),之后定義該結構體變量不需要使用struct Book,而直接使用book聲明該變量的類型即可:

book Book1 = { "大話數據結構", "張三", 20 };

? ? ? ??這里相當于定義了一本書,由于使用了typedef函數,可以省略struct關鍵字,寫出結構體名稱,創建變量即可,這里創建了2個變量,之后打印變量名.成員。

//兩種形式都可以使用
book Book1 = { "大話數據結構", "張三", 20 };struct Book Book2 = { "C語言", "我", 1 };
printf("書名是:%s\n", Book1.Book_Name);
printf("書名是:%s\n", Book2.Book_Name);

? ? ? ?結構體成員可以是結構體,要用大括號來說明結構體中另外的結構體。

struct s
{int a;char c;char arr[20];double d;
};
struct t
{char ch[10];struct s s;//結構體成員可以是結構體char* pc;
};
int main()
{char arr[] = "holle bit";struct t t1 = { "hehe",{3,'u',"holle world",3.14},arr };printf("%s\n", t1.ch);//heheprintf("%s\n", t1.s.arr);//holle worldprintf("%d\n", t1.s.a);//3printf("%lf\n", t1.s.d);//3.140000printf("%s\n", t1.pc);//holle bitreturn 0;
}

? ? ? ?這里結構體成員中有指針,我們創建一個數組,把數組名放進去。?

結構體的傳參:?

? ? ? ?我們知道,形參是實參的一份臨時拷貝,我們對結構體進行傳參時,如果是傳值,就是函數中把這個結構體變量臨時復制一份,這樣無疑會浪費很多空間。

? ? ? ?所以我們一般進行傳址調用,就是傳入結構體的指針:

typedef struct stu
{char name[20];short age;char tele[12];char sex[5];
}stu;
void print1(stu s)
{printf("%s\n", s.name);//張三printf("%d\n", s.age);//40printf("%s\n", s.tele);//15568886688printf("%s\n", s.sex);//男//不是指針就用"."
}
void print2(stu* p)
{printf("%s\n", p->name);printf("%d\n", p->age);printf("%s\n", p->tele);printf("%s\n", p->sex);//指針就用箭頭
}
int main()
{stu s = { "張三",40,"15568886688","男" };print1(s);//用這個不太好print2(&s);//用這個函數比較好return 0;
}

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

匿名結構體:?

? ? ? ?匿名結構體,顧名思義,就是沒有名字的結構體,意味著沒有標簽,但有一個成員變量。

struct 
{//匿名結構體類型int a;char c;
}sa;
struct
{//匿名結構體類型int a;char c;
}*psa;//匿名結構體指針類型
int main()
{psa = &sa;//編譯器會認為這是兩種不同的類型return 0;
}

? ? ? ??一般最好不要使用,使用一次以后就最好不要使用了。

結構體的自引用:

? ? ? ?結構體可以自信用,并不是遞歸。結構體類型中可以有一個同類型的結構體指針,結構體自引用牽扯到數據結構,先大致了解。

//結構體的自引用
//數據結構:鏈表
//在內存中,每個數據是隨機分布的,為了讓他們有規律的連接起來,就要用到鏈表
//
struct Node
{int data;struct Node *next;
};
int main()
{return 0;
}

? ? ? ?因為typedef可以定義數據類型的名字,所以可以:

typedef struct Node
{int data;struct Node* next;
}Node;
int main()
{struct Node n1;Node n2;return 0;
}

結構體的大小:?

? ? ? ?這是結構體最重要的部分,因為我們一定要知道每個類型在內存中的占據規則。結構體在內存中的占據規則是很復雜的。

結構體內存占據規則:

? ? ? ?結構體占據內存遵循地址對齊。第一個成員在與結構體變量偏移量為0的地址處對齊。所有成員都會遵循字節對齊,且第一個成員總是在與結構體變量偏移量為0的地址處對齊。其實結構體是先在內存中找到能被第一個類型整除的地址。

? ? ? ?結構體每個成員都遵循地址對齊,對齊數是根據系統對齊數和當前成員大小對齊的。

? ? ? ?對齊數 = 編譯器默認的對齊數 與 改成員大小的較小值

? ? ? ?vs編譯器默認對齊數為8。

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

? ? ? ?先看第一個成員,占據8個字節,所以先在內存中找到能被8整除的地址,偏移量為0(我們一會再解釋),所以先占據8個字節,之后又找能被下一個成員內存(較小的對齊數是1)整除的地址,最后又找能被4整除的地址,最后整體結構體大小必須是當前最大成員屬性大小的整數倍。

? ? ? ? ?即使VS默認對齊數是8,但是結構體大小是根據自己本身成員屬性最大整數倍對齊的。

結構體嵌套:

? ? ? ?結構體可是可以嵌套的。

struct S3
{double d;char c;int i;
};
struct s4
{char c1;struct S3 s3;double d;
};

使用pragma來指定對齊數:?

? ? ? ?我們可以自己設置默認對齊數,提高空間利用效率,因為對齊數總是等于較小值。先設置默認對齊數為2幾次方。要加入預處理指令#pragma pack(設置的默認對齊數)

#pragma pack(1)//設置默認對齊數為4
struct S
{char c1;//1double d;//8
};
#pragma pack()//取消設置的默認對齊數
int main()
{struct S s;printf("%d\n", sizeof(s));return 0;
}

? ? ? ?此時最小默認對齊數為1,所以所有屬性都找到能被1整除的地址即可。結構在對齊方式不合適的時候,我蠻可以自己更改默認對齊數。一般是2幾次方。?

相對偏移函數offsetof:

? ? ? ?我們可以求出它相對于結構體偏移了幾個字節。要引入頭文件stddef.h。

#include<stddef.h>//offsetof的頭文件
struct S1
{char c1;char c2;int i;
};int main()
{printf("%zd\n", offsetof(struct S1, c1));printf("%zd\n", offsetof(struct S1, c2));printf("%zd\n", offsetof(struct S1, i));return  0;
}

? ? ? ?相對起始位置的偏移量。

內存對齊的意義:?

  1. 平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的數據的,某些硬件只能在某某些地址處去某些特定類型的數據,否則拋出異常。
  2. 性能原因:對于未對齊的內存,處理器需要兩次內存訪問;而對齊的的內存訪問僅需要一次。假設一個處理器總是從內存中取8個字節,則地址必須是8的倍數,如果我們能保證將所有的doubl類型數據的地址都對齊成8的倍數,就可以用一個內存操作來讀取或者寫值了,否則,我們可能需要執行兩次內存訪問,因為對象可能分放在兩個8字節內存中。

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

位段:?

位段是什么?

? ? ? ?位段的出現就是為了節省空間,因為結構體遵循內存對齊,有時候會造成空間浪費,于是衍生出來了位段。位段的聲明和結構體是類似的,有兩個不同:

  1. 位段成員必須是int、 unsigned int 、signed int或者char等類型。

  2. 位段的成員名后面有一個冒號和一個數字。

位段的使用和大小:?

? ? ? ?位段的使用是類似于結構體的。


//1.位段的成員必須是int、unsigned int、signed int
//2.位段的成員名后有一個冒號和數字
//位段 - 二進制位
struct A
{int a : 2;//2//冒號后面的數字表示a只需要兩個比特位就夠了int b : 5;//5int c : 10;int d : 30;
};
//47bit - 6個字節*8 = 48bit
//因為位段有自己的對齊方式
int main()
{struct A s;printf("%d\n", sizeof(s));//8個字節return 0;
}

? ? ? ?上圖中A就是一個位段類型。A的大小是8個字節。?

? ? ? ?位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應該避免使用位段。

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
int main()
{struct S s={0};s.a = 10;s.b = 20;s.c = 3;s.d = 4;return 0;
}

? ? ? ?上面的代碼就是相當于先創建一個位段類型,之后聲明每個成員占多少個bit,之后有給成員賦值,但很明顯,給a賦值10所占據的比特位已經超過了3個bit,于是只將10的二進制前后3個為給成員a。如果不夠,高位補0。之后以此類推。?

位段成員的賦值:?

? ? ? ?位段的幾個成員共有一個字節,這樣有些成員的起始位置并不是某個字節的起始位置,那么這些位置處是沒有地址的。

? ? ? ?內存中每個字節分配一個地址,一個字節內部的bit位是沒有地址的,所以不能對位段成員使用&操作,這樣就不能使用scanf直接給位段的成員輸入值,只能是先輸入放在一個變量中,然后賦值給位段的成員。

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};int main()
{struct A sa = { 0 };//scanf("%d", &sa._b);//這里報錯//只能這樣賦值int b = 0;scanf("%d", &b);sa._b = b;printf("%d\n", sa._b);return 0;
}

位段存在的意義:?

? ? ? ?學過網絡的都知道,我們的數據都是封裝成幀發送的,我們一般采用IP數據報的形式發送,我們觀察IP數據報的格式:

? ? ? ?因為地址最小的的地址編號是字節,1個字節8個bit位,若使用結構體,必然會造成空間的浪費,位段的出現使我們將每一個bit位都合理的使用,但有人會問?既然現在硬件內存都那么大了,還有必要限制內存嗎?

? ? ? ?我們可以將網絡通道想象成一條高速公路,如果都是大型文件,就像是都是大卡車,這樣勢必會造成交通擁擠;但是如果都是小文件,就是小客車,即使會用交通擁擠也會比都是大卡車的路況好。?

位段的跨平臺問題:

  1. int位段被當做有符號數還是無符號數是不確定的。
  2. 位段中最大位的數目不確定。(16位機器int是2個字節,寫成27會出問題)。

  3. 位段中的成員在內存中從左向右分配,還是從右向左分配標準尚未定義。

  4. 當一個結構包括兩個位段,第二位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,這個是不確定的。

? ? ? ?最后我們來看一道關于位段的練習題:

int main()
{unsigned char puc[4];struct tagPIM{unsigned char ucPim1;unsigned char u0 : 1;unsigned char u1 : 2;unsigned char u2 : 3;}*p;p = (struct tagPIM*)puc;memset(puc, 0, 4);//設置4個字節,每個內容為0p->ucPim1 = 2;p->u0 = 3;p->u1 = 4;p->u2 = 5;printf("%02x %02x %02x %02x\n", puc[0], puc[1], puc[2], puc[3]);//%02x打印出兩個16進制的數return 0;
}

???????

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/210704.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/210704.shtml
英文地址,請注明出處:http://en.pswp.cn/news/210704.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

聊聊spring.mvc.servlet.load-on-startup

序 本文主要研究一下spring.mvc.servlet.load-on-startup spring.mvc.servlet.load-on-startup org/springframework/boot/autoconfigure/web/servlet/WebMvcProperties.java ConfigurationProperties(prefix "spring.mvc") public class WebMvcProperties {//.…

json精講

本文介紹json的規范及javascript和java對數據的交換讀取 1. json介紹1.1 json簡介1.2為什么使用 JSON&#xff1f; 2. json規范2.1基礎規范2.2 key值為-字符串、數字、布爾值2.3 key值為對象Object2.4 key值為數組2.5 json本身就是一個數組 3.javascript操作json3.1 javascript…

WPF(Windows Presentation Foundation) 的 Menu控件

WPF&#xff08;Windows Presentation Foundation&#xff09;的 Menu 是一種用于創建菜單的控件。菜單通常位于應用程序窗口的頂部&#xff0c;并提供了一組命令或選項&#xff0c;用于導航到不同的功能區域、執行特定的操作或訪問特定的功能。 Menu 控件是 WPF 中的一個容器…

2、關于使用ajax驗證繞過(實例2)

ajax原理我上一篇有寫過&#xff0c;參考&#xff1a;1、關于前端js-ajax繞過-CSDN博客 一、實例環境&#xff1a; 為手機上的某一割韭菜app 二、目的&#xff1a; 實現繞過手機驗證碼&#xff0c;找回密碼 三、工具&#xff1a; bp代理 四、驗證步驟如下&#xff1a; …

ECU安全學習網站和書籍介紹

ECU安全是指關注和保護汽車電子控制單元&#xff08;ECU&#xff09;的安全性和防護措施。ECU是現代汽車中的關鍵組件&#xff0c;它負責監控和控制車輛各種系統的運行&#xff0c;如發動機、制動、轉向等。ECU安全的重要性在于防止惡意攻擊者操控或干擾車輛的操作。 ECU安全涉…

hive自定義函數及案例

一.自定義函數 1.Hive自帶了一些函數&#xff0c;比如&#xff1a;max/min等&#xff0c;但是數量有限&#xff0c;自己可以通過自定義UDF來方便的擴展。 2.當Hive提供的內置函數無法滿足你的業務處理需要時&#xff0c;此時就可以考慮使用用戶自定義函數。 3.根據用戶自定義…

GitHub為Rust語言添加了供應鏈安全工具

GitHub的供應鏈安全特性包括咨詢數據庫、Dependabot警報和依賴關系圖現在可以用于Rust Cargo文件。 為了幫助Rust開發人員發現和防止安全漏洞&#xff0c;GitHub已經為快速增長的Rust語言提供了供應鏈安全特性套件。 這些特性包括GitHub Advisory Database&#xff0c;它已經有…

構建外賣系統:使用Django框架

在當今數字化的時代&#xff0c;外賣系統的搭建不再是什么復雜的任務。通過使用Django框架&#xff0c;我們可以迅速建立一個強大、靈活且易于擴展的外賣系統。本文將演示如何使用Django構建一個簡單的外賣系統&#xff0c;并包含一些基本的技術代碼。 步驟一&#xff1a;安裝…

shell的條件測試

shell 的條件測試 概述 條件測試是 shell 編程中非常重要的一個概念&#xff0c;它允許我們根據某個條件是否滿足&#xff0c;來選擇執行相應的任務。 條件測試的語法 shell 中的條件測試語法如下&#xff1a; [ 條件表達式 ]如果條件表達式為真&#xff0c;則返回 0&…

CentOS 7.9--離線安裝python3.9.18+virtualenv-20.25.0

# 想在centos6.x 上安裝新版本的python&#xff0c;但是擔心在用系統的環境被破壞&#xff0c;所以需要安裝python虛擬環境&#xff0c;然后就找到自用的aliyun主機先測試下離線安裝&#xff0c;在用6.X環境是沒有互聯網的&#xff0c;必須需要離線安裝。 1. 下載對應python源…

力扣解題之保姆教程:(1)兩數之和(代碼詳解)

題目描述 給定一個整數數組 nums 和一個整數目標值 target&#xff0c;請你在該數組中找出 和為目標值 target 的那 兩個 整數&#xff0c;并返回它們的數組下標。 假設每種輸入只會對應一個答案。但是&#xff0c;數組中同一個元素在答案里不能重復出現。你可以按任意順序返回…

Django模板

以下是一個簡單的Django模板示例&#xff1a; <!DOCTYPE html> <html><head><title>{{ title }}</title></head><body><h1>{{ heading }}</h1><p>{{ content }}</p></body> </html>一、模板的…

波奇學Linux:父子進程和進程狀態

vim編輯器&#xff0c;編寫一個程序模擬進程 在vim中查看sleep函數 底行模式輸入 寫個Makefile自動運行波奇學Linux:yum和vim-CSDN博客 運行程序 PID和PPID 查看進程目錄信息 實際有過濾出來有兩個&#xff0c;一個進程本身一個是grep程序&#xff0c;通過 -v grep過濾走含gre…

新版Android Studio 正則表達式匹配代碼注釋,刪除注釋,刪除全部注釋,IntelliJ IDEA 正則表達式匹配代碼注釋

正則表達式匹配代碼注釋 完整表達式拼接Android Studio 搜索匹配【IntelliJ IDEA 也是一樣的】 完整表達式拼接 (/*{1,2}[\s\S]?*/)|(//[\x{4e00}-\x{9fa5}].)|(<!-[\s\S]?–>)|(^\s\n)|(System.out.println.*) 表達式拆解&#xff0c;可以根據自己需求自由組合&#x…

Mybatis、Mybatis整合Spring的流程圖

Mybatis 注意MapperProxy里面有invoke方法&#xff0c;當進到invoker方法會拿到 二、mybatis整合Spring 1、當我們的拿到的【Dao】其實就是【MapperProxy】&#xff0c;執行Dao的方法時&#xff0c;會被MapperProxy的【Invoke方法攔截】 2、圖上已經標注了MapperProxy包含哪些…

力扣:200. 島嶼數量(Python3)

題目&#xff1a; 給你一個由 1&#xff08;陸地&#xff09;和 0&#xff08;水&#xff09;組成的的二維網格&#xff0c;請你計算網格中島嶼的數量。 島嶼總是被水包圍&#xff0c;并且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連接形成。 此外&#xff0c;你可以假…

STM32-TIM定時器中斷

目錄 一、TIM&#xff08;Timer&#xff09;定時器簡介 二、定時器類型 2.1基本定時器結構 2.2通用定時器結構 2.3高級定時器結構 三、定時中斷基本結構 四、時序圖分析 4.1 預分頻器時序 4.2 計數器時序 4.3 計數器無預裝時序&#xff08;無影子寄存器&#xff09; …

C#的線程技術及操作

每個正在操作系統上運行的應用程序都是一個進程&#xff0c;一個進程可以包括一個或多個線程。線程是操作系統分配處理器時間的基本單元&#xff0c;在進程中可以有多個線程同時執行代碼。每個線程都維護異常處理程序、調度優先級和一組系統用于在調度該線程前保存線程上下文的…

PyQt6 水平布局Horizontal Layout (QHBoxLayout)

鋒哥原創的PyQt6視頻教程&#xff1a; 2024版 PyQt6 Python桌面開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili2024版 PyQt6 Python桌面開發 視頻教程(無廢話版) 玩命更新中~共計41條視頻&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面開發 視頻教程(無廢話版…

[足式機器人]Part2 Dr. CAN學習筆記-自動控制原理Ch1-1開環系統與閉環系統Open/Closed Loop System

本文僅供學習使用 本文參考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN學習筆記-自動控制原理Ch1-1開環系統與閉環系統Open/Closed Loop System EG1: 燒水與控溫水壺EG2: 蓄水與最終水位閉環控制系統 EG1: 燒水與控溫水壺 EG2: 蓄水與最終水位 h ˙ q i n A ? g h A R \dot{…