结构体简介:
- 采用关键字 
struct修饰,可以将不同类型的值组合在一起。(很像C++里面的类) 
结构体格式:
1  | struct 结构体名{  | 
例子:
1  | struct Student{ // 定义结构体:Student学生  | 
声明结构体变量并调用成员:
声明结构体变量格式1:(常用)
1
struct 结构体类型名称 结构体变量名;
注意:声明自定义类型的变量时,类型名前面,不要忘记加上 struct 关键字
举例:struct Student stu1;
调用结构体变量的成员:
1
结构体变量名.成员名 = 常量或变量值;
举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main() {
struct Student stu1; //声明结构体变量
//调用结构体成员
stu1.id = 1001;
//stu1.name = "Tom"; //报错,不能直接通过赋值运算符来给字符数组赋值
strcpy(stu1.name, "Tony");
stu1.gender = 'M';
strcpy(stu1.address, "北京市海淀区五道口");
printf("id = %d,name = %s,gender = %c,address = %s\n",
stu1.id, stu1.name, stu1.gender, stu1.address);
return 0;
}说明:
1)先声明了一个 struct Student类型的变量 stu1,这时编译器就会为 stu1 分配内存,接着就可以为 stu1 的不同属性赋值。可以看到,struct 结构的属性通过点( . )来表示,比如 id 属性要写成 stu1.id。
2)字符数组是一种特殊的数组,直接改掉字符数组名的地址会报错,因此不能直接通过赋值运算符来对它进行赋值。你可以使用字符串库函数
strcpy()来进行字符串的复制操作。
声明结构体变量格式2:
除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。此时,初始化的属性个数最好与结构体中成员个数相同,且成员的先后顺序一一对应。格式:
1
struct 结构体名 结构体变量={初始化数据};
举例:
1
2
3
4
5
6
7
8
9
10
11
12//构造结构体
struct Car {
char* name;
double price;
int speed;
};
int main()
{
//声明结构体变量并初始化
struct Car aud1 = {"audi A6L", 460000.99, 175};
struct Car aud1 = {"audi A6L"};//没有赋值的会自动初始化
}**注意:**如果大括号里面的值的数量少于属性的数量,那么缺失的属性自动初始化为 0 。
声明结构体变量格式3:
方式2中大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。此时,可以为每个值指定属性名。格式:
1
struct 结构体名 结构体变量={.成员1=xxx,.成员2=yyy,...};
举例:
1
struct Car audi = {.speed=175, .name="audi A6L"};
同样,初始化的属性少于声明时的属性,剩下的那些属性都会初始化为 0 。
声明变量以后,可以修改某个属性的值。
1
2struct Car audi = {.speed=175, .name="audi A6L"};
audi.speed = 185; //将 speed 属性的值改成 185**声明结构体变量格式4:**声明类型的同时定义变量
struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。格式:
1
2
3struct 结构体名 {
成员列表
} 结构体变量名;举例:同时声明了数据类型 Circle 和该类型的变量 c1
1
2
3
4struct Circle {
int id;
double radius;
} c1;//c1就是这个结构体的变量名,用法也是c1.id等
**声明结构体变量格式5:**不指定类型名而直接定义结构体类型变量
如果结构体名(比如Student、Circle、Employee等)只用在声明时这一个地方,后面不再用到,那就可以将类型名省略。 该结构体称为
匿名结构体。格式:1
2
3struct {
成员列表;
} 变量名列表;举例:
1
2
3
4
5
6struct {
char name[20];
int age;
char gender;
char phone[11];
} emp1, emp2;struct 声明了一个匿名数据类型,然后又声明了这个类型的两个变量emp1、emp2 。与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。
1
2
3
4
5
6
7struct {
char name[20];
int age;
char gender;
char phone[11];
} emp1 = {"Lucy", 23, 'F', "13012341234"},
emp2 = {"Tony", 25, 'M', "13367896789"};上例在声明变量 emp1 和 emp2 的同时,为它们赋值。
**声明结构体变量格式6:**使用 typedef 命令(常用)
使用
typedef可以为 struct 结构指定一个别名举例:
1
2
3
4
5
6
7
8//声明结构体
typedef struct cell_phone {
int phone_no; //电话号码
double minutes_of_charge; //每分钟费用
} Phone;
//声明结构体变量
Phone p = {13012341234, 5};上例中,
Phone就是struct cell_phone的别名。声明结构体变量时,可以省略struct关键字。这种情况下,C 语言允许省略 struct 命令后面的类型名。进一步改为:
1
2
3
4
5
6
7
8//声明匿名结构体
typedef struct {
int phone_no;
double minutes_of_charge;
} Phone;
//声明结构体变量
Phone p = {13012341234, 5};
说明:
- 在创建一个结构体变量后,需要给成员赋值。在没有给成员赋值的情况下调用,打印的值是垃圾数据,可能导致程序异常终止。
 - 不同结构体变量的成员是独立的,互不影响,一个结构体变量的成员更改,不影响另外一个。
 
