什么是結(jié)構(gòu)體
- 結(jié)構(gòu)體和數(shù)組一樣屬于構(gòu)造類型
- 數(shù)組是用于保存一組相同類型數(shù)據(jù)的, 而結(jié)構(gòu)體是用于保存一組不同類型數(shù)組的
- 例如,在學生登記表中,姓名應(yīng)為字符型;學號可為整型或字符型;年齡應(yīng)為整型;性別應(yīng)為字符型;成績可為整型或?qū)嵭汀?/li>
- 顯然這組數(shù)據(jù)不能用數(shù)組來存放, 為了解決這個問題,C語言中給出了另一種構(gòu)造數(shù)據(jù)類型——“結(jié)構(gòu)(structure)”或叫“結(jié)構(gòu)體”。
定義結(jié)構(gòu)體類型
- 在使用結(jié)構(gòu)體之前必須先定義結(jié)構(gòu)體類型, 因為C語言不知道你的結(jié)構(gòu)體中需要存儲哪些類型數(shù)據(jù), 我們必須通過定義結(jié)構(gòu)體類型來告訴C語言, 我們的結(jié)構(gòu)體中需要存儲哪些類型的數(shù)據(jù)
- 格式:
struct 結(jié)構(gòu)體名{
類型名1 成員名1;
類型名2 成員名2;
……
類型名n 成員名n;
};
- 示例:
struct Student {
char *name; // 姓名
int age; // 年齡
float height; // 身高
};
定義結(jié)構(gòu)體變量
定好好結(jié)構(gòu)體類型之后, 我們就可以利用我們定義的結(jié)構(gòu)體類型來定義結(jié)構(gòu)體變量
-
格式:
struct 結(jié)構(gòu)體名 結(jié)構(gòu)體變量名;
先定義結(jié)構(gòu)體類型,再定義變量
struct Student {
char *name;
int age;
};
struct Student stu;
- 定義結(jié)構(gòu)體類型的同時定義變量
struct Student {
char *name;
int age;
} stu;
- 匿名結(jié)構(gòu)體定義結(jié)構(gòu)體變量
struct {
char *name;
int age;
} stu;
- 第三種方法與第二種方法的區(qū)別在于,第三種方法中省去了結(jié)構(gòu)體類型名稱,而直接給出結(jié)構(gòu)變量,這種結(jié)構(gòu)體最大的問題是結(jié)構(gòu)體類型不能復用
結(jié)構(gòu)體成員訪問
- 一般對結(jié)構(gòu)體變量的操作是以成員為單位進行的,引用的一般形式為:
結(jié)構(gòu)體變量名.成員名
struct Student {
char *name;
int age;
};
struct Student stu;
// 訪問stu的age成員
stu.age = 27;
printf("age = %d", stu.age);
結(jié)構(gòu)體變量的初始化
- 定義的同時按順序初始化
struct Student {
char *name;
int age;
};
struct Student stu = {“l(fā)nj", 27};
- 定義的同時不按順序初始化
struct Student {
char *name;
int age;
};
struct Student stu = {.age = 35, .name = “l(fā)nj"};
- 先定義后逐個初始化
struct Student {
char *name;
int age;
};
struct Student stu;
stu.name = "lnj";
stu.age = 35;
- 先定義后一次性初始化
struct Student {
char *name;
int age;
};
struct Student stu;
stu2 = (struct Student){"lnj", 35};
結(jié)構(gòu)體類型作用域
- 結(jié)構(gòu)類型定義在函數(shù)內(nèi)部的作用域與局部變量的作用域是相同的
- 從定義的那一行開始, 直到遇到return或者大括號結(jié)束為止
- 結(jié)構(gòu)類型定義在函數(shù)外部的作用域與全局變量的作用域是相同的
- 從定義的那一行開始,直到本文件結(jié)束為止
//定義一個全局結(jié)構(gòu)體,作用域到文件末尾
struct Person{
int age;
char *name;
};
int main(int argc, const char * argv[])
{
//定義局部結(jié)構(gòu)體名為Person,會屏蔽全局結(jié)構(gòu)體
//局部結(jié)構(gòu)體作用域,從定義開始到“}”塊結(jié)束
struct Person{
int age;
};
// 使用局部結(jié)構(gòu)體類型
struct Person pp;
pp.age = 50;
pp.name = "zbz";
test();
return 0;
}
void test() {
//使用全局的結(jié)構(gòu)體定義結(jié)構(gòu)體變量p
struct Person p = {10,"sb"};
printf("%d,%s\n",p.age,p.name);
}
結(jié)構(gòu)體數(shù)組
- 結(jié)構(gòu)體數(shù)組和普通數(shù)組并無太大差異, 只不過是數(shù)組中的元素都是結(jié)構(gòu)體而已
- 格式:
struct 結(jié)構(gòu)體類型名稱 數(shù)組名稱[元素個數(shù)]
struct Student {
char *name;
int age;
};
struct Student stu[2];
- 結(jié)構(gòu)體數(shù)組初始化和普通數(shù)組也一樣, 分為先定義后初始化和定義同時初始化
- 定義同時初始化
struct Student {
char *name;
int age;
};
struct Student stu[2] = {{"lnj", 35},{"zs", 18}};
- 先定義后初始化
struct Student {
char *name;
int age;
};
struct Student stu[2];
stu[0] = {"lnj", 35};
stu[1] = {"zs", 18};
結(jié)構(gòu)體指針
- 一個指針變量當用來指向一個結(jié)構(gòu)體變量時,稱之為結(jié)構(gòu)體指針變量
- 格式:
struct 結(jié)構(gòu)名 *結(jié)構(gòu)指針變量名 - 示例:
// 定義一個結(jié)構(gòu)體類型
struct Student {
char *name;
int age;
};
// 定義一個結(jié)構(gòu)體變量
struct Student stu = {“l(fā)nj", 18};
// 定義一個指向結(jié)構(gòu)體的指針變量
struct Student *p;
// 指向結(jié)構(gòu)體變量stu
p = &stu;
/*
這時候可以用3種方式訪問結(jié)構(gòu)體的成員
*/
// 方式1:結(jié)構(gòu)體變量名.成員名
printf("name=%s, age = %d \n", stu.name, stu.age);
// 方式2:(*指針變量名).成員名
printf("name=%s, age = %d \n", (*p).name, (*p).age);
// 方式3:指針變量名->成員名
printf("name=%s, age = %d \n", p->name, p->age);
return 0;
}
- 通過結(jié)構(gòu)體指針訪問結(jié)構(gòu)體成員, 可以通過以下兩種方式
- (*結(jié)構(gòu)指針變量).成員名
- 結(jié)構(gòu)指針變量->成員名(用熟)
- (pstu)兩側(cè)的括號不可少,因為成員符“.”的優(yōu)先級高于“”。
- 如去掉括號寫作pstu.num則等效于(pstu.num),這樣,意義就完全不對了。
結(jié)構(gòu)體內(nèi)存分析
- 給結(jié)構(gòu)體變量開辟存儲空間和給普通開辟存儲空間一樣, 會從內(nèi)存地址大的位置開始開辟
- 給結(jié)構(gòu)體成員開辟存儲空間和給數(shù)組元素開辟存儲空間一樣, 會從所占用內(nèi)存地址小的位置開始開辟
- 結(jié)構(gòu)體變量占用的內(nèi)存空間永遠是所有成員中占用內(nèi)存最大成員的倍數(shù)(對齊問題)
+多實際的計算機系統(tǒng)對基本類型數(shù)據(jù)在內(nèi)存中存放的位置有限制,它們會要求這些數(shù)據(jù)的起始地址的值是 某個數(shù)k的倍數(shù),這就是所謂的內(nèi)存對齊,而這個k則被稱為該數(shù)據(jù)類型的對齊模數(shù)(alignment modulus)。
- 這種強制的要求一來簡化了處理器與內(nèi)存之間傳輸系統(tǒng)的設(shè)計,二來可以提升讀取數(shù)據(jù)的速度。比如這么一種處理器,它每次讀寫內(nèi)存的時候都從某個8倍數(shù)的地址開始,一次讀出或?qū)懭?個字節(jié)的數(shù)據(jù),假如軟件能 保證double類型的數(shù)據(jù)都從8倍數(shù)地址開始,那么讀或?qū)懸粋€double類型數(shù)據(jù)就只需要一次內(nèi)存操作。否則,我們就可能需要兩次內(nèi)存操作才能完成這個動作,因為數(shù)據(jù)或許恰好橫跨在兩個符合對齊要求的8字節(jié) 內(nèi)存塊上
結(jié)構(gòu)體變量占用存儲空間大小
struct Person{
int age; // 4
char ch; // 1
double score; // 8
};
struct Person p;
printf("sizeof = %i\n", sizeof(p)); // 16
- 占用內(nèi)存最大屬性是score, 占8個字節(jié), 所以第一次會分配8個字節(jié)
- 將第一次分配的8個字節(jié)分配給age4個,分配給ch1個, 還剩下3個字節(jié)
- 當需要分配給score時, 發(fā)現(xiàn)只剩下3個字節(jié), 所以會再次開辟8個字節(jié)存儲空間
- 一共開辟了兩次8個字節(jié)空間, 所以最終p占用16個字節(jié)
struct Person{
int age; // 4
double score; // 8
char ch; // 1
};
struct Person p;
printf("sizeof = %i\n", sizeof(p)); // 24
- 占用內(nèi)存最大屬性是score, 占8個字節(jié), 所以第一次會分配8個字節(jié)
- 將第一次分配的8個字節(jié)分配給age4個,還剩下4個字節(jié)
- 當需要分配給score時, 發(fā)現(xiàn)只剩下4個字節(jié), 所以會再次開辟8個字節(jié)存儲空間
- 將新分配的8個字節(jié)分配給score, 還剩下0個字節(jié)
- 當需要分配給ch時, 發(fā)現(xiàn)上一次分配的已經(jīng)沒有了, 所以會再次開辟8個字節(jié)存儲空間
- 一共開辟了3次8個字節(jié)空間, 所以最終p占用24個字節(jié)
結(jié)構(gòu)體嵌套定義
- 成員也可以又是一個結(jié)構(gòu),即構(gòu)成了嵌套的結(jié)構(gòu)
struct Date{
int month;
int day;
int year;
}
struct stu{
int num;
char *name;
char sex;
struct Date birthday;
Float score;
}
-
在stu中嵌套存儲Date結(jié)構(gòu)體內(nèi)容
- 注意:
- 結(jié)構(gòu)體不可以嵌套自己變量,可以嵌套指向自己這種類型的指針
struct Student { int age; struct Student stu; };
- 對嵌套結(jié)構(gòu)體成員的訪問
- 如果某個成員也是結(jié)構(gòu)體變量,可以連續(xù)使用成員運算符"."訪問最低一級成員
struct Date {
int year;
int month;
int day;
};
struct Student {
char *name;
struct Date birthday;
};
struct Student stu;
stu.birthday.year = 1986;
stu.birthday.month = 9;
stu.birthday.day = 10;
結(jié)構(gòu)體和函數(shù)
- 結(jié)構(gòu)體雖然是構(gòu)造類型, 但是結(jié)構(gòu)體之間賦值是值拷貝, 而不是地址傳遞
struct Person{
char *name;
int age;
};
struct Person p1 = {"lnj", 35};
struct Person p2;
p2 = p1;
p2.name = "zs"; // 修改p2不會影響p1
printf("p1.name = %s\n", p1.name); // lnj
printf("p2.name = %s\n", p2.name); // zs
- 所以結(jié)構(gòu)體變量作為函數(shù)形參時也是值傳遞, 在函數(shù)內(nèi)修改形參, 不會影響外界實參
#include <stdio.h>
struct Person{
char *name;
int age;
};
void test(struct Person per);
int main()
{
struct Person p1 = {"lnj", 35};
printf("p1.name = %s\n", p1.name); // lnj
test(p1);
printf("p1.name = %s\n", p1.name); // lnj
return 0;
}
void test(struct Person per){
per.name = "zs";
}
共用體
- 和結(jié)構(gòu)體不同的是, 結(jié)構(gòu)體的每個成員都是占用一塊獨立的存儲空間, 而共用體所有的成員都占用同一塊存儲空間
- 和結(jié)構(gòu)體一樣, 共用體在使用之前必須先定義共用體類型, 再定義共用體變量
- 定義共用體類型格式:
union 共用體名{
數(shù)據(jù)類型 屬性名稱;
數(shù)據(jù)類型 屬性名稱;
... ....
};
- 定義共用體類型變量格式:
union 共用體名 共用體變量名稱;
- 特點: 由于所有屬性共享同一塊內(nèi)存空間, 所以只要其中一個屬性發(fā)生了改變, 其它的屬性都會受到影響
- 示例:
union Test{
int age;
char ch;
};
union Test t;
printf("sizeof(p) = %i\n", sizeof(t));
t.age = 33;
printf("t.age = %i\n", t.age); // 33
t.ch = 'a';
printf("t.ch = %c\n", t.ch); // a
printf("t.age = %i\n", t.age); // 97
- 共用體的應(yīng)用場景
- (1)通信中的數(shù)據(jù)包會用到共用體,因為不知道對方會發(fā)送什么樣的數(shù)據(jù)包過來,用共用體的話就簡單了,定義幾種格式的包,收到包之后就可以根據(jù)包的格式取出數(shù)據(jù)。
- (2)節(jié)約內(nèi)存。如果有2個很長的數(shù)據(jù)結(jié)構(gòu),但不會同時使用,比如一個表示老師,一個表示學生,要統(tǒng)計老師和學生的情況,用結(jié)構(gòu)體就比較浪費內(nèi)存,這時就可以考慮用共用體來設(shè)計。
+(3)某些應(yīng)用需要大量的臨時變量,這些變量類型不同,而且會隨時更換。而你的堆棧空間有限,不能同時分配那么多臨時變量。這時可以使用共用體讓這些變量共享同一個內(nèi)存空間,這些臨時變量不用長期保存,用完即丟,和寄存器差不多,不用維護。
枚舉
-
什么是枚舉類型?
- 在實際問題中,有些變量的取值被限定在一個有限的范圍內(nèi)。例如,一個星期內(nèi)只有七天,一年只有十二個月,一個班每周有六門課程等等。如果把這些量說明為整型,字符型或其它類型 顯然是不妥當?shù)摹?/li>
- C語言提供了一種稱為“枚舉”的類型。在“枚舉”類型的定義中列舉出所有可能的取值, 被說明為該“枚舉”類型的變量取值不能超過定義的范圍。
-
該說明的是,枚舉類型是一種基本數(shù)據(jù)類型,而不是一種構(gòu)造類型,因為它不能再分解為任何基本類型。
-
枚舉類型的定義
- 格式:
enum 枚舉名 {
枚舉元素1,
枚舉元素2,
……
};
- 示例:
// 表示一年四季
enum Season {
Spring,
Summer,
Autumn,
Winter
};
- 枚舉變量
- 先定義枚舉類型,再定義枚舉變量
enum Season {
Spring,
Summer,
Autumn,
Winter
};
enum Season s;
- 定義枚舉類型的同時定義枚舉變量
enum Season {
Spring,
Summer,
Autumn,
Winter
} s;
- 省略枚舉名稱,直接定義枚舉變量
enum {
Spring,
Summer,
Autumn,
Winter
} s;
- 枚舉類型變量的賦值和使用
enum Season {
Spring,
Summer,
Autumn,
Winter
} s;
s = Spring; // 等價于 s = 0;
s = 3; // 等價于 s = winter;
printf("%d", s);
- 枚舉使用的注意
- C語言編譯器會將枚舉元素(spring、summer等)作為整型常量處理,稱為枚舉常量。
- 枚舉元素的值取決于定義時各枚舉元素排列的先后順序。默認情況下,第一個枚舉元素的值為0,第二個為1,依次順序加1。
- 也可以在定義枚舉類型時改變枚舉元素的值
enum Season {
Spring,
Summer,
Autumn,
Winter
};
// 也就是說spring的值為0,summer的值為1,autumn的值為2,winter的值為3
enum Season {
Spring = 9,
Summer,
Autumn,
Winter
};
// 也就是說spring的值為9,summer的值為10,autumn的值為11,winter的值為12


