欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報邀請碼778899分享:開發(fā)語言 C語言詳解(結(jié)構(gòu)體)

柚子快報邀請碼778899分享:開發(fā)語言 C語言詳解(結(jié)構(gòu)體)

http://yzkb.51969.com/

? ? ? Hi~!這里是奮斗的小羊,很榮幸各位能閱讀我的文章,誠請評論指點,歡迎歡迎~~? ? ?

?????????????????????????????????????????????????個人主頁:小羊在奮斗

?????????????????????????????????????????????????所屬專欄:C語言? ?

????????本系列文章為個人學(xué)習(xí)筆記,在這里撰寫成文一為鞏固知識,二為一些學(xué)友們展示一下我的學(xué)習(xí)過程及理解。文筆、排版拙劣,望見諒。?

????????????????????????????????1、結(jié)構(gòu)體類型的聲明

????????????????????????????????2、結(jié)構(gòu)體內(nèi)存對齊

????????????????????????????????3、結(jié)構(gòu)體傳參

????????????????????????????????4、結(jié)構(gòu)體實現(xiàn)位段

1、結(jié)構(gòu)體類型的聲明

? ? ? ? 1.1結(jié)構(gòu)體變量的創(chuàng)建和初始化

? ? ? ? 其實之前在C語言(操作符)2中,我們已經(jīng)比較詳細(xì)地介紹過結(jié)構(gòu)體變量的創(chuàng)建和初始化,這里再補充一個特殊的初始化方法——按照指定的順序初始化。

? ? ? ? 前面我們學(xué)到的初始化方法是按結(jié)構(gòu)體成員的順序初始化,就像下面這樣:

? ? ? ? 除了按順序初始化,我們也可以按指定的順序初始化:

? ? ? ? 這兩種初始化方法得到的效果是一樣的。

? ? ? ? 1.2結(jié)構(gòu)體的特殊聲明

? ? ? ? 我們之前學(xué)過的結(jié)構(gòu)聲明常規(guī)形式是這樣的:

?????????但在聲明結(jié)構(gòu)的時候,還可以不完全聲明,就是省略掉自定義名。

? ? ? ? 但是這種不完全的結(jié)構(gòu)體聲明必須在聲明的同時直接創(chuàng)建變量,并且這個類型只能使用一次,也就是創(chuàng)建一次變量,但是一次可以創(chuàng)建多個。

? ? ? ? 下面這個代碼的問題在哪兒呢?用這個不完全的結(jié)構(gòu)類型創(chuàng)建一個指針p,將p1的地址賦給p。

? ? ? ?當(dāng)我們運行起來就會發(fā)現(xiàn)編譯器報警告,說兩個指針類型不兼容。

? ? ? ? 這是因為我們創(chuàng)建的結(jié)構(gòu)體類型是沒有名字的,雖然兩個成員一樣,但編譯器認(rèn)為它們兩個的地址類型是不一樣的。

? ? ? ? 1.3結(jié)構(gòu)的自引用

? ? ? ? 什么是結(jié)構(gòu)的自引用呢?說白了就是結(jié)構(gòu)自己引用自己,有點遞歸的意思。舉個例子,當(dāng)我們想將一個數(shù)據(jù)存到內(nèi)存中時,可以按順序存,也可以隨機地存,只要能找到就行。那當(dāng)我們隨機存的時候,找到第一個數(shù)怎么找到第二個數(shù)就是一個問題。

? ? ? ? 這時候我們可以定義一個結(jié)構(gòu)體類型,有兩個成員名,一個存數(shù)據(jù),另一個存下一個數(shù)據(jù)的地址,這樣的話當(dāng)我們找到第一個數(shù)就能找到第二個數(shù),以此類推。

? ? ? ? 在結(jié)構(gòu)體的自引用過程中,夾雜了 typedef 對不完全結(jié)構(gòu)體類型聲明的重命名,也容易引出問題。