区分三个概念:结构体、结构体变量、结构体变量的成员:
结构体是自定义的 数据类型,表示的是一种数据类型。
结构体变量代表一个具体变量。类比:
1
2
3int num1 ; // int 是数据类型, 而num1是一个具体的int变量
struct Car car1; // Car 是结构体数据类型,而car1是一个Car变量Car 就像一个“汽车图纸”,生成出来的具体的一辆辆汽车,就类似于一个个的结构体变量。这些结构体变量都含有相同的成员,将结构体变量的成员比作“零件”,同一张图纸生产出来的零件的作用都是一样的。
结构体嵌套:
结构体的成员也是变量,那么成员可以是
基本数据类型,也可以是数组、指针、结构体等类型 。如果结构体的成员是另一个结构体,这就构成了结构体嵌套。举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25struct Date { //声明一个结构体类型 struct Date
int year; //年
int month; //月
int day; //日
};
struct Employee { //声明一个结构体类型 struct Employee
int id;
char name[20];
int age;
//结构体里面包含另一个结构体变量
struct Date birthday; //成员birthday属于struct Date类型
};
int main(){
struct Employee emp1;
emp1.id = 1001;
strcpy(emp1.name,"Tony");
emp1.age = 24;
emp1.birthday.year = 2001;
emp1.birthday.month = 3;
emp1.birthday.day = 12;
return 0;
}说明:如果成员本身又属一个结构体类型,则要用若干个点( . ),一级一级地找到最低的一级的成员。比如,
emp1.birthday.year。
结构体占用空间:
结构体占用的存储空间,不是各个属性存储空间的总和。为了计算效率,C 语言的内存占用空间一般来说,都必须是 int 类型存储空间的
整数倍。如果 int 类型的存储是4字节,那么 struct 类型的存储空间就总是4的倍数。1
2
3
4
5
6
7
8
9struct A{
char a;
int b;
} s;
int main() {
printf("%d\n", sizeof(s)); // 8
return 0;
}变量 s 的存储空间不是5个字节,而是占据8个字节。a 属性与 b 属性之间有3个字节的“空洞”。如果再加一个char类型的变量,则变成12字节
结构体变量的赋值操作:
同类型的结构体变量可以使用赋值运算符( = ),赋值给另一个变量,比如
1
2//假设student1和student2已定义为同类型的结构体变量
student1 = student2;这时会生成一个
全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生成了一份数据。也就是说,结构体变量的传递机制是
值传递,而非地址传递。这一点跟数组的赋值不同,使用赋值运算符复制数组,不会复制数据,只是传递地址。举例1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20struct Car {
double price;
char name[30];
} a = {.name = "Audi A6L", .price = 390000.99};
int main() {
struct Car b = a;
printf("%p\n", &a); //结构体a变量的地址 00007ff75a019020
printf("%p\n", &b); //结构体b变量的地址 000000a6201ffcd0
printf("%p\n", a.name); //结构体a变量的成员name的地址 00007ff719199028
printf("%p\n", b.name); //结构体b变量的成员name的地址 000000c2565ffd88
a.name[0] = 'B';
printf("%s\n", a.name); // Budi A6L
printf("%s\n", b.name); // Audi A6L
return 0;
}- 上例中,变量 b 是变量 a 的副本,两个变量的值是各自独立的,修改掉 b.name 不影响 a.name 。
 
举例2:将结构体内的字符数组改为字符指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16struct Car {
char *name;
double price;
} a = {"Audi A6L", 390000.99};
int main() {
struct Car b = a;
printf("%p\n", &a); //结构体a变量的地址 00007ff75a019020
printf("%p\n", &b); //结构体b变量的地址 000000a6201ffcd0
printf("%p\n", a.name); //结构体a变量的成员name的地址 00007ff7d778a000
printf("%p\n", b.name); //结构体b变量的成员name的地址 00007ff7d778a000
return 0;
}- name 属性变成了一个字符指针,这时 a 赋值给 b ,此时的b变量仍然是新开辟的内存空间。但是,a 和 b的 name 成员保存的指针相同,也就是说两个属性共享同一个”Audi A6L”。
 - 在C语言中,相同的字符串常量通常只会保存一份,即这些字符串常量共享相同的内存。当你声明多个指针变量并让它们指向相同的字符串常量时,它们实际上都指向相同的内存地址。字符串常量的共享,有助于减小程序的内存占用。
 
注意:C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如 == 和 != )比较两个数据结构是否相等或不等。
结构体数组:
- 本质:结构体数组是一个数组,只不过它的每个元素都是一个结构体类型的变量。
 
