1 結(jié)構(gòu)體
1.1結(jié)構(gòu)體類型的聲明
1.1.1 結(jié)構(gòu)的基礎(chǔ)知識
結(jié)構(gòu)是一些值的集合,這些值稱為成員變量。結(jié)構(gòu)的每個成員可以是不同類型的變量
1.1.2 結(jié)構(gòu)的聲明
struct tag
{
member-list;
}variable-list;
例如描述一個學(xué)生
struct Stu
{
char name[20];//名字
int age;//年齡
char sex[5];//性別
char id[20];//學(xué)號
}; //分號不能丟
1.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;
上面的兩個結(jié)構(gòu)在聲明的時候省略掉了結(jié)構(gòu)體標(biāo)簽(tag)
那么問題來了
//在上面代碼的基礎(chǔ)上,下面的代碼合法嗎?
p = &x;
警告:
編譯器會把上面的兩個聲明當(dāng)成完全不同的兩個類型
所以是非法的
1.1.4 結(jié)構(gòu)的自引用
在結(jié)構(gòu)中包含一個類型為該結(jié)構(gòu)本身的成員是否可以呢?
//代碼1
struct Node
{
int data;
struct Node next;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?
這里涉及到數(shù)據(jù)結(jié)構(gòu)
所謂數(shù)據(jù)結(jié)構(gòu)描述的就是數(shù)據(jù)在內(nèi)存中的組織結(jié)構(gòu)
這個結(jié)構(gòu)體的自引用是錯誤的
你無法計算出sizeof(struct Node)的大小,類似于套娃,一個next變量里面又包含一個結(jié)構(gòu)體,包含一個data
正確的自引用方式
//代碼2
struct Node
{
int data;
struct Node* next;
};
data//數(shù)據(jù)域
//struct Node* next//指針域
結(jié)構(gòu)體指針訪問下一個節(jié)點(diǎn)的地址進(jìn)一步訪問到下一個節(jié)點(diǎn)的數(shù)據(jù)
注意
//代碼3
typedef struct
{
int data;
Node* next;
}Node;
//這樣寫代碼,可行否?
//解決方案:
typedef struct Node
{
int data;
struct Node* next;
}Node;
當(dāng)你去編譯的時候會報錯
這里不能提前使用Node
原因我們要對一個結(jié)構(gòu)體重命名的時候,結(jié)構(gòu)體必須是完整的
這時我對結(jié)構(gòu)體重命名為Node*就無法知道這個Node星從何處而來
1.1.5 結(jié)構(gòu)體變量的定義和初始化
有了結(jié)構(gòu)體類型,那如何定義變量
struct Point
{
int x;
int y;
}p1; //聲明類型的同時定義變量p1
struct Point p2; //定義結(jié)構(gòu)體變量p2
//初始化:定義變量的同時賦初值。
struct Point p3 = {x, y};
struct Stu //類型聲明
{
char name[15];//名字
int age; //年齡
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //結(jié)構(gòu)體嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//結(jié)構(gòu)體嵌套初始化
//struct SN
//{
// char c;
// int i;
//}sn1 = { 'q', 100 }, sn2 = {.i=200, .c='w'};//全局變量
//
//struct S
//{
// double d;
// struct SN sn;
// int arr[10];
//};
//
//int main()
//{
// //struct SN sn3, sn4;//局部變量
// //printf("%c %d\n", sn2.c, sn2.i);
// struct S s = { 3.14, {'a', 99}, {1,2,3} };
// printf("%lf %c %d\n", s.d, s.sn.c, s.sn.i);
// int i = 0;
// for (i = 0; i < 10; i++)
// {
// printf("%d ", s.arr[i]);
// }
// return 0;
//}
//
1.1.6 結(jié)構(gòu)體內(nèi)存對齊
我們已經(jīng)掌握了結(jié)構(gòu)體的基本使用了
現(xiàn)在我們深入討論一個問題:計算結(jié)構(gòu)體的大小
這也是一個特別熱門的考點(diǎn): 結(jié)構(gòu)體內(nèi)存對齊
//練習(xí)1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
//練習(xí)2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
這里打印的結(jié)構(gòu)是12和8
那導(dǎo)致這個結(jié)果的原因是什么呢?
在解釋這個答案之前我們需要了解以下offsetof這個宏
這個宏可以計算結(jié)構(gòu)體成員相較于結(jié)構(gòu)體起始位置的偏移量
對于第一個例子
打印偏移量可以發(fā)現(xiàn),結(jié)構(gòu)體成員不是按照順序在內(nèi)存中連續(xù)存放的,而是有一定的對齊規(guī)則的
結(jié)構(gòu)體內(nèi)存對齊規(guī)則:
1:結(jié)構(gòu)體的第一個成員永遠(yuǎn)放在相較于結(jié)構(gòu)體變量起始位置的偏移量為0的位置
2:從第二個成員開始,往后的每個成員都要對齊到某個對其數(shù)的整數(shù)倍處
對其數(shù):結(jié)構(gòu)體成員自身大小和默認(rèn)對其數(shù)的較小值
VS上默認(rèn)對其數(shù)為8
在gcc環(huán)境上沒有默認(rèn)對其數(shù),對其數(shù)就是結(jié)構(gòu)體成員的自身大小
這里需要重點(diǎn)注意以下對其數(shù)
i的自身大小是4,4和8的默認(rèn)較小值為4,所以i必須要對齊到4的倍數(shù)
偏移量4就是4的倍數(shù),一次往后數(shù)4個字節(jié)
目前我們只占用了9個字節(jié)
為什么打印出來是12個字節(jié)呢?
當(dāng)然了這里有第三個注意的點(diǎn)就是:
結(jié)構(gòu)體的總大小,必須是最大對齊數(shù)的整數(shù)倍
最大對齊數(shù)是所有成員的對齊數(shù)中最大的值
所以結(jié)構(gòu)體成員中的最大對齊數(shù)為4,最小值12才是4的倍數(shù),所以我們打印出來12個字節(jié)
根據(jù)上圖我們可以發(fā)現(xiàn),結(jié)構(gòu)體成員第一個變量的起始位置為0,即偏移量從0開始
先找各個結(jié)構(gòu)體成員的偏移量,然后找對齊數(shù),算出總的對齊數(shù)再找結(jié)構(gòu)體成員中最大的對齊數(shù)再和總的對齊數(shù)找出對齊數(shù)的倍數(shù)即可
我們通過下圖引出第四個規(guī)則
4. 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對齊到自己的最大對齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整
體大小就是所有最大對齊數(shù)(含嵌套結(jié)構(gòu)體的對齊數(shù))的整數(shù)倍
簡而言之就是說結(jié)構(gòu)體S3中的最大對齊數(shù)為8,所以我們要從偏移量為8的位置處開始向下數(shù)16個字節(jié),因?yàn)槲覀儐为?dú)寫出S3這個結(jié)構(gòu)體依靠前3個規(guī)則就可以計算出大小為16個字節(jié),也可以參考第一個和第二個例子進(jìn)行計算
S4中的最大對齊數(shù)為8,所以總大小為32個字節(jié)
講了這么多,為什么要內(nèi)存對齊呢?
大部分的參考資料都是如是說的:
- 平臺原因(移植原因)
不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常 - 性能原因
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊
原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問
總體來說:
結(jié)構(gòu)體的內(nèi)存對齊是拿空間來換取時間的做法
那在設(shè)計結(jié)構(gòu)體的時候,我們既要滿足對齊,又要節(jié)省空間
如何做到:
讓占用空間小的成員盡量集中在一起
//例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1和S2類型的成員一模一樣,但是S1和S2所占空間的大小有了一些區(qū)別
1.1.7 修改默認(rèn)對齊數(shù)
之前我們見過了 #pragma 這個預(yù)處理指令,這里我們再次使用,可以改變我們的默認(rèn)對齊數(shù)
#include <stdio.h>
#pragma pack(8)//設(shè)置默認(rèn)對齊數(shù)為8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的默認(rèn)對齊數(shù),還原為默認(rèn)
#pragma pack(1)//設(shè)置默認(rèn)對齊數(shù)為1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消設(shè)置的默認(rèn)對齊數(shù),還原為默認(rèn)
int main()
{
//輸出的結(jié)果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
結(jié)論:
結(jié)構(gòu)在對齊方式不合適的時候,我們可以自己更改默認(rèn)對齊數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-564311.html
1.1.8 結(jié)構(gòu)體傳參
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ù)哪個好些?
答案是:首選print2函數(shù)。
原因:
函數(shù)傳參的時候,參數(shù)是需要壓棧,會有時間和空間上的系統(tǒng)開銷
如果傳遞一個結(jié)構(gòu)體對象的時候,結(jié)構(gòu)體過大,參數(shù)壓棧的的系統(tǒng)開銷比較大,所以會導(dǎo)致性能的下降
結(jié)論:結(jié)構(gòu)體傳參的時候,要傳結(jié)構(gòu)體的地址。文章來源地址http://www.zghlxwxcb.cn/news/detail-564311.html
到了這里,關(guān)于一篇文章讓你搞懂自定義類型-----結(jié)構(gòu)體的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!