结构体
结构:一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量;
结构体声明:struct是结构体关键字,结构体声明不能省略struct;
匿名结构体:只能在声明结构体的时候声明结构体成员,之后再想用这个结构体声明结构体成员时就不行了;因为匿名结构体只能用一次,没有结构体名字没有办法声明结构体成员;(只打算用一次的结构体)
结构体变量的声明与初始化
结构体变量的初始化需要使用大括号进行初始化;
// 结构体成员的声明与初始化
struct student
{
// 声明的是一个描写学生的结构体
char name[20];
int age;
char sex[5];
} s1 = { "张三", 18, "男" }; // 声明的是结构体成员并初始化;
int main()
{
struct student s2 = { "李四", 19, "女" }; // 声明的是结构体成员并初始化;
struct student s3 = { .age = 20, .name = "王五", .sex = "男" }; // 指定初始化结构体成员变量,通过 . 指定;
// 如果不指定初始化结构体成员变量,就顺序初始化;
printf("%s %d %s\n", s1.name, s1.age, s1.sex);
printf("%s %d %s\n", s2.name, s2.age, s2.sex);
printf("%s %d %s\n", s3.name, s3.age, s3.sex);
return 0;
}
结构的自引用
可以包含同类型的结构体指针,但是不能包含同类型结构体;
#include <stdio.h>
struct Node
{
int data; // 存储数据 -> 数据域
struct Node* n; // 存放下一个节点的地址 -> 指针域
// 可以包含同类型结构体指针,但是不能包含同类型结构体
};
结构体内存对齐
怎么对齐:
1、第一个成员在结构体偏移量为0的地址处存放;
2、其他成员变量要对齐到对齐数的整数倍的地址处;
3、对齐数:VS默认对齐数、结构体成员自身大小就是对齐数,取较小的对齐数;
4、结构体总大小为最大对齐数的整数倍(每个成员都有一个对齐数),所有成员中的最大对齐数
5、嵌套结构体的对齐,嵌套的结构体要对齐到自身最大对齐数的整数位;结构体的大小就是所有成员中的最大对齐数的整数倍
为什么对齐:
1、不是所有硬件平台都能访问任意地址上的数据的,有些只能在特定位置处取某些特定数据;
2、结构体(尤其是栈)应该尽可能在自然边界上对齐;未对齐的内存,处理器需要访问两次;而对齐的只需访问一次;
3、结构体的内存对齐就是拿空间换取时间的;
如何节省空间:让占用空间小的成员集中在一起;
代码验证:
#include <stdio.h>
int main()
{
struct S
{
char c1;
int a;
double b;
char c2;
}s;
struct S1
{
int a;
char c1;
char c2;
double b;
}s1;
printf("%d\n", sizeof(s));
printf("%d\n", sizeof(s1));
return 0;
}
百度笔试题:
写一个宏,计算结构体中某变量相对于首地址的偏移(offsetof宏);
offsetof宏的使用:
#include <stdio.h>
#include <stddef.h>
int main()
{
struct S
{
char c1;
int a;
double b;
char c2;
}s;
struct S1
{
int a;
char c1;
char c2;
double b;
}s1;
// offsetof宏:结构体成员变量相对于首地址的偏移量
printf("%d %d %d %d\n", offsetof(struct S, c1), offsetof(struct S, a), offsetof(struct S, b), offsetof(struct S, c2));
printf("%d %d %d %d\n", offsetof(struct S1, a), offsetof(struct S1, c1), offsetof(struct S1, c2), offsetof(struct S1, b));
printf("%d\n", sizeof(s));
printf("%d\n", sizeof(s1));
return 0;
}
结构体传参:
1、传值调用,形参相当于一份临时拷贝,修改形参不会改变实参;
2、传址调用,如果不加上const,修改新参就会改变实参;
3、传址调用比传值调用更节省空间,结构体传参尽量传址;
位段
1、设计位段的目的就是为了节省空间;
2、位段的成员必须是int、unsigned int、signed int(c99之后,其他类型也可以,但是基本都是int、char类型);
3、位段的成员名后边有一个冒号和一个数字(数字表示几个比特位);
4、位段:为表示二进制位;
#include <stdio.h>
int main()
{
struct S1 // 结构体
{
int a;
int b;
char c;
};
struct S2 // 位段
{
int a : 2;
int b : 4;
char c : 1;
};
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
位段的内存分配
1、不知道是从低位开始分配还是从高位才是分配,这是不确定的;
位段的跨平台问题:
1、int位段被当成有符号数还是无符号数是不确定的;
2、位段中最大位的值是不确定的;
3、位段中的成员在内存中,从右到左还是从左到右分配标准未定义;
4、一个结构包含两个位段,第二个位段无法容纳于第一个位段剩余的位时,是舍弃位还是利用,是不确定的;
枚举
枚举类型的定义:
1、把可能的取值一一列举(月份、星期);
2、必须包含关键字enum;
3、枚举的值是默认从0一次递增的;
4、枚举常量的默认值是可以修改的;
#include <stdio.h>
enum Color
{
RED, // 注意,这是逗号,不是分号
GREEN,
BLUE // 最后这里什么符号都没有
};
enum Sex
{
MALE = 2, // 枚举常量的默认值是可以修改的
FEMALE = 4,
CECRECY = 1
};
int main()
{
printf("%d\n", RED);
printf("%d\n", GREEN);
printf("%d\n", BLUE); // 打印枚举常量
printf("%d\n", FEMALE);
return 0;
}
枚举的优点:
1、枚举可以增加代码的可读性和可维护性;
2、#define是用于替换的,没有类型的,但枚举有类型检查;
3、可以一次定义多个常量;
联合(共用体)
1、联合定义的变量包含一系列的成员;
2、这些成员共用同一块空间,所以也叫做共用体;
3、在同一时间段内,联合体内的成员不能同时使用,只能使用其中一个;
4、如果修改联合体内其中一个成员的值,则另一个的值也会改变;
5、联合体的关键字:union;
#include <stdio.h>
union Un
{
int a;
char b;
};
int main()
{
union Un un;
un.a = 10;
un.b = 'a';
printf("%d\n", sizeof(un)); // 大小是成员中较大的那个大小
printf("%p\n", &un);
printf("%p\n", &(un.a));
printf("%p\n", &(un.b)); // 说明联合体共用一块空间
return 0;
}
联合体大小的计算
1、联合体的大小是最大成员的大小,这句话是错的;
2、联合体的大小至少是最大成员的大小;
3、当最大成员大小没有对齐到最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍处;
#include <stdio.h>
union Un
{
short a[7]; // 对齐数 2
int b; // 对齐数 4
// a占14个字节
// 但是不是最大对齐数的整数倍
// 所以联合体的大小是16
}un;
int main()
{
printf("%d\n", sizeof(un));
return 0;
}