? ? ? ? 上面的代碼是否正確呢?我們重命名了結(jié)構(gòu)體類型名,并且在結(jié)構(gòu)體成員中也用了重命名后的類型名。這樣做是不對的,因為我們是要重定義這個結(jié)構(gòu)體類型的類型名,上面的代碼是在沒有重定義之前就使用了,打破了順序的問題。

2、結(jié)構(gòu)體內(nèi)存對齊

? ? ? ? 2.1對齊現(xiàn)象?

? ? ? ? 在介紹之前我們可以先猜一下結(jié)構(gòu)體類型是怎么計算大小的呢?

? ? ? ?上面兩個結(jié)構(gòu)體類型成員變量相同,都是兩個char類型和一個int類型,那兩個結(jié)構(gòu)體類型的大小會是6個字節(jié)嗎?

? ? ? ? ?可以看到結(jié)果并不是我們猜測的,而且上面兩個結(jié)構(gòu)體類型只是改變了成員變量的順序,它們的大小就發(fā)生了變化。那我們可以得到的結(jié)論是,結(jié)構(gòu)體類型的大小并不是單純的成員變量類型大小之和,而且結(jié)構(gòu)體類型的大小還跟成員順序有關(guān)系。這是為什么呢?

? ? ? ? 其實,結(jié)構(gòu)體的成員在內(nèi)存中是存在對齊現(xiàn)象的。

? ? ? ? ? ? ? ?

? ? ? ? 接著我們就來探討一下上面兩個結(jié)構(gòu)體類型的大小為什么是8個字節(jié)和12個字節(jié)。假設(shè)上面是一塊內(nèi)存,一小格表示一個字節(jié),第一個字節(jié)相較于起始位置偏移量為1,第二個字節(jié)相較于起始位置偏移量為2,以此類推,這就是偏移量的概念。

????????用結(jié)構(gòu)體類型 struct S1 創(chuàng)建一個結(jié)構(gòu)體變量s,假設(shè)s從第0個字節(jié)開始,我們知道s的大小是8個字節(jié),那其成員n、c1、c2分別在哪個位置呢?這里再介紹一個宏 offsetof ,它的作用是計算結(jié)構(gòu)體成員相較于結(jié)構(gòu)體變量起始位置的偏移量。

?????????可以看到n的偏移量為0,c1的偏移量為4,c2的偏移量為5。也就是說這三個結(jié)構(gòu)體成員在內(nèi)存是像下面這樣存的。

? ? ? ? 但是,這也不夠8個字節(jié)啊,難道說結(jié)構(gòu)體變量s即使用不到第6、7個字節(jié)但還是將這兩個字節(jié)霸占了嗎?是的,這兩個字節(jié)就相當(dāng)于浪費掉了。

? ? ? ? 那用結(jié)構(gòu)體類型 struct S2 創(chuàng)建的結(jié)構(gòu)體變量所占的12個字節(jié)里n、c1、c2三個成員變量是存在哪些位置呢??

? ? ? ??可以看到c2的偏移量為0,n的偏移量為4,c1的偏移量為8。也就是說這三個結(jié)構(gòu)體成員在內(nèi)存中是像下面這樣存的。

? ? ? ? 可以看到上面也沒有12個字節(jié),并且把第1、2、3、9、10、11個字節(jié)都浪費了。那這又是為什么呢?

? ? ? ? 通過上面的內(nèi)容我們可以得到的結(jié)論是,結(jié)構(gòu)體成員在內(nèi)存中存的時候并不是一個挨著一個存的,而是按一定的規(guī)則存儲的,這個規(guī)則就是結(jié)構(gòu)內(nèi)存對齊。?

2.2對齊規(guī)則

? ? ? ? 如果我們想要計算結(jié)構(gòu)體類型的大小,就必須要先了解結(jié)構(gòu)體的內(nèi)存對齊,才能知道結(jié)構(gòu)體類型在內(nèi)存中究竟是如何開辟空間的。具體的對齊規(guī)則如下:

? ? ? ? (1)結(jié)構(gòu)體的第一個成員(不管是什么類型)對齊到和結(jié)構(gòu)體變量起始位置偏移量為0的地址處;

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

? ? ? ? ???對齊數(shù) = 編譯器默認(rèn)的一個對齊數(shù) 與 該成員變量大小的較小值,VS中默認(rèn)的值為8,Linux中g(shù)cc沒有默認(rèn)對齊數(shù),對齊數(shù)就是成員自身的大小.

? ? ? ? (3)結(jié)構(gòu)體總大小為最大對齊數(shù)(結(jié)構(gòu)體中每個成員變量都有一個對齊數(shù),所有對齊數(shù)中最大的)的整數(shù)倍;

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

? ? ? ? ?了解了對齊規(guī)則,我們就來解釋一下上面兩個結(jié)構(gòu)體類型的大小為什么是8個字節(jié)和12個字節(jié)。

? ? ? ? 先看結(jié)構(gòu)體類型 struct S1,根據(jù)規(guī)則(1),我們知道n存在第0、1、2、3這四個字節(jié)中。根據(jù)規(guī)則(2),VS默認(rèn)對齊數(shù)是8,c1的大小為1小于默認(rèn)對齊數(shù),c1要對齊到1的整數(shù)倍的地址處,所以c1存到了第4個字節(jié)中;再看c2,c2的大小也是1小于默認(rèn)對齊數(shù),c2要對齊到1的整數(shù)倍的地址處,所以c2存到了第5個字節(jié)中。根據(jù)規(guī)則(3),結(jié)構(gòu)體成員中最大對齊數(shù)為4,此時結(jié)構(gòu)體成員所占6個字節(jié),不是4的倍數(shù),所以還要再額外占用兩個字節(jié)的空間,那這兩個字節(jié)的空間就被浪費掉了。所以最終這個結(jié)構(gòu)體類型的大小就是8個字節(jié)。

? ? ? ? 再看結(jié)構(gòu)體類型 struct S2,根據(jù)規(guī)則(1),我們知道c2存在第0個字節(jié)中。根據(jù)規(guī)則(2),VS默認(rèn)對齊數(shù)是8,n的大小為4小于默認(rèn)對齊數(shù),n要對齊到4的整數(shù)倍的地址處,所以n存到了第4、5、6、7個字節(jié)中,那第1、2、3個字節(jié)就被浪費掉了;再看c1,c1的大小是1小于默認(rèn)對齊數(shù),c1要對齊到1的整數(shù)倍的地址處,所以c1存到了第8個字節(jié)中。根據(jù)規(guī)則(3),結(jié)構(gòu)體成員中最大對齊數(shù)為4,此時結(jié)構(gòu)體成員所占9個字節(jié),不是4的倍數(shù),所以還要再額外占用三個字節(jié)的空間,那這三個字節(jié)的空間也被浪費掉了。所以最終這個結(jié)構(gòu)體類型的大小就是12個字節(jié)。

? ? ? ? 再來通過下面這個練習(xí)理解規(guī)則(4):

? ? ? ? 可以看到結(jié)構(gòu)體類型 struct S2 的大小是24個字節(jié),三個結(jié)構(gòu)體成員的偏移量分別為0、4和16。那么其成員變量在內(nèi)存中的存儲就應(yīng)該是這樣:

? ? ? ? 其中結(jié)構(gòu)體變量s的大小是8個字節(jié),其結(jié)構(gòu)體類型中成員變量最大對齊數(shù)為4,而對于結(jié)構(gòu)體類型 struct S2 其成員變量中最大對齊數(shù)為8,所以最終結(jié)構(gòu)體類型的大小就是24。?

? ? ? ? 2.3為什么存在內(nèi)存對齊