声明格式:结构体类型 数组名[数组长度];
1
2
3
4
5
6
7
8
9
10
11
12struct Person{
char name[20];
int age;
};
struct Person pers[3]; //pers是结构体数组名
//也可以直接声明,和上述结构体的声明六种方式是相同的
struct Person{
char name[20];
int age;
}pers[3];初始化:
1
2
3struct Student stus[3] = { {1001,"Tom",'M', 14},
{1002, "Jerry", 'M', 13},
{1003, "Lily",'F',12}};调用:结构体数组名[下标].成员名
1
stus[1].age = 23;
结构体指针:
结构体指针:指向结构体变量的指针 (将结构体变量的起始地址存放在指针变量中)
格式:struct 结构体名 *结构体指针变量名;
举例:
1
2
3
4
5
6
7struct Book {
char title[50];
char author[10];
double price;
};
struct Book *b1;**说明:**变量 b1 是一个指针,指向的数据是 struct Book 类型的实例。
结构体传参:
如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16struct Person {
char *name;
int age;
char *address;
};
void addAge(struct Person per) {
per.age = per.age + 1;
}
int main() {
struct Person p1 = {"Tom", 20, "北京市海淀区"};
addAge(p1);
printf("age = %d\n", p1.age); // 输出 20
return 0;
}函数 addAge() 要求传入一个 struct 变量 per,但实际上传递的是 struct 变量p1的
副本,改变副本影响不到函数外部的原始数据。通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性。如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16struct Person {
char *name;
int age;
char *address;
};
void addAge(struct Person *per) { //说明1
(*per).age = (*per).age + 1; //说明2
}
int main() {
struct Person p1 = {"Tom", 20, "北京市海淀区"};
addAge(&p1); //说明3
printf("age = %d\n", p1.age); // 说明4:输出 21
return 0;
}**说明1:**per 是 struct 结构的指针,调用函数时传入的是指针。
**说明2:**函数内部必须使用
(*per).age的写法,从指针拿到 struct 结构本身。因为运算符优先级问题,不能写成*per.age,会将per.age看成是一个指针,然后取其值。**说明3:**结构体类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成 &p1。
**说明4:**addAge() 内部对 struct 结构的操作,就会反映到函数外部。
-> 操作符:(指针变量引用属性)
如果一个结构体指针变量p指向一个结构体变量stu,以下3种用法等价:
1
2
3① stu.成员名 stu.num 变量调用属性
② (*p).成员名 (*p).num
③ p->成员名 p->num 指针调用属性例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct Student {
char name[20];
int age;
char gender;
};
int main() {
//打印结构体信息
struct Student s = {"张三", 20, 'M'};
//方式1:.为结构成员访问操作符
printf("name = %s,age = %d,gender = %c\n", s.name, s.age, s.gender);
struct Student *ps = &s;
//方式2:.为结构成员访问操作符
printf("name = %s,age = %d,gender = %c\n", (*ps).name, (*ps).age, (*ps).gender);
//方式3:->操作符
printf("name = %s,age = %d,gender = %c\n", ps->name, ps->age, ps->gender);
return 0;
}1
2
3
4void addAge(struct Person * per) {
//使用结构体指针访问指向对象的成员
per->age = per->age + 1;
}指向结构体数组的指针:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct Person {
int id;
char name[20];
};
int main() {
struct Person per;
struct Person arr[5];
struct Person *p,*q;
p = &per; //指向单个结构体变量
q = arr; //指向结构体数组
return 0;
}
共用体简介:
- 共用体(Union) 是 C 语言中的一种特殊数据类型,它允许在相同的内存位置存储不同的数据类型。共用体的所有成员
共享同一块内存空间,因此同一时间只能存储其中一个成员的值。 - 共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。
 
共用体格式:
1  | union 共用体类型名称{  | 
举例:
1  | union Data {  | 
上例中, union 命令定义了一个包含三个属性的数据类型 Data。虽然包含三个属性,但是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性。
声明共用体变量:
和结构体非常相似
1
2
3
4
5
6
7
8union Data {
short m;
float x;
char c;
};
//声明共用体变量
union Data a, b;1
2
3
4
5union Data {
short m;
float x;
char c;
} a, b;以共用体变量a为例,它由3个成员组成,分别是m、x和c,编译时,系统会按照最长的成员为它分配内存,由于成员x的长度最长,它占4个字节,所以共用体变量a的内存空间也为4个字节。

调用共用体成员:
和结构体相似
方式1:
1
2union Data a;
a.c = 4;方式2:声明共用体变量的同时,给任一成员赋值
1
union Data a = {.c = 4};
方式3:声明共用体变量的同时,给首成员赋值
1
union Data a = {8};
注意,方式3不指定成员名,所以只能为第一个成员进行赋值。
错误的方式:
1
2
3union Data a = {1,1.5,'a'}; //错误的
因为共同体是共用一块内存空间,同一刻只能存储一个成员的值,
所以上述代码,只有最后一个有效,也就是'a'
-> 操作符:
Union 结构也支持指针运算符 -> 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18union evaluation { //评价
int score;
float grade;
char level;
};
int main() {
union evaluation e;
e.score = 85;
union evaluation *p;
p = &e;
printf("%d\n", p->score); // 85 int类型
e.grade=3.0f;
printf("%d\n", p->grade);//3.0 float类型
return 0;
}上例中, p 是 e 的指针,那么 p->score等同于 e.score。
**了解:**Union指针与它的属性有关,当前哪个属性能够取到值,它的指针就是对应的数据类型。
共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。