結(jié)構(gòu)體的基本概念
結(jié)構(gòu)體是一種用戶自定義的數(shù)據(jù)類型,可以包含多個(gè)不同類型的變量。通過使用結(jié)構(gòu)體,我們可以將相關(guān)聯(lián)的數(shù)據(jù)組織在一起,便于管理和使用。
結(jié)構(gòu)體的聲明
正常的結(jié)構(gòu)體聲明
在C語言中,結(jié)構(gòu)體(struct)指的是一種數(shù)據(jù)結(jié)構(gòu),是C語言中聚合數(shù)據(jù)類型的一類。
結(jié)構(gòu)體可以包含多個(gè)不同類型的數(shù)據(jù)成員,例如:int、float、char等。
結(jié)構(gòu)體的聲明方式如下:
struct 結(jié)構(gòu)體名 {
類型1 數(shù)據(jù)成員1;
類型2 數(shù)據(jù)成員2;
...
類型n 數(shù)據(jù)成員n;
};
例如:
struct student {
int id;
char name[50];
int age;
}; //注意,結(jié)構(gòu)體定義后面的分號(hào)不要忘了
以上屬于是正常的結(jié)構(gòu)體聲明↑↑↑↑↑↑↑↑↑↑↑↑
匿名結(jié)構(gòu)體聲明
既然有正常的,那就有特殊的結(jié)構(gòu)體聲明了↓↓↓↓↓↓↓↓↓↓↓↓↓↓
在聲明結(jié)構(gòu)體的時(shí)候,可以不完全的聲明。(匿名結(jié)構(gòu)體類型)
如下:
//匿名結(jié)構(gòu)體類型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}*p;
- p是結(jié)構(gòu)體指針,這里可以看到,上下兩個(gè)結(jié)構(gòu)體的成員內(nèi)容都是一模一樣的,
- 能不能實(shí)現(xiàn)
p = &x
呢?- 答案是不能,編譯器會(huì)把上面的兩個(gè)聲明當(dāng)成完全不同的結(jié)構(gòu)體類型,所以p不能放x的地址!??!
上面兩個(gè)結(jié)構(gòu)體在聲明的時(shí)候省略掉了結(jié)構(gòu)體標(biāo)簽,這樣聲明也是可以的,但是!匿名的結(jié)構(gòu)體類型,只能在創(chuàng)建的時(shí)候定義結(jié)構(gòu)體變量,如果沒有對(duì)結(jié)構(gòu)體類型重命名的話,基本上只能使用一次。
typedef 結(jié)構(gòu)體起別名
可以使用typedef關(guān)鍵字為結(jié)構(gòu)體起別名,這樣一些名字很長的結(jié)構(gòu)體名字就可以簡化,這樣當(dāng)我們需要頻繁創(chuàng)建結(jié)構(gòu)體變量的時(shí)候就比較方便。
例如:
#include <stdio.h>
struct Chinese_Student {
char name[50];
int age;
float score;
};
typedef struct Chinese_Student Student; // 為結(jié)構(gòu)體起別名Student
int main() {
Student stu = {"Tom", 20, 88.5}; // 創(chuàng)建并初始化結(jié)構(gòu)體變量,其實(shí)就等價(jià)于Chinese_Student stu = {"Tom", 20, 88.5};
printf("Name: %s\n", stu.name); // 訪問結(jié)構(gòu)體變量的成員
printf("Age: %d\n", stu.age);
printf("Score: %.1f\n", stu.score);
return 0;
}
結(jié)構(gòu)體的自引用
在結(jié)構(gòu)體中包含一個(gè)類型為結(jié)構(gòu)體本身的成員可以嗎?
比如下面這段代碼:
struct Node
{
int data;
struct Node next;
}; //error報(bào)錯(cuò)
這段代碼是會(huì)報(bào)錯(cuò)的,因?yàn)榻Y(jié)構(gòu)體中包含同一個(gè)類型的結(jié)構(gòu)體作為成員,這樣結(jié)構(gòu)體變量的大小就會(huì)無窮的大,是不合理的。
正確的結(jié)構(gòu)體自引用方式,使用結(jié)構(gòu)體指針
struct Node
{
int data;
struct Node* next;
};
結(jié)構(gòu)體變量的創(chuàng)建和初始化
對(duì)于結(jié)構(gòu)體變量的創(chuàng)建和初始化,用示例代碼來進(jìn)行說明:
#include <stdio.h>
//創(chuàng)建結(jié)構(gòu)體變量前,要先對(duì)結(jié)構(gòu)體進(jìn)行聲明↓↓↓↓↓↓
struct student {
char name[50]; //這里聲明一個(gè)student類型的結(jié)構(gòu)體,有三個(gè)成員
int age; //分別是字符類型姓名,整型類型年齡,浮點(diǎn)型類型分?jǐn)?shù)
float score;
};
int main() {
// 可以在創(chuàng)建結(jié)構(gòu)體變量的時(shí)候同時(shí)按聲明中的結(jié)構(gòu)體成員順序?qū)Y(jié)構(gòu)體變量進(jìn)行初始化操作↓↓
struct student stu1 = {"Tom", 20, 88.5};
//也可以用成員訪問操作符,不按照成員順序?qū)Y(jié)構(gòu)體成員進(jìn)行初始化操作↓↓↓↓↓
struct student stu2 = {.age=18, .name="weil", .score=99.9};
// 訪問結(jié)構(gòu)體變量的成員↓↓↓↓↓↓↓
printf("Name: %s\n", stu2.name);
printf("Age: %d\n", stu2.age);
printf("Score: %.1f\n", stu2.score);
return 0;
}
結(jié)構(gòu)體內(nèi)存對(duì)齊
對(duì)于結(jié)構(gòu)體的大小,我們可以用sizeof 運(yùn)算符進(jìn)行計(jì)算
如下:
struct S1
{
char c1; //1字節(jié)
int i; //4字節(jié)
char c2; //1字節(jié)
};
int main()
{
printf("%zd\n", sizeof(struct S1));
return 0;
}
運(yùn)行結(jié)果為
上面代碼的結(jié)構(gòu)體占的字節(jié)數(shù)大小為什么會(huì)是12個(gè)字節(jié)呢?
這就涉及到結(jié)構(gòu)體內(nèi)存對(duì)齊的知識(shí)點(diǎn)了↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
結(jié)構(gòu)體內(nèi)存對(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ù)倍。
為什么存在內(nèi)存對(duì)齊
大部分的參考資料都是這樣說的:
1.平臺(tái)原因(移植原因):
不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2.性能原因:
數(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í)間的做法。
舉例解釋
struct S1
{
char c1;
char c2;
int i;
};
struct S2
{
char c1;
int i;
char c2;
};
int main()
{
printf("%zd\n", sizeof(struct S1));
printf("%zd\n", sizeof(struct S2));
return 0;
}
以上代碼運(yùn)行結(jié)果(VS 2022):
結(jié)構(gòu)體中的成員類型相同,不同的排列順序也會(huì)影響結(jié)構(gòu)體變量的大小,這就是結(jié)構(gòu)體內(nèi)存對(duì)齊的奧妙,接下來畫內(nèi)存布局圖來進(jìn)行解釋:
那在設(shè)計(jì)結(jié)構(gòu)體的時(shí)候,我們既要滿足對(duì)齊,又要節(jié)省空間,就要讓占用空間小的成員盡量集中在一起
結(jié)構(gòu)體數(shù)組
↓↓↓↓↓↓↓↓↓↓↓↓↓創(chuàng)建結(jié)構(gòu)體數(shù)組 & 遍歷訪問結(jié)構(gòu)體數(shù)組↓↓↓↓↓↓↓↓↓↓↓↓↓
代碼示例如下:
#include <stdio.h>
// 定義一個(gè)結(jié)構(gòu)體類型
struct Student {
char name[20];
int age;
float score;
};
int main() {
// 創(chuàng)建結(jié)構(gòu)體數(shù)組并初始化
struct Student students[] = {
{"Tom", 20, 88.5},
{"Jack", 21, 92.0},
{"Alice", 19, 95.5}
};
// 遍歷訪問打印結(jié)構(gòu)體數(shù)組中的所有內(nèi)容
int len = sizeof(students) / sizeof(struct Student); // 計(jì)算數(shù)組長度
for (int i = 0; i < len; i++) {
printf("Name: %s\n", students[i].name);
printf("Age: %d\n", students[i].age);
printf("Score: %.1f\n", students[i].score);
}
return 0;
}
在這個(gè)示例中,我們首先定義了一個(gè)名為Student的結(jié)構(gòu)體類型,包含三個(gè)成員變量:name、age和score。然后,我們在main函數(shù)中創(chuàng)建了一個(gè)Student類型的結(jié)構(gòu)體數(shù)組students,并初始化其中的元素。接著,我們使用一個(gè)for循環(huán)遍歷訪問數(shù)組中的每個(gè)元素,并使用printf函數(shù)打印出每個(gè)學(xué)生的姓名、年齡和分?jǐn)?shù)。
在訪問結(jié)構(gòu)體數(shù)組元素時(shí),我們使用了結(jié)構(gòu)體變量名加上索引的方式來訪問,例如students[i].name表示訪問第i個(gè)元素的name成員變量。
結(jié)構(gòu)體指針
↓↓↓↓↓↓↓↓↓↓↓↓↓結(jié)構(gòu)體指針的創(chuàng)建 & 初始化 & 成員訪問↓↓↓↓↓↓↓↓↓↓↓↓↓
#include <stdio.h>
// 定義一個(gè)結(jié)構(gòu)體類型
struct Student {
char name[20];
int age;
float score;
};
int main() {
// 創(chuàng)建結(jié)構(gòu)體變量并初始化
struct Student stu1 = {"Tom", 20, 88.5};
struct Student stu2 = {"Jack", 21, 92.0};
struct Student stu3 = {"Alice", 19, 95.5};
// 創(chuàng)建結(jié)構(gòu)體指針變量
struct Student *stuPtr;
// 將結(jié)構(gòu)體變量的地址賦給結(jié)構(gòu)體指針變量
stuPtr = &stu1;
// 遍歷打印結(jié)構(gòu)體指針中的內(nèi)容
printf("Name: %s\n", stuPtr->name);
printf("Age: %d\n", stuPtr->age);
printf("Score: %.1f\n", stuPtr->score);
// 將結(jié)構(gòu)體指針變量指向下一個(gè)結(jié)構(gòu)體變量
stuPtr = &stu2;
// 遍歷打印結(jié)構(gòu)體指針中的內(nèi)容
printf("Name: %s\n", stuPtr->name);
printf("Age: %d\n", stuPtr->age);
printf("Score: %.1f\n", stuPtr->score);
// 將結(jié)構(gòu)體指針變量指向下一個(gè)結(jié)構(gòu)體變量
stuPtr = &stu3;
// 遍歷打印結(jié)構(gòu)體指針中的內(nèi)容
printf("Name: %s\n", stuPtr->name);
printf("Age: %d\n", stuPtr->age);
printf("Score: %.1f\n", stuPtr->score);
return 0;
}
在這個(gè)示例中,我們首先定義了一個(gè)Student結(jié)構(gòu)體類型,包含了學(xué)生的姓名、年齡和分?jǐn)?shù)。在main函數(shù)中,我們創(chuàng)建了三個(gè)Student類型的結(jié)構(gòu)體變量,并分別初始化它們。文章來源:http://www.zghlxwxcb.cn/news/detail-726525.html
然后,我們創(chuàng)建了一個(gè)Student類型的指針變量stuPtr
,用于指向這些結(jié)構(gòu)體變量。我們將第一個(gè)結(jié)構(gòu)體變量的地址賦給了stuPtr,并使用->運(yùn)算符
通過指針訪問結(jié)構(gòu)體中的數(shù)據(jù),并打印出學(xué)生的信息。接著,我們將指針變量指向下一個(gè)結(jié)構(gòu)體變量,重復(fù)上述操作,直到遍歷完所有的結(jié)構(gòu)體變量。文章來源地址http://www.zghlxwxcb.cn/news/detail-726525.html
到了這里,關(guān)于C語言——自定義類型結(jié)構(gòu)體_學(xué)習(xí)筆記的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!