? ? ? ? 通過上面的學(xué)習(xí)我們知道內(nèi)存對齊的時候很容易就浪費了內(nèi)存空間,那為什么還要存在內(nèi)存對齊呢??大部分的參考資料都是這樣說的:

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

? ? ? ? 不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的,某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),比如只能取int類型,那就只能訪問4的倍數(shù)的內(nèi)存空間,否則拋出硬件異常。

? ? ? ? (2)性能原因

? ? ? ? 數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要做兩次內(nèi)存訪問,而對齊的內(nèi)存訪問僅需要一次訪問。假設(shè)一個處理器總是從內(nèi)存中取8個字節(jié),則地址必須是8的倍數(shù)。如果我們能保證將所有的double類型的數(shù)據(jù)的地址都對齊成8的倍數(shù),那么就可以用一個內(nèi)存操作來讀或?qū)懥恕7駝t,我們可能需要執(zhí)行兩次內(nèi)存訪問,因為對象可能被分在兩個8字節(jié)內(nèi)存塊中。

? ? ? ? 什么意思呢?假設(shè)創(chuàng)建一個結(jié)構(gòu)體類型,其中成員變量為char類型的c和int類型的n。

????????下面是不考慮對齊的情況,直接在c后面存n:

? ? ? ? 下面是考慮對齊的情況:

?

? ? ? ??因為n為int類型,考慮對齊的話c后面三個字節(jié)的空間就浪費掉了。

? ? ? ? 假設(shè)我們現(xiàn)在要用一個32位的機器去訪問這個結(jié)構(gòu)體的成員變量n,32位的機器一次能訪問4個字節(jié)的內(nèi)存,那在開始位置訪問不考慮對齊的情況時需要訪問兩次才能讀取完整的n,但是在訪問考慮對齊的情況時只需要訪問一次就行了,因為n前面剛好是4個字節(jié)可以跳過就沒有必要訪問前面的內(nèi)存了,這樣的話效率就會提高。

? ? ? ??總的來說:結(jié)構(gòu)體的內(nèi)存對齊是拿空間來換取時間的做法。

? ? ? ??那在設(shè)計結(jié)構(gòu)體的時候,我們既要滿足對齊,又要節(jié)省空間,如何做到呢?

? ? ? ? 有一個小技巧:讓占用空間小的成員盡量集中在一起。就像前面我們創(chuàng)建的 struct S1 和 struct S2 一樣,雖然兩個成員一樣,但是成員順序不一樣最終兩個結(jié)構(gòu)體類型的大小也就不一樣了。

?????????2.4修改默認(rèn)對齊數(shù)

? ? ? ? #pragma 這個預(yù)處理指令,可以改變編譯器的默認(rèn)對齊數(shù)。

? ? ? ? 上面結(jié)構(gòu)體類型 struct S 的大小在默認(rèn)對齊數(shù)下是12個字節(jié)。

????????當(dāng)我們將默認(rèn)對齊數(shù)改為1時,結(jié)構(gòu)體類型 struct S 的大小就變成了6個字節(jié)。因為結(jié)構(gòu)體成員c1、n、c2的大小分別為1、4、1,而默認(rèn)對齊數(shù)是1的時候,其每個成員的對齊數(shù)都為1,就相當(dāng)于沒有對齊,是緊挨著存儲的。

? ? ? ? 默認(rèn)對齊數(shù)一般修改的都是2的次方數(shù)。

3、結(jié)構(gòu)體傳參

? ? ? ? 來看下面的代碼:

#include

struct S

{

int arr[1000];

char ch;

int n;

};

void print1(struct S tmp)

{

int i = 0;

for (i = 0; i < 10; i++)

{

printf("%d ", tmp.arr[i]);

}

printf("\n");

printf("ch = %c\n", tmp.ch);

printf("n = %d\n", tmp.n);

}

void print2(struct S* tmp)

