C語言基礎:(二十)自定義類型:結構體

目錄

前言

一、結構體類型的聲明

1.1? 結構體回顧

1.1.1? 結構體的聲明

1.1.2? 結構體變量的創建和初始化

1.2? 結構的特殊聲明

1.3? 結構的自引用

二、結構體內存對齊

2.1??對齊規則

2.1.1? 練習1

2.1.2? 練習2

2.1.3? 練習3:結構體嵌套問題

2.2? 為什么存在內存對齊?

2.2.1? 平臺原因(移植原因)

2.2.2? 性能原因

2.3? 修改默認對齊數

三、結構體傳參

四、結構體實現位段

4.1? 什么是位段

4.2? 位段的內存分配

4.3? 位段的跨平臺問題

4.4? 位段的應用

4.5? 位段使用的注意事項


前言

????????在C語言中,結構體(struct)是一種強大的復合數據類型,允許將不同類型的數據項組合成一個單一的實體。它廣泛應用于數據組織、內存管理以及復雜系統的建模,能夠顯著提升代碼的可讀性和模塊化程度。無論是實現鏈表、樹等數據結構,還是處理文件記錄、網絡協議等實際場景,結構體都扮演著關鍵角色。本文將深入探討C語言結構體的定義、使用方法、內存對齊機制。下面就讓我們正式開始吧!


一、結構體類型的聲明

? ? ? ? 前面我們在學習操作符的時候,已經學習過了結構體的知識,這里我們就來稍微復習一下。

1.1? 結構體回顧

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

1.1.1? 結構體的聲明

? ? ? ? 結構體聲明的格式如下:

struct tag
{member-list;
}variable-list;

? ? ? ? 例如,我們要用結構體描述一個學生的信息,可以如下聲明:

struct Stu
{char name[20];//名字int age;//年齡char sex[5];//性別char id[20];//學號
}; //分號不能丟

1.1.2? 結構體變量的創建和初始化

#include <stdio.h>
struct Stu
{char name[20];//名字int age;//年齡char sex[5];//性別char id[20];//學號
};int main()
{//按照結構體成員的順序初始化struct Stu s = { "張三", 20, "男", "20230818001" };printf("name: %s\n", s.name);printf("age : %d\n", s.age);printf("sex : %s\n", s.sex);printf("id : %s\n", s.id);//按照指定的順序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex =
"?" };printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id : %s\n", s2.id);return 0;
}

1.2? 結構的特殊聲明

? ? ? ? 在聲明結構的時候,可以不完全的聲明。如下所示:

//匿名結構體類型
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], *p;

? ? ? ? 上面的兩個結構體在聲明的時候省略掉了結構體標簽(tag)。那么這就有個問題了:

//在上?代碼的基礎上,下?的代碼合法嗎?
p = &x;

? ? ? ? 注意:

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

? ? ? ? 匿名的結構體類型,如果沒有對結構體類型重命名的話,基本上就只能用一次。

1.3? 結構的自引用

? ? ? ? 在結構中包含一個類型為該結構本身的成員是否可以呢?

? ? ? ? 比如,我們來定義一個鏈表的節點:

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

? ? ? ? 上述代碼是正確的嗎?如果正確,那么 sizeof(struct Node) 是多少?

? ? ? ? 仔細分析之后,其實是不行的,因為一個結構體中再包含一個同類型的結構體變量,這樣的結構體變量的大小自會無窮的大,是不合理的。

? ? ? ? 那么正確的自引用方式是什么呢?如下所示:

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

? ? ? ? 在結構體自引用使用的過程中,夾雜了 typedef 對匿名結構體類型重命名,也容易引入問題,現在我們來看看下面的代碼:

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

? ? ? ? 答案是不行的,因為Node是對前面的匿名結構體類型的重命名產生的,但是在匿名結構體內部提前使用Node類型來創建變量,這是不行的。

? ? ? ? 解決方案如下:定義結構體不要使用匿名結構體。

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

二、結構體內存對齊

? ? ? ? 我們現在已經掌握了結構體的基本使用了,那么現在我們再來深入討論一個問題:那就是計算結構體的大小。

? ? ? ? 這也是一個特別熱門的面試考點:結構體內存對齊

2.1??對齊規則

? ? ? ? 結構體的對齊規則如下:

1.? 結構體的第一個成員對齊到和結構體變量起始位置偏移量為0的地址處。

2.? 其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。

