【C語言】【數據結構】自定義類型:結構體

引言

這是一篇對結構體的詳細介紹,這篇文章對結構體聲明、結構體的自引用、結構體的初始化、結構體的內存分布和對齊規則、庫函數offsetof、以及進行內存對齊的原因、如何修改默認對齊數、結構體傳參進行介紹和說明。

?158c3f50b199454985017a51dbef9841.png? ? ? ? ? ? ? ???豬巴戒:個人主頁?

???????????????所屬專欄:《C語言進階》

? ? ? ? 🎈跟著豬巴戒,一起學習C語言🎈

目錄

引言

結構體的聲明

結構體的基礎

結構的聲明

匿名結構體類型

結構體的自引用

typedef作用于結構體的問題

?結構體變量的定義和初始化

多個元素的初始化要用大括號{ }

結構體的內存對齊

1.對齊規則

1.例子

2.例子

?3.例子

??????????

4.例子

offsetof

offsetof的使用

??編輯

?為什么要存在內存對齊

修改默認對齊數

?結構體傳參


結構體的聲明

??

結構體的基礎

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

在一個變量中,要存放性別、年齡、成績、地址多種類型的數據時,C語言允許用戶自己建立由不同類型數據組成的組合型的數據結構,它稱為結構體。

? ??

結構的聲明

結構體是怎么聲明的呢?

struct tag
{member_list;
}variable_list;  //分號不能丟struct Student
{//學生的相關信息char name[20];int age;
}s1,s2;
  • tag,Student是結構體名
  • member_list是成員表列
  • struct是聲明結構體類型是必須使用的關鍵字,不能省略
  • s1,s2變量就是學生變量。
  • { }后面要記得把“ ;”帶上

struct tag就是一個結構體類型,我們可以根據自己的需要建立結構體類型,struct Teacher,struct Student等結構體類型,各自包含不同的成員。

如果將s1,s2放在main函數的外面,那么s1,s2就是全局變量。

struct Student
{//學生的相關信息char name[20];int age;
}s1,s2;int main()
{return 0;
}

????????

匿名結構體類型

結構體在聲明的時候省略了結構體標簽(tag),沒有名字的結構體類型只能使用一次,被稱為匿名結構體類型

由于沒有名字,編譯器會把下面的兩個代碼當成完全不同的兩個類型。

所以,p = &x.

會因為類型不同報錯。

struct
{char name[20];int age;
}s1;struct
{char name[20];int age;
}a[20],*p;

????????

結構體的自引用

結構體的自引用用到數據結構中的鏈表。

數據結構中有順序表、鏈表的概念,

順序表

數據在內存中是順序排放的,可以逐個根據地址找到下一個數據。

鏈表

數據在內存中的存放是沒有規律的但是存放數據,會分為兩個部分,

一個部分叫數據域,存放有效數據,

另一個部分叫指針域,用來存放下一個數據的地址,可以通過地址直接找到下一個數據。

89dab2a99bb64a65acdd24ac4d63b4e8.png

我們通過鏈表就可以實現結構體的自引用。

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

????????

typedef作用于結構體的問題

下面在結構體自引用使用的改成中,夾雜了typedef對匿名結構體類型重命名,看看下面的代碼,有沒有問題?

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

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

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

??????????

?結構體變量的定義和初始化

struct Point是結構體類型,它相當于一個模型,是沒有占據具體空間的,

當我們建立結構體變量p1,它相當于具體的房屋,在內存中儲存數據。

struct Point
{int x;int y;
}p1 = { 2,3 };

????????

多個元素的初始化要用大括號{ }

在結構體中,如果存在多個元素的變量,我們初始化時要使用大括號。

像數組一樣,arr[] = { 0, 1, 2, 3, 4 };

  • 打印結構體,s1是struct Stu的變量,name是s1的成員變量,用s1.name表示s1結構體的name變量
  • s是struct Stu中的成員變量,用s1.s.n表示在結構體struct score的成員變量n。
struct score
{int n;char ch;
};
struct Stu
{char name[20];int age;struct score s;
};int main()
{struct Stu s1 = { "zhangsan",20,{100,'q' } };printf("%s %d %d %c\n", s1.name, s1.age, s1.s.n, s1.s.ch);return 0;
}

????????

結構體的內存對齊

如何計算結構體的大小?

結構體的內存分布是怎樣的?

????????

1.對齊規則

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

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

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