{

int i = 0;

for (i = 0; i < 10; i++)

{

printf("%d ", tmp->arr[i]);

}

printf("\n");

printf("ch = %c\n", tmp->ch);

printf("n = %d\n", tmp->n);

}

int main()

{

struct S s = { {1,2,3,4,5,6,7,8,9,10}, 'a', 8 };

print1(s);

print2(&s);

return 0;

}

? ? ? ? 上面寫了兩個函數(shù)print1和print2打印結(jié)構(gòu)體變量s的內(nèi)容,print1用的是傳值調(diào)用,print2用的是傳址調(diào)用,哪個更好呢?

? ? ? ? 答案是傳址調(diào)用更好。傳值調(diào)用時形參tmp是拷貝了一份結(jié)構(gòu)體變量s,需要壓棧,要開辟一塊和s大小相等的內(nèi)存空間,而且拷貝的過程也是需要時間的,所以時間和空間上都要消耗;而傳址調(diào)用指針tmp只需要接收一個4個字節(jié)或8個字節(jié)的地址就行,并不需要額外開辟新的空間,并且還沒有拷貝過程中時間的消耗。

? ? ? ? 另外,傳值調(diào)用能做到的傳址調(diào)用都能做到,但是傳址調(diào)用能做到的傳值調(diào)用未必都能做到,比如間接修改內(nèi)存中的值。要是我們不想指針指向的對象被修改也可以加上const修飾,這樣就沒什么后顧之憂了。

? ? ? ? 所以,結(jié)構(gòu)體傳參的時候,要傳結(jié)構(gòu)體的地址。

4、結(jié)構(gòu)體實現(xiàn)位段

? ? ? ? 4.1什么是位段

? ? ? ? 位段的聲明和結(jié)構(gòu)體是類似的,有兩個不同:

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

? ? ? (2)位段的成員名后邊有一個冒號和一個數(shù)字。

? ? ? ? 來看結(jié)構(gòu)體和位段聲明的比較:

? ? ? ? 位段中的位指的是二進制位也就是比特位,所以能想到的是位段中冒號后面的數(shù)字指的就是比特位,其中_a占2個比特位,_b占5個比特位,_c占10個比特位,_d占30個比特位。(成員名前面的_只是編程習(xí)慣沒有特殊意思)。而int型大小是4個字節(jié)最大32位,所以不能超過這個數(shù)。

? ? ? ? 為什么要有位段呢?

? ? ? ? 以前我們在寫代碼的時候,有沒有想過這樣一個問題。就是說我們創(chuàng)建了一個整型變量_a,它占4個字節(jié),但是這個_a我們只是想讓它表示0、1、2、3這四個值,而這四個值二進制表示為00、01、10、11只需要2個比特位就行了,那我們還給它開辟一個32位的空間是不是太浪費了。同樣的如果_b只需要5個比特位就夠了,_c只需要10個比特位就夠了,_d只需要30個比特位就夠了,那我們就沒有必要給它們都開辟4個字節(jié)的內(nèi)存空間了。

? ? ? ? 為了實現(xiàn)這種比較精準(zhǔn)的內(nèi)存大小的開辟,位段就出現(xiàn)了。我們只需要按它們的需求開辟相應(yīng)大小的內(nèi)存空間,就能避免很多不必要的浪費。我們用上面聲明的結(jié)構(gòu)體和位段來做個驗證,結(jié)構(gòu)體的大小是16個字節(jié),位段是不是真的變小了呢?

? ? ? ? 可以看到位段所占內(nèi)存是結(jié)構(gòu)體的一半。但是_a_b_c_d加起來一共是47個比特位,按道理來說6個字節(jié)就夠了,為什么是8個字節(jié)呢?這跟位段的內(nèi)存分配有關(guān)。

? ? ? ? 4.2位段的內(nèi)存分配