? ? ? ? 對齊數 = 編譯器默認的一個對齊數 與 該變量大小的較小值

  • 在VS中默認的對齊數的值為0
  • Linux中gcc沒有默認對齊數,對齊數就是成員自身的大小

3.? 結構體總大小為最大對齊數(結構體中每個成員變量都有一個對齊數,所以對齊數中最大的)的整數倍

4.? 如果嵌套了結構體的情況,嵌套的結構體成員對齊到自己的成員中最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體中成員的對齊數)的整數倍。

? ? ? ? 下面我們來看幾道練習題:

2.1.1? 練習1

//練習1 --- 分析輸出結果
struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));

? ? ? ? 輸出結果如下:

2.1.2? 練習2

struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3));

? ? ? ? 輸出結果及畫圖分析如下:

2.1.3? 練習3:結構體嵌套問題

struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));

? ? ? ? 畫圖分析如下:

2.2? 為什么存在內存對齊?

? ? ? ? 大部分的參考資料對于內存對齊都是這樣說的:

2.2.1? 平臺原因(移植原因)

? ? ? ? 不是所有的硬件平臺都能夠訪問任意地址上的任意數據的,某些硬件平臺只能夠在某些地址處取特定類型的數據,否則拋出硬件異常。

2.2.2? 性能原因

? ? ? ? 數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于:為了訪問未對齊的內存,處理器需要作兩次內存訪問,而對齊的內存訪問僅需要一次訪問。假設一個處理器總是從內存中取8個字節,則地址必須是8的倍數。如果我們能保證將所有的double類型的數據的地址都對齊成8的倍數,那么就可以用一個內存操作來讀或者寫值了。否則,我們可能需要執行兩次內存訪問,因為的對象可能被放在兩個8字節內存塊中。

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

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

? ? ? ? 我們需要讓占用空間少的成員盡量都集中在一起:

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

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

2.3? 修改默認對齊數

? ? ? ? #pragma 這個預處理指令可以改變編譯器的默認對齊數,如下所示:

#include <stdio.h>#pragma pack(1)//設置默認對?數為1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消設置的對?數,還原為默認

? ? ? ? 當結構體在對齊方式不合適的時候,我們可以自己更改默認對齊數。

三、結構體傳參

? ? ? ? 我們先來看看下面的代碼:

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函數哪個好些?

? ? ? ? 答案是:首選printf2函數。為什么?

? ? ? ? 函數傳參的時候,參數是需要壓棧的,這就會有時間和空間上的系統開銷。

? ? ? ? 如果傳遞一個結構體對象的時候,結構體過大,參數壓棧的系統開銷比較大,因此就會導致性能的下降。

? ? ? ? 所以結構體傳參時,要傳結構體的地址。

四、結構體實現位段

? ? ? ? 結構體講完了,那我們就來講一下結構體實現位段的能力。

4.1? 什么是位段

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

1.? 位段的成員必須是 int、unsigned int 或 signed int,在C99中位段成員的類型也可以選擇其他類型。

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

? ? ? ? 比如:

struct A
{int _a:2;    //冒號后的數字表示這個成員,要占用的比特位的數量int _b:5;int _c:10;int _d:30;
};

? ? ? ? A在此處就是一個位段類型。那么位段A所占的內存大小是多少呢?

? ? ? ? 我們來看看畫圖分析:

? ? ? ? 最后我們可以推知:所占內存大小為8字節。

4.2? 位段的內存分配

  1. 位段的成員可以是 int 、unsigned int 、signed int 或者是 char 類型。
  2. 位段的空間上是按照需要以4個字節(int)或者1個字節(char)的方式來開辟的。
  3. 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應該避免使用位段。
//?個例?
struct S
{char a:3;char b:4;char c:5;char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;//空間是如何開辟的?

? ? ? ? 我們在VS2013環境下測試數據如下:

4.3? 位段的跨平臺問題

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

? ? ? ? 因此我們可以做出總結:跟結構相比,位段可以達到同樣的效果,并且可以很好的節省空間,但是有跨平臺的問題所在。

4.4? 位段的應用

? ? ? ? 下圖是網絡協議中,IP數據報的格式,我們可以看到其中很多的屬性只需要幾個bit位就能描述,這里使用位段,能夠實現想要的效果,也節省了空間。這樣網絡傳輸的數據報大小也會較小一些,對網絡的暢通是有幫助的。

4.5? 位段使用的注意事項

? ? ? ? 位段的成員共有同一個字節,這樣有些成員的起始位置并不是某個字節的起始位置,那么這些位置處是沒有地址的。內存中每個字節分配一個地址,一個字節內部的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;return 0;
}