對?數 = 編譯器默認的?個對?數 與 該成員變量??的較?值。

- VS 中默認的值為 8

- Linux中 gcc 沒有默認對?數,對?數就是成員??的??

3. 結構體總??最?對?數(結構體中每個成員變量都有?個對?數,所有對?數中最?的)的整數倍。

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

??????????

只是文字的說明,免不了晦澀難懂,接下來用例子來給大家講解

1.例子

#include <stdoi.h>
struct S1
{char c1;int i;char c2;
};
int main()
{printf("%d\n", sizeof(struct S1));return 0;
}

177ba4f9a12045639def737ae589d621.png

解析:?

右邊表示的是偏移量,

1.第一個成員char c1要對齊到和結構體變量起始位置偏移量為0的地址處,占一個字節

2.其他成員要對齊到對齊數的整數倍的地址處

對?數 = 編譯器默認的?個對?數 與 該成員變量??的較?值。

VS中的默認對齊數是8.

? int i的大小是4個字節,對齊數就是4。int i 的地址要對齊到為偏移量整數倍的地址,也就是4的整數倍,偏移量為4的地址。int i 是4個字節,那占據的地址偏移量為4~7

char c2 的大小是1個字節,對齊數是1。1可以為任意偏移量的整數倍。所以char c2的地址的偏移量就是8.

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

成員變量有char c1,int i ,char c2。它們的對齊數分別是1,4,1。因此最大對齊數為4。

結構體總大小為最大對齊數的整數倍,現在偏移量是0~8,一共是9個字節,要湊成4的整數倍,就是12個字節,在浪費3個字節就可以了,地址偏移量9~11一共是3個字節。

這個結構體的內存就儲存在偏移量為0~11的空間。

d4167cce0c0f45e893871db222a54036.png

??????????

2.例子

#include<stdio.h>
struct S2
{char c1;char c2;int i;
};
int main()
{printf("%d\n",sizeof(struct S2));return 0;
}

?1e4f04a380214b4bb0f2a9f78eed0999.png

??????????

解析:? ?

右邊表示的是偏移量,

1.第一個成員char c1要對齊到和結構體變量起始位置偏移量為0的地址處,占一個字節

2.其他成員要對齊到對齊數的整數倍的地址處

對?數 = 編譯器默認的?個對?數 與 該成員變量??的較?值。

VS中的默認對齊數是8.

char c1? 的大小是1個字節,對齊數就是1。char c1的地址要對齊到為偏移量整數倍的地址,也就是1的整數倍,偏移量為1的地址。

int i 的大小是4個字節,對齊數是4。int i 的地址就要移到偏移量為4的倍數的地址。所以int i 的地址的偏移量就是4.int i 是4個字節,那占據的地址偏移量為4~7

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

成員變量有char c1,int i ,char c2。它們的對齊數分別是1,4,1。因此最大對齊數為4。

結構體總大小為最大對齊數的整數倍,現在偏移量是0~7,剛好是8個字節,是4的倍數。

這個結構體的內存就儲存在偏移量為0~7的空間。

d669a6337351463187f729ae196ee7cb.png

????????

?3.例子

#include<stdio.h>
struct S3
{double d;char c;int i;
};
int main()
{printf("%d\n",sizeof(struct S3));return 0;
}

4264bdd243934829baf65243a4fed71d.png

解析:?

1.第一個成員要對齊到結構體變量起始位置偏移量為0的地址處,double d占8個字節,所以占據的內存空間是偏移量為0~7的地址

2.其他成員要對齊到對齊數的整數倍的地址處

char c的大小是1個字節,任意偏移量都可以為1的整數倍,所以char c的地址是下一位,偏移量為8的地址。

int i 的大小是4個字節,要對齊到偏移量為4的倍數的地址,也就是偏移量為12,int i 占據的內存空間為偏移量為12~15的地址。

3.結構體的大小為最大對齊數的整數倍。

最大對齊數是double的對齊數,也就是8。現在的結構體占16個字節(偏移量為0~15),剛好是8的倍數。

695853325b444691812d75cc57fff25c.png

??????????

4.例子

這個例子包括了嵌套結構體的情況,嵌套的結構體成員對?到??的成員中最?對?數的整數倍處,結構體的整體??就是所有最?對?數(含嵌套結構體中成員的對?數)的整數倍。

