(1)结构体类型的声明
(2)结构的自引用
(3)结构体变量的定义和初始化
(4)结构体内存对齐
(5)结构体传参
(6)结构体实现位段(位段的填充&可移植性)
(1)枚举类型的定义
(2)枚举的优点
(3)枚举的使用
(1)联合类型的定义
(2)联合的特点
(3)联合大小的计算。
一、结构体
1. 结构体的声明
(1)结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
(2)结构的声明
struct tag { member-list; }variable-list;
例如描述一个学生:
struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; //分号不能丢
(3)特殊的声明( 匿名结构体 )
在声明结构体的时候,可以不完全的声明。
比如:
//匿名结构体类型 struct { int a; char b; float c; }x; //必须在此紧挨着定义结构体变量,以后在后面不能定义了 //因为其特殊性,所以只能使用一次 struct { int a; char b; float c; }a[20], *p;
上面的两个结构在声明的时候省略掉了结构体标签(tag)。
//在上面代码的基础上,下面的代码不合法 //编译器认为等号两边时不同的结构体类型,所以这种写法时错误的 p = &x;
因为是匿名结构体类型,所以上面的两个结构体是两种类型,并不存在结构体指针
警告:
编译器会把上面的两个声明当成完全不同的两个类型。
所以是非法的。
(4) 结构的自引用
在结构中包含一个类型为该结构本身的成员
以链表为例:
1,2,3,4,5 是五块内存,但是并没有按顺序存储,而且要求他们互相之间能够找到,比如说,要1能够找到2,2能够找到3,3能够找到4,4能够找到5,5不再往下找。因此通过地址查找,1存2的地址,2存3的地址,3存4的地址,5就不存有效的地址,存一个NULL。当第一个是struct Node的类型的时候,因为是自己找自己的类型,所以其余四个也是这种类型的,因此指针也是 struct Node* 类型的。
错误的自引用方式:
//代码1 struct Node { int data; struct Node next; }; //不行! //sizeof(struct Node)是无限大了
正确的自引用方式:
//代码2 struct Node { int data; struct Node* next; }; //通过指针来完成
注意:
//代码3 typedef struct { int data; Node* next; }Node; //不行! //Node* next的Node没人认识,Node* next是结构体里的成员变量 //声明结构体时并没有Node,而Node是重定义以后才有的 //解决方案: typedef struct Node { int data; struct Node* next; }Node; //将该结构体重命名为Node正确做法 int main() { struct Node n2 = { 0 }; Node n = { 0 }; //两种初始化都可以 return 0; }
当想定义把一个结构体重命名,那当前的这个结构体必须是清晰可见的,而结构体中的成员变量又用到了本来的这种结构体类型的指针,那当前的这个要重命名的结构体就不能是匿名的结构体,如上代码
(5)结构体变量的定义和初始化
有了结构体类型,那如何定义变量,其实很简单。
struct Point { int x; int y; }p1; //声明类型的同时定义全局变量p1 struct Point p2; //定义结构体全局变量p2 //初始化:定义变量的同时赋初值。 struct Point p3 = {x, y}; struct Stu //类型声明 { char name[15];//名字 int age; //年龄 }; struct Stu s = {"zhangsan", 20};//初始化 struct Node { int data; struct Point p; struct Node* next; }n1 = {10, {4,5}, NULL}; //结构体嵌套初始化 struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
(6)结构体内存对齐
我们已经掌握了结构体的基本使用了。
现在我们深入讨论一个问题:计算结构体的大小。