dw自己做網(wǎng)站需要什么高端企業(yè)網(wǎng)站模板
文章目錄
- 1. 結(jié)構(gòu)體類(lèi)型的聲明
- 1. 1 結(jié)構(gòu)體聲明
- 1. 2 結(jié)構(gòu)體變量的創(chuàng)建和初始化
- 1. 3 結(jié)構(gòu)體的特殊聲明
- 1. 3 結(jié)構(gòu)體的自引用
- 2. 結(jié)構(gòu)體內(nèi)存對(duì)齊
- 2. 1 對(duì)齊規(guī)則
- 2. 2 為什么存在內(nèi)存對(duì)齊
- 2. 3 修改默認(rèn)對(duì)齊數(shù)
- 3. 結(jié)構(gòu)體傳參
- 4. 結(jié)構(gòu)體實(shí)現(xiàn)位段
- 4. 1 什么是位段
- 4. 2 位段成員的內(nèi)存分配
- 4. 3 位段的跨平臺(tái)問(wèn)題
- 4. 4 位段的使用
- 4. 5 位段使用的注意事項(xiàng)
1. 結(jié)構(gòu)體類(lèi)型的聲明
1. 1 結(jié)構(gòu)體聲明
格式如下:
struct tag
{member - list;//結(jié)構(gòu)成員,可以不止一個(gè)
}variable - list;//在這里可以直接創(chuàng)建結(jié)構(gòu)體變量,可以用逗號(hào)隔開(kāi)來(lái)創(chuàng)建多個(gè),不能初始化
例如描述一個(gè)學(xué)生:
struct student //這個(gè)結(jié)構(gòu)體的名稱(chēng)
{ //以下是結(jié)構(gòu)成員char name[20]; //姓名char sex[5]; //性別int age; //年齡char id[10]; //學(xué)號(hào)
}A, B; //聲明結(jié)構(gòu)體時(shí)創(chuàng)建了學(xué)生A和B,注意分號(hào)不能丟
進(jìn)行聲明時(shí),還可以使用 typedef 進(jìn)行重命名:
typedef struct student
{char name[20];char sex[5];int age;char id[10];
}ST;//之后創(chuàng)建結(jié)構(gòu)體變量時(shí),就可以將 ST 作為類(lèi)型使用了,注意這樣就無(wú)法在聲明結(jié)構(gòu)體時(shí)創(chuàng)建變量了
1. 2 結(jié)構(gòu)體變量的創(chuàng)建和初始化
除了在聲明時(shí)創(chuàng)建變量,還可以像創(chuàng)建int
等其他變量一樣創(chuàng)建并初始化結(jié)構(gòu)體變量。
#include<stdio.h>struct student
{char name[20];char sex[5];int age;char id[10];
}A, B;void Print(struct student S)
{printf("%s %s %d %s\n", S.name, S.sex, S.age, S.id);
}int main()
{ //按照定義順序初始化struct student s1 = { "張三","man",15,"122111" };Print(s1);//按照指定順序初始化struct student s2 = { .age = 18,.id = "454541",.name = "fhvyxyci",.sex = "man" };Print(s2);return 0;
}
1. 3 結(jié)構(gòu)體的特殊聲明
//匿名結(jié)構(gòu)體變量
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], * p;
上面的兩個(gè)結(jié)構(gòu)在聲明的時(shí)候省略掉了結(jié)構(gòu)體標(biāo)簽。
那么問(wèn)題來(lái)了:
//在上面代碼的基礎(chǔ)上,下面的代碼合法嗎?
p = &x;
編譯器會(huì)把上面的兩個(gè)聲明當(dāng)成完全不同的兩個(gè)類(lèi)型,所以是非法的。
匿名的結(jié)構(gòu)體類(lèi)型,如果沒(méi)有對(duì)結(jié)構(gòu)體類(lèi)型(使用 typedef )重命名的話(huà),基本上只能使用一次。
1. 3 結(jié)構(gòu)體的自引用
1. 在結(jié)構(gòu)中包含一個(gè)類(lèi)型為該結(jié)構(gòu)本身的成員是否可以呢?
比如,定義一個(gè)鏈表(一種數(shù)據(jù)結(jié)構(gòu))的節(jié)點(diǎn):
struct Node
{int data;struct Node next;
};
上述代碼正確嗎?如果正確,那sizeof(struct Node)
是多少?
仔細(xì)分析,其實(shí)是不行的,因?yàn)橐粋€(gè)結(jié)構(gòu)體中再包含一個(gè)同類(lèi)型的結(jié)構(gòu)體變量,這樣結(jié)構(gòu)體變量的大
小就會(huì)無(wú)窮的大,是不合理的。
正確的自引用方式:
struct Node
{int data;struct Node* next;//這里放上一個(gè)指針,就合理多了
};
2. 在結(jié)構(gòu)體自引用使用的過(guò)程中,夾雜了 typedef 對(duì)匿名結(jié)構(gòu)體類(lèi)型重命名,也容易引入問(wèn)題,
看下面的代碼,可行嗎?
typedef struct
{int data;Node* next;
}Node;
答案是不行的,因?yàn)?code>Node是對(duì)前面的匿名結(jié)構(gòu)體類(lèi)型的重命名產(chǎn)生的,但是在匿名結(jié)構(gòu)體內(nèi)部提前使用Node類(lèi)型來(lái)創(chuàng)建成員變量,這是不行的。
解決方案:定義結(jié)構(gòu)體不要使用匿名結(jié)構(gòu)體。
typedef struct Node
{int data;struct Node* next;//這里要使用沒(méi)有重命名的名字
}Node;
2. 結(jié)構(gòu)體內(nèi)存對(duì)齊
2. 1 對(duì)齊規(guī)則
- 結(jié)構(gòu)體的第一個(gè)成員對(duì)齊到和結(jié)構(gòu)體變量起始位置偏移量為0的地址處
- 其他成員變量要對(duì)產(chǎn)到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。
對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) 與 該成員變量大小的最大值的較小值。
VS 中默認(rèn)的值為 8
Linux中 gcc 沒(méi)有默認(rèn)對(duì)齊數(shù),對(duì)齊數(shù)就是成員自身的大小 - 結(jié)構(gòu)體總大小為最大對(duì)齊數(shù)(結(jié)構(gòu)體中每個(gè)成員變量都有一個(gè)對(duì)齊數(shù),所有對(duì)齊數(shù)中最大的)的整數(shù)倍。
- 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體成員對(duì)齊到自己的對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體中成員的對(duì)齊數(shù))的整數(shù)倍。
來(lái)做幾個(gè)練習(xí)鞏固一下:
練習(xí)一:
#include<stdio.h>
struct S1
{char c1;int i;char c2;
};
int main()
{printf("%zd\n", sizeof(struct S1));return 0;
}
首先 c1
:根據(jù)對(duì)齊規(guī)則1,c1
占1
個(gè)字節(jié),和結(jié)構(gòu)體處于同一個(gè)地址。
然后 i
:根據(jù)對(duì)齊規(guī)則2,i
需要對(duì)齊到相對(duì)結(jié)構(gòu)體地址的偏移量為4的位置,所以i
的起始地址相對(duì)結(jié)構(gòu)體是4
,目前結(jié)構(gòu)體大小為8
。
然后c2:占一個(gè)字節(jié),結(jié)構(gòu)體大小直接+1
(任何偏移量都是1的倍數(shù),所以不需要額外偏移)。
最后,根據(jù)對(duì)齊規(guī)則3, 對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) (以VS為例,8)與 該成員變量大小的最大值的較小值,這里顯然對(duì)齊數(shù)是4
,因此9的下一個(gè)對(duì)齊數(shù)的倍數(shù)是12
,所以結(jié)構(gòu)體的大小是12。
圖解:
輸出結(jié)果:
代碼二:
#include<stdio.h>struct S2
{char c1;char c2;int i;
};int main()
{printf("%zd\n", sizeof(struct S2));return 0;
}
c1
不比多說(shuō),來(lái)看c2
:char
類(lèi)型的大小是1,任何偏移量都一定是1的倍數(shù),所以到了c2
,結(jié)構(gòu)體的大小是2。
接著看i
:int
變量的大小是4,2后面的最小的4的倍數(shù)是4,所以此時(shí)結(jié)構(gòu)體的大小是8。
最后對(duì)齊數(shù)也是4,所以結(jié)構(gòu)體的大小就是8。
圖解:
輸出結(jié)果:
代碼三:
#include<stdio.h>struct S3
{double d;char c;int i;
};int main()
{printf("%zd\n", sizeof(struct S3));return 0;
}
首先d
:double
類(lèi)型為8個(gè)字節(jié)。
然后c
:結(jié)構(gòu)體大小+1。
然后i
:9后面第一個(gè)4的倍數(shù)是12,所以從12開(kāi)始向后+4。
最后對(duì)齊數(shù):對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) (8)與 該成員變量最大值的較小值(8),所以對(duì)齊數(shù)是8,最終大小就是16。
圖解:
運(yùn)行結(jié)果:
;練習(xí)四:
//結(jié)構(gòu)體嵌套問(wèn)題
#include<stdio.h>
struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%zd\n", sizeof(struct S4));return 0;
}
c1
不再贅述,來(lái)看這個(gè) s3
:
s3的對(duì)齊數(shù)是8,所以s3
的偏移量為8,上面我們已經(jīng)算出了S3的大小是16,所以現(xiàn)在S4
的大小是24。
因?yàn)?strong>24是8的倍數(shù),所以d
就再向后找8個(gè)地址,是32。
S4
的偏移量是S4
中所有除了S3
以外的元素和S3
的所有成員的大小中的最大值與默認(rèn)對(duì)齊數(shù)8之間的較小值,是8,所以S4
的大小是32。
圖解:
輸出結(jié)果:
2. 2 為什么存在內(nèi)存對(duì)齊
- 平臺(tái)原因(移植原因):
不是所有的硬件平臺(tái)都能訪(fǎng)問(wèn)任意地址上的任意數(shù)據(jù)的,某些硬件平臺(tái)只能在某些地址處取某些特定類(lèi)型的數(shù)據(jù),否則拋出硬件異常。 - 性能原因:
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于,為了訪(fǎng)問(wèn)未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪(fǎng)問(wèn),而對(duì)齊的內(nèi)存訪(fǎng)問(wèn)僅需要一次訪(fǎng)問(wèn)。假設(shè)一個(gè)處理器總是從內(nèi)存中取8個(gè)字節(jié),則地址必須是8的倍數(shù)。如果我們能保證將所有的double
類(lèi)型的數(shù)據(jù)的地址都對(duì)齊成8的倍數(shù),那么就可以用一個(gè)內(nèi)存操作來(lái)讀或者寫(xiě)值了。否則,我們可能需要執(zhí)行兩次內(nèi)存訪(fǎng)問(wèn),因?yàn)閷?duì)象可能被分放在兩個(gè)8字節(jié)內(nèi)存塊中。
總體來(lái)說(shuō):結(jié)構(gòu)體的內(nèi)存對(duì)齊是拿空間來(lái)?yè)Q取時(shí)間的做法。
那在設(shè)計(jì)結(jié)構(gòu)體的時(shí)候,我們既要滿(mǎn)足對(duì)齊,又要節(jié)省空間,如何做到?
讓占用空間小的成員盡量集中在一起。
例如:
struct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};
經(jīng)過(guò)上面的計(jì)算,你會(huì)發(fā)現(xiàn)雖然這兩個(gè)結(jié)構(gòu)體的成員一樣,但是大小卻差的很多。
2. 3 修改默認(rèn)對(duì)齊數(shù)
#pragma
這個(gè)預(yù)處理指令,可以改變編譯器的默認(rèn)對(duì)齊數(shù)。
#include <stdio.h>
#pragma pack(1)//設(shè)置默認(rèn)對(duì)齊數(shù)為1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消設(shè)置的對(duì)齊數(shù),還原為默認(rèn)
int main()
{//輸出的結(jié)果是什么?printf("%zd\n", sizeof(struct S));return 0;
}
結(jié)果是6:
說(shuō)明對(duì)齊數(shù)是在結(jié)構(gòu)體聲明時(shí)計(jì)算的,而不是調(diào)用時(shí)。
盡管使用場(chǎng)景可能比較少,但是在對(duì)齊方式不合適的時(shí)候,我們可以自己更改默認(rèn)對(duì)齊數(shù)。
3. 結(jié)構(gòu)體傳參
#include<stdio.h>
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };
//結(jié)構(gòu)體傳參
void print1(struct S s)
{printf("%d\n", s.num);
}
//結(jié)構(gòu)體地址傳參
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //傳結(jié)構(gòu)體print2(&s); //傳地址return 0;
}
上面的 print1
和 print2
函數(shù)哪個(gè)好些?
答案是:首選print2
函數(shù)。
原因:
函數(shù)傳參的時(shí)候,參數(shù)是需要壓棧(可以理解為拷貝實(shí)參到形參),會(huì)有時(shí)間和空間上的系統(tǒng)開(kāi)銷(xiāo)。
傳遞一個(gè)結(jié)構(gòu)體對(duì)象,結(jié)構(gòu)體過(guò)大,參數(shù)壓棧的的系統(tǒng)開(kāi)銷(xiāo)就大,會(huì)導(dǎo)致性能的下降。
結(jié)論:結(jié)構(gòu)體傳參的時(shí)候,要傳結(jié)構(gòu)體的地址。
4. 結(jié)構(gòu)體實(shí)現(xiàn)位段
4. 1 什么是位段
位段的聲明和結(jié)構(gòu)是類(lèi)似的,但是有兩個(gè)不同:
- 位段的成員必須是
int
、unsigned int
或signed int
(在C99中位段成員也可以選擇其他類(lèi)型)。 - 位段的成員名后邊有一個(gè)冒號(hào)和一個(gè)數(shù)字。
比如:
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
A就是一個(gè)位段類(lèi)型。
那位段A所占內(nèi)存的大小是多少?
是8。
我們來(lái)了解一下。
4. 2 位段成員的內(nèi)存分配
位段的成員可以是 int unsigned int signed int
或者是 char
等類(lèi)型
位段的空間上是按照需要以4個(gè)字節(jié)(int
)或者1個(gè)字節(jié) (char
)的方式來(lái)開(kāi)辟的。
位段涉及很多不確定因素(比如上一行),位段是不跨平臺(tái)的,注重可移植的程序應(yīng)該避免使用位段。
//一個(gè)例子
#include<stdio.h>
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 = 12;s.c = 3;s.d = 4;//空間是如何開(kāi)辟的?return 0;
}
在VS上是這樣開(kāi)辟的:每個(gè)字節(jié)從右向左使用,如果下一個(gè)位段成員比較大,就舍棄該字節(jié)中剩下的比特位去開(kāi)辟新的字節(jié)。
那么上面的那個(gè)8字節(jié)也就很好分析出來(lái)了。
4. 3 位段的跨平臺(tái)問(wèn)題
- int 位段被當(dāng)成有符號(hào)數(shù)還是無(wú)符號(hào)數(shù)是不確定的。
- 位段中最大位的數(shù)目不能確定。
(16位機(jī)器最大16,32位機(jī)器最大32,寫(xiě)成27,在16位機(jī)器會(huì)出問(wèn)題。 - 位段中的成員在內(nèi)存中從左向右分配,還是從右向左分配,標(biāo)準(zhǔn)尚未定義。
- 當(dāng)一個(gè)結(jié)構(gòu)包含兩個(gè)位段,第二個(gè)位段成員比較大,無(wú)法容納于第一個(gè)位段剩余的位時(shí),是舍棄
剩余的位還是利用,這是不確定的。
總結(jié):
跟結(jié)構(gòu)相比,位段可以達(dá)到同樣的效果,并且可以很好的節(jié)省空間,但是有跨平臺(tái)的問(wèn)題存在。
4. 4 位段的使用
下圖是網(wǎng)絡(luò)協(xié)議中,IP數(shù)據(jù)報(bào)的格式,我們可以看到其中很多的屬性只需要幾個(gè)比特位就能描述,這里使用位段,能夠?qū)崿F(xiàn)想要的效果,也節(jié)省了空間,這樣網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)報(bào)大小也會(huì)較小一些,對(duì)網(wǎng)絡(luò)的暢通是有幫助的。
4. 5 位段使用的注意事項(xiàng)
位段的幾個(gè)成員共有同一個(gè)字節(jié),這樣有些成員的起始位置并不是某個(gè)字節(jié)的起始位置,那么這些位置處是沒(méi)有地址的。內(nèi)存中每個(gè)字節(jié)分配一個(gè)地址,一個(gè)字節(jié)內(nèi)部的比特位是沒(méi)有地址的。
所以不能對(duì)位段的成員使用&
操作符,這樣就不能使用scanf
直接給位段的成員輸入值,只能先輸入放在一個(gè)變量中,然后賦值給位段的成員。
#include<stdio.h>struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};int main()
{struct A sa = { 0 };//scanf("%d", &sa._b);//這是錯(cuò)誤的//正確的示范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}
如果喜歡這篇博客的話(huà)不妨順手點(diǎn)個(gè)贊,收藏,評(píng)論,關(guān)注!
我會(huì)持續(xù)更新更多優(yōu)質(zhì)文章!!