總結

? ? ? ? 以上就是本期博客的全部內容了。下一期博客我將為大家介紹聯合和枚舉類型的相關內容,敬請期待!

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

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

相關文章

數據倉庫分層解析(詳細)

目錄 一、數據倉庫為什么要分層 二、數據倉庫怎么分層 1、ODS&#xff08;Operational Data Store&#xff09;&#xff1a;數據源層 2、DW&#xff08;Data Warehouse&#xff09;&#xff1a; 數據倉庫層 2.1、DWD&#xff08;Data Warehouse Detail&#xff09;&#x…

智慧城管云平臺源碼,微服務vue+element+springboot+uniapp技術架構,數字化綜合執法辦案系統

智慧城管綜合執法系統源碼&#xff0c;包括PC端和移動端。微服務架構&#xff0c;vueelementspringbootuniapp技術框架開發。智慧城管建立了統一的城管執法案件數據庫、法律法規庫、檔案信息庫等&#xff0c;支持簡易程序案件、一般程序案件、行政強制管理等執法業務的辦理&…

VUE實現多個彈窗優先級變化實現思路

在開發復雜的單頁應用&#xff08;SPA&#xff09;時&#xff0c;我們經常會遇到需要管理多個浮動窗口&#xff08;或稱“彈窗”、“面板”&#xff09;的場景。一個核心的用戶體驗要求是&#xff1a;用戶當前操作的窗口應該總是在最頂層。本文將結合代碼示例&#xff0c;總結一…

集成算法和kmeans

一、集成算法&#xff08;Ensemble Learning&#xff09; 1. 基本概念 集成學習通過構建并結合多個學習器&#xff08;基分類器/回歸器&#xff09;來完成學習任務&#xff0c;旨在通過集體決策提升模型性能&#xff0c;類似于“多個專家的綜合判斷優于單個專家”。 2. 結合策略…

圖數據庫性能與可擴展性評估

圖數據庫的性能與可擴展性直接決定業務場景&#xff08;如實時風控、知識圖譜分析&#xff09;的落地效果&#xff0c;需結合業務場景特性&#xff08;OLTP/OLAP&#xff09;、技術指標&#xff08;響應時間、吞吐量&#xff09;和擴展能力&#xff08;數據量/節點擴展&#xf…

樹莓派常用的國內鏡像源列表以及配置方法

1. 常用的鏡像源使用下來發現清華源經常訪問不到&#xff0c;阿里源比較好用。其他源還未測試。源名稱URL清華源https://pypi.tuna.tsinghua.edu.cn/simple阿里云https://mirrors.aliyun.com/pypi/simple/中科大https://pypi.mirrors.ustc.edu.cn/simple/華為云https://repo.hu…

Transformer在文本、圖像和點云數據中的應用——經典工作梳理

摘要 最近在整一些3D檢測和分割的任務&#xff0c;接觸了一下ptv3&#xff0c;在之前梳理的工作owlv2中用到了vit&#xff0c;去年年假閱讀《多模態大模型&#xff1a;算法、應用與微調》&#xff08;劉兆峰&#xff09;時學習了Transformer網絡架構及其在文本數據中的應用&am…

訓練后數據集后部署PaddleOCR轉trt流程

訓練后的模型部署&#xff0c;首先要進行訓練 0.訓練流程見文章 PaddleOCR字符識別&#xff0c;訓練自己的數據集全流程&#xff08;環境、標注、訓練、推理&#xff09;-CSDN博客文章瀏覽閱讀1.6k次&#xff0c;點贊53次&#xff0c;收藏23次。PaddleOCR是基于百度飛槳框架的…

《MLB美職棒》美國國球是橄欖球還是棒球·棒球5號位

USAs National Sport Showdown: MLB?? vs NFL Ultimate Guide!從商業價值到文化基因&#xff0c;360解析美國體育王座之爭&#xff01;添加圖片注釋&#xff0c;不超過 140 字&#xff08;可選&#xff09;? 歷史定位 Historical Roots?? MLB&#xff1a;The "Classi…

常見 Linux 網絡命令梳理

