结构体是什么?
结构体和数组一样属于构造类型
数组是用于保存一组相同类型数据的,而结构体是用于保存一组不同类型的数组
定义结构体
在使用结构体之前必须先定义结构体类型,因为C语言不知道你的结构体中需要存储哪些类型的数据,我们必须通过定义结构体类型来告诉C语言,我们的结构体中需要存储哪些类型的数据
格式
struct 结构体名称{
数据类型 属性名称;
数据类型 属性名称;
... ...
};
struct Person{
char *name;
int age;
double height;
};
// 注意:结尾有分号
定义结构体变量
格式: struct 结构体名 结构体变量名;
struct Person p;
// Person 结构体名
// p 结构体变量名
使用结构体变量
结构体变量名称.结构体属性名称;
p.name = "cww";
p.age = 35;
p.height = 1.9;
printf("name = %s\n", p.name);
printf("age = %i\n", p.age);
printf("height = %lf\n", p.height);
结构体变量初始化的几种方式
定义的同时按顺序初始化
struct Person{
char *name;
int age;
double height;
};
struct Person p = {"cww", 18, 1.8};
定义的同时不按顺序初始化
struct Person{
char *name;
int age;
double height;
};
struct Person p = {.age = 35, .name = "cww", .height = 1.8};
先定义后逐个初始化
struct Person{
char *name;
int age;
double height;
} P;
p.name = "cww";
p.age = 18;
p.height = 1.8;
先定义后一次性初始化(不推荐使用)
struct Person {
char *name;
int age;
double height;
} P;
P = (struct Person){"cww", 18, 1.8}
定义结构体变量的几种方式
先定义结构体类型,再定义结构体变量
struct Person{
char *name;
int age;
double height;
};
struct Person p1;
定义结构体类型的同时定义结构体变量
struct Person{
char *name;
int age;
double height;
} p2;
定义结构体类型的同时省略结构体名称,同时定义结构变量(匿名结构体)
特点: 结构体类型之能使用一次;
struct {
char *name;
int age;
double height;
} p3;
结构体类型作用域
结构类型定义在函数内部的作用域与局部变量的作用域是相同的
从定义的哪一行开始,直到遇到return或者大括号结束
#include
void test();
int main()
{
struct Person{
char *name;
int age;
double height;
};
struct Person p;
p.name = "cww";
p.age = 18;
p.height = 1.8;
printf("name = %s\n", p.name);
test();
return 0;
}
void test(){
printf("name = %s\n", p.name); // 报错
}
结构类型定义在函数外部的作用域与全局变量的作用域是相同的
从定义的哪一行开始,知道文件结束为止
#include
struct Person{// 定义全局结构体
char *name;
int age;
double height;
} ;
void test();
int main()
{
struct Person p;
p.name = "cww";
p.age = 18;
p.height = 1.8;
printf("name = %s\n", p.name); // cww
test();
return 0;
}
void test(){
struct Person p2; //可以使用全局的结构体
p2.name = "ppp";
printf("name = %s\n", p2.name); // ppp
}
结构体数组
结构体数组和普通数组并无太大差异,只不过是数组中的元素都是结构体而已
格式: struct 结构体类型名称 数组名称[元素个数];
struct Person {
char *name;
int age;
};
struct Person p[2];
结构体数组初始化和普通数组也一样,分为先定义后初始化和定义同时初始化;
定义的同时初始化
struct Person {
char *name;
int age;
};
struct Person p[2] = {{"cww",18}, {"ppp", 20}};
- 先定义后初始化
```
struct Person {
char *name;
int age;
};
struct Person p[2];
p[0] = {"cww", 18};
p[1] = {"ppp", 20};
```
结构体内存分析
注意点:
给整个结构体变量分配存储空间和数组一样,从内存地址比较大的开始分配
给结构体变量中的属性分配存储空间也和数组一样,从所占用内存地址比较小的开始分配
注意点:
和数组不同的是,数组名保存的就是数组首元素的地址
而结构体变量名, 保存的不是结构体首属性的地址
#include
int main()
{
struct Person {
int age;
double height;
};
struct Person p;
// 结构体变量的名称并没有保存结构体首属性的地址;
printf("p = %p\n", p); // 00401770
printf("&p = %p\n", &p); // 0028FEB0
return 0;
}
结构体在分配内存的时候,会做一个内存对齐的操作(并不是都按最大属性字节分配)
会先获取所有属性中,占用内存最大的属性的字节
然后再开辟最大属性字节的内存给第一个属性
如果分配给一个属性之后还能继续分配给第二个属性,那么继续分配
如果分配给第一个属性之后,剩余的内存不够分配给第二个属性,那么会再次开辟最大属性字节的内存,再次分配
以此类推
```
struct Person{
int age; // 4
char ch; // 1
double score; // 8
};
struct Person p;
printf("sizeof = %i\n", sizeof(p)); // 16
```
- 调换顺序
```
struct Person{
int age; // 4
double score; // 8
char ch; // 1
};
struct Person p;
printf("sizeof = %i\n", sizeof(p)); // 24
```
结构体指针
因为结构体变量也会分配内存空间,所以结构体变量也有内存地址,所以也可以使用指针保存结构体变量的地址
规律:定义指向结构体变量的指针和过去定义指向普通变量的指针一样
struct Person {
int age;
double height;
};
struct Person per = {22, 1.8};
struct Person *p;
// p = per; // 错误写法, 写法和普通变量一样
p = &per; // 正确写法,要在变量前面添加取地址符号 &;
printf("per = %p\n", &per); // 0028FEA8;
printf("p = %p\n", p); // 0028FEA8;
如果指针指向了一个结构体变量,那么访问结构体变量的方式有3种
1.结构体变量名称.属性名称
2.(*结构体指针变量名称).属性名称;
3.结构体指针变量名称->属性名称;
1.结构体变量名称.属性名称
printf("per.age = %i\n", per.age); // 22
2.(*结构体指针变量名称).属性名称;
printf("per.age = %i\n", (*p).age); // 22
3.结构体指针变量名称->属性名称;
printf("per.name = %i\n", p->age); // 22
结构体嵌套定义
结构体的属性可以又是一个结构体
如果某个成员也是结构体变量,可以连续使用成员运算符 "." 访问低一级的成员
#include
int main()
{
// 定义一个日期的结构体
struct Date{
int year;
int month;
int day;
};
// 定义一个人的结构体
struct Person{
char *name;
int age;
// 嵌套定义
struct Date birthday;
};
// 完全初始化
struct Person p = {"cww", 18, {2020,02,20}};
printf("name = %s\n", p.name); // cww
printf("year = %i\n", p.birthday.year); // 2020
printf("day = %i\n", p.birthday.day); // 20
return 0;
}
结构体和函数
结构体变量之间的赋值和基本数据类型一样,是值拷贝
#include
int main()
{
struct Person{
char *name;
int age;
};
struct Person p1 = {"cww", 18};
struct Person p2;
p2 = p1;
p2.name = "ppp"; // 修改p2不会影响p1 是值拷贝
printf("p1.name = %s\n", p1.name); // cww
printf("p2.name = %s\n", p2.name); // ppp
return 0;
}
注意:
定义结构体类型不会分配存储空间
只有定义结构体变量才会分配存储空间;
结构体变量作为函数形参时也是值传递,在函数内修改形参,不会影响外界实参
#include
struct Person{
char *name;
int age;
};
void test(struct Person p);
int main()
{
struct Person p1 = {"cww", 18};
printf("p1.name = %s\n", p1.name); // cww
test(p1);
printf("p1.name = %s\n", p1.name); // cww
return 0;
}
void test(struct Person p){
p.name = "hhh";
printf("p1.name = %s\n", p.name); // hhh
}
共用体
共用体和结构体不同的是,结构体的每个成员都是占用一块独立的存储空间,而共用体所有成员占用同一块存储空间
定义共用体格式:
union 共用体名称{
数据类型 属性名称;
数据类型 属性名称;
... ...
};
union Test{
int age;
char ch;
};
定义共用体类型变量格式: union 共用体名 共用体变量名称
union Test t;
应用场景:
1.通信中的数据包会用到共用体
2.节约内存
3.需要大量的临时变量,这些变量类型不同
共用体定义的格式和结构体只有关键字不一样,结构体用struct,共用体用 union
共用体特点:
结构体的每个属性都会占用一块单独的存储空间,而共用体所有的属性都共用同一块存储空间
只要其中一个属性发生了改变, 其他的属性都会受到影响
#include
int main()
{
union Test {
int age;
char ch;
};
union Test t;
printf("sizeof(t) = %i\n", sizeof(t)); //4
t.age = 18;
printf("t.age = %i\n", t.age); // 18
t.ch = 'a';
printf("t.ch = %c\n", t.ch); // a
printf("t.age = %i\n", t.age); // 97
return 0;
}
枚举
枚举用于提升代码的阅读性, 一般用于表示几个固定的值
应用场景: 如果某些变量的取值是固定的, 那么就可以考虑使用枚举来实现;
格式:
enum 枚举类型名称{
取值1,
取值2,
};
注意点: 和结构体不同,枚举是用逗号隔开
enum Gender{
male,
female,
};
规范:
枚举的取值一般是以K开头,后面跟上枚举类型名称,后面再跟上表达的含义
K代表这是一个常量
枚举类型名称,主要是为了有多个枚举的时候,方便区分
含义,你想要定义名字
enum Gender{
KGenderMale, // 0
KGenderFemale, // 1
// 2 ... ...
};
枚举的取值:
默认的情况下, 是从0开始取值,依次递增
也可以手动指定从几开始,依次递增;
enum Gender{
KGenderMale = 6, // 6
KGenderFemale, // 7
// 8 ... ...
};
枚举类型的作用域
和结构体类型的作用域一样,和变量的作用域一样
#include
int main()
{
enum Gender {
KGenderMale,
KGenderFemale,
};
printf("KGenderFemale = %i\n", KGenderFemale); // 1
return 0;
}
#include
int main()
{
{
enum Gender {
KGenderMale,
KGenderFemale,
};
}
printf("KGenderFemale = %i\n", KGenderFemale); // 报错
return 0;
}
枚举类型变量的多种定义方式
和结构体类型的多种定义方式一样
// 先定义枚举类型,再定义枚举变量
enum Gender{
kGenderMale,
KGenderFemale,
}
enum Gender g1;
// 定义枚举类型的同时,定义枚举变量
enum Gender{
KGenderMale,
KGenderFemale,
} g2;
// 定义枚举类型的同时,定义枚举变量,并且省略枚举类型名称
enum {
KGenderMale,
KGenderFemale,
} g3;