结构体
结构:一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量;
结构体声明: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]; // 对齐数 2int b; // 对齐数 4// a占14个字节// 但是不是最大对齐数的整数倍// 所以联合体的大小是16
}un;
int main()
{printf("%d\n", sizeof(un));return 0;
}