#include<stdio.h>
struct S3
{double d;char c;int i;
};
struct S4
{char c1;struct S3 s3;double d;
};
int main()
{printf("%d\n",sizeof(struct S4));return 0;
}

?4a85bf8db58440a5992a83d6a540841a.png

解析:?

1.第一個成員要對齊到結構體變量起始位置偏移量為0的地址處,char c1占1個字節,占據偏移量為0的空間。

2.嵌套的結構體成員對?到??的成員中最?對?數的整數倍處,結構體的整體??就是所有最?對?數(含嵌套結構體中成員的對?數)的整數倍。

接下來是struct s3,要對齊自己成員的最大對齊數,double d的對齊數為8個字節,對齊到偏移量為8的地址,

3.其他成員要對齊到對齊數的整數倍的地址處,嵌套的結構體成員也是這樣,double d占據8個字節,占據偏移量為8~15的地址。

char c對齊偏移量16,占據一個字節。

int i 的對齊數為4,對齊偏移量為20,占據4個字節,就是偏移量為20~23的空間。

struct S3整理完,繼續到struct S4,輪到double d

double d的對齊數為8,對齊偏移量24,占據8個字節,占據空間偏移量為24~31。

4.結構體的大小為最大對齊數的整數倍。

當前空間一共是32個字節(0~31),結構體struct S4,struct S3中的成員的最大對齊數是8。因此結構體的大小要是最大對齊數的整數倍。32剛好是8的整數倍。

b3918788de7c482f9619ec761120d842.png

??????????

offsetof

返回成員的偏移量 ,頭文件<stddef.h>

offsetof (type,member)

a0f30360e47c4f359c4a43f75531940b.png

offsetof的使用

type是類型,

#include <stdio.h>
#include <stddef.h>
struct S1
{char c1;int i;char c2;
};
int main()
{printf("%d\n", offsetof(struct S1, c1));printf("%d\n", offsetof(struct S1, i));printf("%d\n", offsetof(struct S1, c2));return 0;
}

?3e8aa4f2d6b64882ad78ae257b6f82c9.png

??????????

?為什么要存在內存對齊

1. 平臺原因 (移植原因):
不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
2. 性能原因:
數據結構(尤其是棧)應該盡可能地在?然邊界上對?。原因在于,為了訪問未對?的內存,處理器需要 作兩次內存訪問;?對?的內存訪問僅需要?次訪問。假設?個處理器總是從內存中取8個字節,則地址必須是8的倍數。如果我們能保證將所有的double類型的數據的地址都對?成8的倍數,那么就可以 ??個內存操作來讀或者寫值了。否則,我們可能需要執?兩次內存訪問,因為對象可能被分放在兩 個8字節內存塊中。
總體來說:結構體的內存對?是拿空間來換取時間的做法。

?以32為機器為例,32位機器一次可以訪問32位比特位的數據,

如果沒有對齊規則,就像左邊,機器要訪問兩次才可以得到 int i 的值,

有對齊規則,就像右邊,想要訪問 i ,只需要訪問一次就足夠了。

對齊規則的思想:把數據放在機器可以一次訪問得到數據的空間內,使訪問更具效率。?

f8a8927cd0f343869f356c4706bb6311.png? ?

修改默認對齊數

當結構體的對齊方式不適合時,我們也可以修改默認對齊數。

  • 在括號填寫數字,對默認對齊數進行修改。
  • 如果()內沒有數字,則時將默認對齊數恢復到默認值。
#pragma pack()

下面的struct S原本是占據12個字節的空間,對默認對齊數進行修改后,只占據6個字節的空間。?

#include <stdio.h>
#pragma pack(1)//設置默認對?數為1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消設置的對?數,還原為默認
int main()
{//輸出的結果是什么?printf("%d\n", sizeof(struct S));return 0;
}
0ca731e68b5c4920b09113e7131a054a.png

? ?????????

?結構體傳參

  • 傳值調用,將數據通過參數傳過去,然后函數print會創立獨立的空間,對傳過來的數據進行存儲
  • 傳址調用,將數據的地址傳過去,函數通過指向數據的地址對數據進行使用,不需要再建立空間對數據進行存放。
#include<stdio.h>
struct S
{int data[1000];int num;
};
void print1(struct S ss)
{int i = 0;for (i = 0; i < 3; i++){printf("%d ", ss.data[i]);}printf("%d\n", ss.num);
}
void print2(struct S* ps)
{int i = 0;for (i = 0; i < 3; i++){printf("%d ", ps->data[i]);}printf("%d\n", ps->num);
}
int main()
{struct S s = { {1,2,3},100 };print1(s);print2(&s);return 0;
}