? ? ? ? (1)位段的成員可以是 int、unsigned int、signed int 或者是 char 類型;

? ? ? ? (2)位段的空間上是按照需要以4個字節(jié)(int)或1個字節(jié)(char)的方式來開辟的;

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

? ? ? ? 那位段到底是如何分配內(nèi)存的呢?

? ? ? ? 以上面這個位段為例,char類型一次開辟8個比特位,而a只需要占用3個比特位就行,但是這時候就有個問題,a是從左往右存呢還是從右往左存呢(左右或上下都是一樣的,這里以左右為例),這是不確定的,不妨假設(shè)是從右向左存的。a存好占用了3個比特位,剩下的5個比特位還足夠存b,存好b后只剩下1個比特位不夠存c了,還需要再開辟8個比特位,那這時候又有個問題,剩下的那一個比特位是浪費掉呢還是存一部分c呢,這也是不確定的,不妨我們再假設(shè)不夠存下一個數(shù)據(jù)的話就浪費掉。在開辟的第二個字節(jié)中存好c后還剩3個比特位不足以存d,還需要再開辟一個字節(jié)存d,按我們的假設(shè)需要開辟3個字節(jié)的空間。

? ? ? ? 開辟好內(nèi)存空間后,就需要給相應(yīng)的成員變量賦值,10的二進制表示是1010,但是a只有3個比特位,所以a中只存了010;12的二進制表示是1100,所以b中存了1100;3的二進制表示是11,所以c中存了00011;4的二進制表示是100,所以d中存了0100。

? ? ? ? 如果真按如上我們假設(shè)的存儲方式存儲,那在內(nèi)存中應(yīng)該是下面這樣:

?????????

? ? ? ? 可以看到,在當(dāng)前VS環(huán)境下,我們的分析是正確的。了解了位段的內(nèi)存分配,我們再回到上面的問題,為什么下面這個位段是8個字節(jié)而不是6個字節(jié)。

? ? ? ? 一個int類型是32位,存好_a_b_c需要17個比特位,剩下15個比特位顯然不足以存_d,所以還需要再開辟4個字節(jié)的空間存_d,所以總共需要8個字節(jié),而不是6個字節(jié)。

? ? ? ? 4.3位段的跨平臺問題

? ? ? ? (1)int 位段被當(dāng)成有符號數(shù)還是無符號數(shù)是不確定的;

? ? ? ? (2)位段中最大位的數(shù)目不能確定(16位機器最大16,32位機器最大32,寫成27,在16位機器中會出問題);

? ? ? ? (3)位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配,標(biāo)準(zhǔn)尚未定義;

? ? ? ? (4)當(dāng)一個結(jié)構(gòu)包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余位還是利用,這是不確定的。

? ? ? ? ?總結(jié):跟結(jié)構(gòu)相比,位段可以達到同樣的效果,并且可以很好的節(jié)省空間,但是有夸平臺的問題。

? ? ? ? 4.4位段使用的注意事項

? ? ? ? 位段的幾個成員可能共有同一個字節(jié)的地址,這樣有些成員的起始位置并不是某個字節(jié)的起始位置,那么這些位置處是沒有地址的。內(nèi)存中每個字節(jié)分配一個地址,一個字節(jié)內(nèi)部的比特位是沒有地址的。

? ? ? ? 所以不能對位段的成員使用&操作符,這樣就不能使用scanf直接給位段的成員輸入值,只能是先輸入放在一個變量中,然后賦值給位段的成員。

????????????如果覺得我的文章還不錯,請點贊、收藏 + 關(guān)注支持一下,我會持續(xù)更新更好的文章。???

柚子快報邀請碼778899分享:開發(fā)語言 C語言詳解(結(jié)構(gòu)體)

http://yzkb.51969.com/

精彩內(nèi)容

評論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://m.gantiao.com.cn/post/19137196.html

發(fā)布評論

您暫未設(shè)置收款碼

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