在日常運維和排障工作中&#xff0c;網絡相關命令是最常用的一類工具。無論是檢查網絡連通性&#xff0c;還是定位路由問題&#xff0c;又或是分析端口和服務占用&#xff0c;熟悉這些命令都能讓我們更高效地解決問題。本文將從幾個常見的維度來梳理 Linux 下的網絡命令&#x…

Docker 搭建 Gitlab 實現自動部署Vue項目

1、配置要求: 硬件要求: CPU:雙核或以上 內存:4GB或以上 軟件要求:Centos6 或更高版本 2、gitlab鏡像: # 中文版倉庫 #docker pull twang2218/gitlab-ce-zh docker pull gitlab/gitlab-ce 3、gitlab部署目錄 說明:為了跟其他容器區分,gitlab相關容…

如何解決機器翻譯的“幻覺“問題(Hallucination)?

更多內容請見: 機器翻譯修煉-專欄介紹和目錄 文章目錄 一、數據層面優化 二、模型架構改進 三、訓練策略調整 四、評估與迭代 五、前沿方向與挑戰 六、案例:WMT2023幻覺緩解方案 機器翻譯中的“幻覺”(Hallucination)指模型生成與源文本語義無關、邏輯矛盾或事實錯誤的翻譯…

基于STM32+NBIOT設計的宿舍安防控制系統_264

文章目錄 1.1 項目介紹 【1】開發背景 【2】實現需求 【3】項目硬件模塊組成 【4】設計意義 【5】國內外研究現狀 【6】摘要 1.2 系統總體設計 【1】系統功能需求分析 【2】系統總體方案設計 【3】系統工作原理 1.3 系統框架圖 1.4 系統功能總結 1.5 系統原理圖 1.6 實物圖 1.7…

SLAM文獻之-Globally Consistent and Tightly Coupled 3D LiDAR Inertial Mapping

一、簡介 該論《Globally Consistent and Tightly Coupled 3D LiDAR Inertial Mapping》是日本先進工業科學技術研究所&#xff08;AIST&#xff09;的Koide等人于2022年在IEEE國際機器人與自動化會議&#xff08;ICRA&#xff09;上發表的一篇論文。該研究提出了一種基于全局…

【STM32】HAL庫中的實現(七):DMA(直接存儲器訪問)

DMA 是什么&#xff1f; DMA&#xff08;Direct Memory Access&#xff09;是 外設直接和內存之間數據搬運的機制&#xff0c;不需要 CPU 參與。 ? 舉個例子&#xff1a;傳統方式&#xff1a; ADC → CPU → RAM 使用 DMA&#xff1a;ADC → DMA → RAM&#xff08;CPU 不需干…

【LeetCode熱題100道筆記+動畫】字母異位詞分組

題目描述 給你一個字符串數組,請你將 字母異位詞 組合在一起。可以按任意順序返回結果列表。 示例 1: 輸入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”] 輸出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]] 解釋: 在 strs 中沒有字符串可…

【Kafka】常見簡單八股總結

為什么使用消息隊列&#xff1f; 解耦&#xff1a; 我以我的一段開發經驗舉例&#xff1a; 【Kafka】登錄日志處理的三次階梯式優化實踐&#xff1a;從同步寫入到Kafka多分區批處理 我做過一個登錄日志邏輯&#xff0c;就是在登錄邏輯末尾&#xff0c;加一段寫進數據庫登錄日志…

微信小程序連接到阿里云物聯網平臺

目錄準備階段阿里云配置下載mqtt.min.js文件小程序實現注意小程序配置服務器域名概述&#xff1a;介紹使用微信小程序連接到阿里云平臺的快捷方法和完整過程。 阿里云平臺建立設備&#xff0c;提供mqtt連接參數&#xff0c;小程序借助mqtt.min.js&#xff0c;也就是基于Github下…

2-3〔O?S?C?P? ? 研記〕? 漏洞掃描?AppScan(WEB掃描)

鄭重聲明&#xff1a; 本文所有安全知識與技術&#xff0c;僅用于探討、研究及學習&#xff0c;嚴禁用于違反國家法律法規的非法活動。對于因不當使用相關內容造成的任何損失或法律責任&#xff0c;本人不承擔任何責任。 如需轉載&#xff0c;請注明出處且不得用于商業盈利。 …

LeetCode 刷題【47. 全排列 II】

47. 全排列 II 自己做 解1&#xff1a;檢查重復 class Solution { public:void circle(vector<int> nums, vector<vector<int>> &res,int start){int len nums.size();if(start len - 1){ //到頭了//檢查重復bool is_exist fa…