b2d177f2dc1c40c6b3b1b06078388bec.png

????????

上面的傳值調用print1?和 傳址調用print2 函數那哪個更好?

答案是:?選print2函數。
原因:
函數傳參的時候,參數是需要壓棧,會有時間和空間上的系統開銷。
如果傳遞?個結構體對象的時候,結構體過?,參數壓棧的的系統開銷?較?,所以會導致性能的下降。

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

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

相關文章

Ubuntu——永久掛載/卸載硬盤

Ubuntu——永久掛載/卸載硬盤 一、掛載1. 查詢硬盤2. 格式化硬盤3. 掛載硬盤4. 開機自動掛載5. 查看掛載是否成功 二、取消掛載參考資料&#xff1a; 一、掛載 1. 查詢硬盤 sudo fdisk -l2. 格式化硬盤 # 格式化硬盤(/dev/sda)的文件系統&#xff0c;這里格式化為 ext4 系統…

面試經典150題(3-4)

leetcode 150道題 計劃花兩個月時候刷完&#xff0c;今天&#xff08;第二天&#xff09;完成了兩道(3-4)150&#xff1a; (26. 刪除有序數組中的重復項) 題目描述&#xff1a; 給你一個 非嚴格遞增排列 的數組 nums &#xff0c;請你 原地 刪除重復出現的元素&#xff0c;使…

C#生成Token字符串

Token字符串來保證數據安全性&#xff0c;如身份驗證、跨域訪問等。但是由于Token字符串的長度比較長&#xff0c;可能會占用過多的空間和帶寬資源&#xff0c;因此我們需要生成短的Token字符串 方法一&#xff1a;使用Base64編碼 Base64編碼是一種常用的編碼方式&#xff0c…

測試:接口參數測試

接口參數測試是接口測試中非常重要的一部分&#xff0c;主要是為了驗證接口在不同參數輸入下的行為和響應。下面詳細介紹一下接口參數測試的相關內容&#xff1a; 參數必填與非必填測試&#xff1a;需要測試接口對必填參數和非必填參數的處理。對于必填參數&#xff0c;不提供…

【學習筆記】LLM for Education

ChatGPT has entered the classroom: how LLMs could transform education 前言IntroductionThe risks are realEmbracing LLMsIntroducing the AI tutorAugmenting retrievalWill it catch on?總結 前言 一篇來自Nature的文章&#xff0c;探討了教育行業的不同參與者&#x…

webSRc實現瀏覽器播放rtsp【海康】

