一甥绿、結(jié)構(gòu)體類型的聲明
1.1 什么是結(jié)構(gòu)體?
- 結(jié)構(gòu)是?些值的集合则披,這些值稱為成員變量共缕。結(jié)構(gòu)的每個(gè)成員可以是不同類型的變量。
- 結(jié)構(gòu)體中可以包含各種類型的數(shù)據(jù)士复,用來描述一個(gè)復(fù)雜對(duì)象的各種屬性!
1.2 結(jié)構(gòu)的聲明
// 名字可以自己指定
struct tag
{
member-list; // 可以有多個(gè)成員
}variable-list; // 全局變量
代碼示例:
struct Stu
{
char name[20]; // 名字
int age; // 年齡
char sex[5]; // 性別
char id[20]; // 學(xué)號(hào)
}; // 分號(hào)不能丟
1.3 特殊的聲明
在聲明結(jié)構(gòu) 的時(shí)候图谷,可以不完全的聲明。
代碼示例:
// 匿名結(jié)構(gòu)體類型
struct
{
int a;
char b;
float c;
} s = {0};
struct
{
int a;
char b;
float c;
}* ps;
int main()
{
* ps = &s; // error
return 0;
}
代碼分析:
雖然結(jié)構(gòu)體的成員一模一樣阱洪,但是編譯器會(huì)把 = 的兩邊當(dāng)成完全不同的兩個(gè)類型便贵,所以是非法的。
匿名的結(jié)構(gòu)體類型冗荸,如果沒有對(duì)結(jié)構(gòu)體類型重命名的話承璃,基本上只能使用一次。
1.4 結(jié)構(gòu)的自引用
在結(jié)構(gòu)中包含?個(gè)類型為該結(jié)構(gòu)本?的成員是否可以呢蚌本?
比如盔粹,定義一個(gè)鏈表的節(jié)點(diǎn):
struct Node
{
int data;
struct Node next;
};
代碼分析:
上述代碼是錯(cuò)誤的隘梨,因?yàn)?個(gè)結(jié)構(gòu)體中再包含?個(gè)同類型的結(jié)構(gòu)體變量,這樣結(jié)構(gòu)體變量的大小就會(huì)無窮的大玻佩,是不合理的出嘹。
正確的自引用方式:
struct Node
{
int data;
struct Node* next;
};
在結(jié)構(gòu)體自引用的使用過程中席楚,夾雜了typedef對(duì)匿名結(jié)構(gòu)體類型重命名咬崔,也容易引入問題,下面這段代碼可行嗎烦秩?
typedef struct
{
int data;
Node* next;
}Node;
答案是不行的垮斯,因?yàn)镹ode是對(duì)前面的匿名結(jié)構(gòu)體類型的重命名產(chǎn)生的,但是在匿名結(jié)構(gòu)體內(nèi)部提前使用Node類型來創(chuàng)建成員變量只祠,這是不行的兜蠕。
解決方案如下:定義結(jié)構(gòu)體不要使用匿名結(jié)構(gòu)體了
typedef struct Node
{
int data;
struct Node* next;
}Node;
二、結(jié)構(gòu)體變量的創(chuàng)建和初始化
2.1 結(jié)構(gòu)體變量的創(chuàng)建
示例代碼:
#include <stdio.h>
struct Stu
{
char name[20];
int age;
float score;
} s4, s5; // 全局變量
int main()
{
struct Stu s1, s2, s3; // 局部變量
return 0;
}
代碼分析:
在上述代碼片段中抛寝,變量創(chuàng)建可以是局部變量熊杨,也可以是全部變量。
2.2 結(jié)構(gòu)體變量的初始化
示例代碼:
#include <stdio.h>
struct Stu
{
char name[20];
int age;
float score;
} s3 = { "wangwu" , 23, 67.5f };
int main()
{
// 初始化方法一:
struct Stu s1 = { "zhangsan" , 18, 98.5f };
struct Stu s1 = { "lisi" , 20, 92.5f };
// 初始化方法二:
struct Stu s5 = {.age = 15, .name = "haha", .score = 60.5f}盗舰;
return 0;
}
代碼分析:
在上述代碼中晶府,變量初始化的方法有兩種。
- 按照結(jié)構(gòu)體成員的順序進(jìn)行初始化钻趋,即:初始化的順序要和結(jié)構(gòu)體成員的順序保持一致川陆。
- 按照指定的順序進(jìn)行初始化,初始化的值前面要加 . 成員蛮位。
2.3 結(jié)構(gòu)成員訪問操作符
結(jié)構(gòu)成員訪問操作符有兩個(gè)较沪,一個(gè)是??.??一個(gè)是??->
形式如下:
- 結(jié)構(gòu)體變量?.?成員名
- 結(jié)構(gòu)體指針?->?成員變量名
代碼示例:
#include <stdio.h>
#include <string.h>
struct Stu
{
char name[15];//名字
int age; //年齡
};
void print_stu(struct Stu s)
{
printf("%s %d\n", s.name, s.age);
}
void set_stu(struct Stu* ps)
{
strcpy(ps->name, "李四");
ps->age = 28;
}
int main()
{
struct Stu s = { "張三", 20 };
print_stu(s);
set_stu(&s);
print_stu(s);
return 0;
}
三、結(jié)構(gòu)體內(nèi)存對(duì)齊
3.1 對(duì)齊規(guī)則
- 結(jié)構(gòu)體的第?個(gè)成員對(duì)齊到相對(duì)結(jié)構(gòu)體變量起始位置偏移量為0的地址處
- 其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處失仁。
對(duì)齊數(shù) = 編譯器默認(rèn)的?個(gè)對(duì)齊數(shù) 與 該成員變量大小的較小值尸曼。- VS中默認(rèn)的值為8
- Linux中沒有默認(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ù)倍解幽。
3.2 為什么存在內(nèi)存對(duì)齊?
- 平臺(tái)原因(移植原因):
不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù)烘苹,否則拋出硬件異常躲株。- 性能原因:
數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于镣衡,為了訪問未對(duì)齊的內(nèi)存霜定,處理器需要作兩次內(nèi)存訪問档悠;而對(duì)齊的內(nèi)存訪問僅需要?次訪問。假設(shè)?個(gè)處理器總是從內(nèi)存中取8個(gè)字節(jié)望浩,則地址必須是8的倍數(shù)辖所。如果我們能保證將所有的double類型的數(shù)據(jù)的地址都對(duì)齊成8的倍數(shù),那么就可以用?個(gè)內(nèi)存操作來讀或者寫值了磨德。否則缘回,我們可能需要執(zhí)行兩次內(nèi)存訪問,因?yàn)閷?duì)象可能被分放在兩個(gè)8字節(jié)內(nèi)存塊中典挑。- 總體來說:結(jié)構(gòu)體的內(nèi)存對(duì)齊是拿空間來換取時(shí)間的做法酥宴。
3.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è)置的默認(rèn)對(duì)?數(shù)拙寡,還原為默認(rèn)
int main()
{
//輸出的結(jié)果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
結(jié)構(gòu)體在對(duì)齊方式不合適的時(shí)候琳水,我們可以自己更改默認(rèn)對(duì)齊數(shù)肆糕。
四、結(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;
}
代碼分析:
在上述代碼中在孝,print2 函數(shù)更好些诚啃,原因如下:
- 函數(shù)傳參的時(shí)候,參數(shù)是需要壓棧浑玛,會(huì)有時(shí)間和空間上的系統(tǒng)開銷绍申。如果傳遞?個(gè)結(jié)構(gòu)體對(duì)象的時(shí)候,結(jié)構(gòu)體過大顾彰,參數(shù)壓棧的的系統(tǒng)開銷比較大极阅,所以會(huì)導(dǎo)致性能的下降。
- 結(jié)論:結(jié)構(gòu)體傳參的時(shí)候涨享,要傳結(jié)構(gòu)體的地址筋搏。