先上代碼 <template><div>video的配置自己寫<video id"video" autoplay width"900" height"900"></video></div> </template><script> export default {name: index1,data() {return {webRtcServer: …

WampServer本地部署結合內網穿透實現公網訪問本地服務

文章目錄 前言1.WampServer下載安裝2.WampServer啟動3.安裝cpolar內網穿透3.1 注冊賬號3.2 下載cpolar客戶端3.3 登錄cpolar web ui管理界面3.4 創建公網地址 4.固定公網地址訪問 前言 Wamp 是一個 Windows系統下的 Apache PHP Mysql 集成安裝環境&#xff0c;是一組常用來…

ESP32-Web-Server編程-通過 Base64 編碼在網頁中插入圖片

ESP32-Web-Server編程-通過 Base64 編碼在網頁中插入圖片 概述 不同于上節 ESP32-Web-Server編程-在網頁中通過 src 直接插入圖片,本節引入 Base64 編碼來顯示圖片。 Base64 是一種用64個字符來編碼表示任意二進制數據的方法。任何符號都可以轉換成 Base64 字符集中的字符,…

在做題中學習(31):電話號碼的字母組合(全排列)

17. 電話號碼的字母組合 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;既然要排列組合&#xff0c;就得先根據數字字符取出來 所以先定義一個string類的數組通過下標取到每個數字對應的映射。 string _numsTostr[10]{"","","abc"…

聊聊AsyncHttpClient的KeepAliveStrategy

序 本文主要研究一下AsyncHttpClient的KeepAliveStrategy KeepAliveStrategy org/asynchttpclient/channel/KeepAliveStrategy.java public interface KeepAliveStrategy {/*** Determines whether the connection should be kept alive after this HTTP message exchange.…

進程的相關知識

進程基本概念&#xff1a;1、進程是程序的一次執行過程&#xff0c;進程是資源分配的基本單位&#xff1b;2、每個進程都會分配自己的0至3G的內存空間&#xff0c;這個0至3G的內存空間可以有多份&#xff0c;但是3G至4G的內核空間獨一份&#xff1b;3、進程其實是內核創建的&am…

gitee對接使用

1.創建一個文件夾 2.進入Gitee接受對方項目編輯 3.打開終端初始化一開始創建的文件夾 git init 3.1打開終端 3.2輸入git.init 4.克隆對方的項目 4.1進入Gitee復制對方項目的路徑 4.2在編輯器終端內克隆對方項目 git clone 網址 如此你的編輯器就會出現對方的項目 …

小紅書AI文章寫作工具,免費的小紅書AI寫作工具有哪些

社交媒體已經成為人們交流、分享生活和獲取信息的主要平臺之一。而在這眾多社交媒體中&#xff0c;小紅書以其獨特的社區氛圍和內容特色而備受矚目。如何更高效地進行小紅書文章創作&#xff0c;本文將深入研究小紅書文章AI寫作工具。 小紅書文章AI寫作工具背后的技術 隨著人工…

Java基于Rest Assured自動化測試接口詳解

前言 不知道大家的項目是否都有對接口API進行自動化測試&#xff0c;反正像我們這種小公司是沒有的。由于最近一直被吐槽項目質量糟糕&#xff0c;只能研發自己看看有什么接口測試方案。那么在本文中&#xff0c;我將探索如何使用 Rest Assured 自動化 API 測試&#xff0c;Re…

基于Java SSM框架實現寵物醫院信息管理系統項目【項目源碼】計算機畢業設計

基于java的SSM框架實現寵物醫院信息管理系統演示 java簡介 Java語言是在二十世紀末由Sun公司發布的&#xff0c;而且公開源代碼&#xff0c;這一優點吸引了許多世界各地優秀的編程愛好者&#xff0c;也使得他們開發出當時一款又一款經典好玩的小游戲。Java語言是純面向對象語言…

關于加密解密,加簽驗簽那些事

面對MD5、SHA、DES、AES、RSA等等這些名詞你是否有很多問號&#xff1f;這些名詞都是什么&#xff1f;還有什么公鑰加密、私鑰解密、私鑰加簽、公鑰驗簽。這些都什么鬼&#xff1f;或許在你日常工作沒有聽說過這些名詞&#xff0c;但是一旦你要設計一個對外訪問的接口&#xff…

聚焦中國—東盟大健康產業峰會 點靚廣西“長壽福地”品牌

12月8-10日2023中國—東盟大健康產業峰會暨大健康產業博覽會在南寧國際會展中心成功舉辦&#xff0c;本次峰會由國家中醫藥管理局、廣西壯族自治區人民政府聯合主辦&#xff0c;中國老年學和老年醫學學會、自治區黨委宣傳部、自治區民政廳、廣西壯族自治區外事辦公室、廣西壯族…

MySQL使用窗口函數ROW_NUMBER()、DENSE_RANK()查詢每組第一名或每組前幾名,窗口函數使用詳解

MySQL數據表結構 創建 tbl_class_info 表&#xff0c;表中有四個字段 id、username、score、group_name 使用 ROW_NUMBER()、DENSE_RANK() 查詢每組前三名 -- 查詢每組前3名 SELECT username, score, group_name FROM ( SELECT username, score, group_name, ROW_NUMBER()…

目標檢測——R-FCN算法解讀

論文&#xff1a;R-FCN: Object Detection via Region-based Fully Convolutional Networks 作者&#xff1a;Jifeng Dai, Yi Li, Kaiming He and Jian Sun 鏈接&#xff1a;https://arxiv.org/pdf/1605.06409v2.pdf 代碼&#xff1a;https://github.com/daijifeng001/r-fcn 文…

5.鴻蒙hap可以直接點擊包安裝嗎?

5.鴻蒙hap可以直接點擊包安裝嗎&#xff1f; hap與apk不同&#xff0c;獲取的hap不能直接安裝 安裝方法1&#xff1a; DevEco studio打開項目源文件&#xff0c;打開手機USB調試&#xff0c;DevEco識別到手機后&#xff0c;點擊播放按鈕安裝到手機 https://txwtech.blog.cs…