结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
例如描述一个学生:
struct Stu { char name[20]; int age; char sex[5]; char id[20]; }S;//S为变量列表 typedef struct Stu { int a; char b; float c; }s,*p;//s,*p为类型在声明结构的时候,可以不完全的声明。
//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20],*p;这种声明是错误的,因为这种声明实际上是一个无限循环,成员next是一个结构体,next的内部还会有成员是结构体,依次下去,无限循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。 正确的自引用方式: (使用指针):
struct Node { int data; struct Node* next;//指针 };由于指针的长度是确定的(在32位机器上指针长度为4),所以编译器能够确定该结构体的长度。
使用typedef 时 错误的方式: typedef struct { int data; Node* next;//虽然也使用指针,但这里的问题是:Node尚未被定义 } Node;这里的目的是使用typedef为结构体创建一个别名Node。但是这里是错误的,因为类型名的作用域是从语句的结尾开始,而在结构体内部是不能使用的,因为还没定义。 正确的方式:有三种,差别不大,使用哪种都可以。
/* 方法一 */ typedef struct Node { int data; struct Node* next; } Node; /* 方法二 */ struct tag_2; typedef struct tag_2 Node; struct tag_2 { int data; Node* next; }; /* 方法三 */ struct tag_3 { int data; struct tag* next; }; typedef struct tag_3 Node;介绍了以上结构体的使用规则,我举两个例子:
strcpy(student.name,"ningbo"); student.age = 20; strcpy(student.sex,"female");这里我将 student 结构体变量的所有成员的值都改了一下。 然后再介绍结构体的一种访问成员的方法:
结构体指针可以直接通过 -> 操作符来访问结构体的成员首先得有一个结构体指针:
struct stu* pstudent; //定义一个struct stu型的指针 pstudent = &student; //将结构体student的地址放在结构体指针pstudent里面然后再进行指针直接访问:
strcpy(pstudent->name,"liubei"); pstudent->age = 18; strcpy(pstudent->sex,"male");这里通过结构体指针pstudent将结构体student的成员又进行了修改。
结构体的对齐规则:
第一个成员在与结构体变量偏移量为0的地址处。其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 (对齐数= 编译器默认的一个对齐数 与 该成员大小的较小值。) VS中默认的值为8Linux中的默认值为4 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所 有最大对齐数(含嵌套结构体的对齐数)的整数倍。 //求结构体的大小 struct s1 { char c1; int i; char c2; }; struct s2 { char c1; char c2; int i; }; struct s3 { double d; char c; int i; }; struct s4 { char c1; struct s3 s3; double d; }; int main() { printf("%d\n", sizeof(struct s1));//12 printf("%d\n", sizeof(struct s2));//8 printf("%d\n", sizeof(struct s3));//16 printf("%d\n", sizeof(struct s4));//32 }利用 #pragma 这个预处理命令,可以改变我们的默认对齐数。
#pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认总体来说:
结构体的内存对齐是拿空间来换取时间的做法。设计结构体时,让占用空间小的成员尽量集中在一起。结构在对齐方式不合适时,可以自己更改默认对齐数。总结:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。结构体传参的时候,要传结构体的地址。位段的声明和结构体是很类似的,只是成员的类型有区别:
位段的成员必须是 int, unsigned int 或 signed int。位段的成员名后边有一个冒号和一个数字,数字代表该成员所占的比特位(bit)。 struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; }; struct S { char a : 3; char b : 4; char c : 5; char d : 4; }; int main() { printf("%d\n", sizeof(struct A));//8 printf("%d\n", sizeof(struct S));//3 return 0; }枚举顾名思义就是——列举,相当于创建一个自定义类型,用这个类型创建的变量只能取你自己设定的范围内的值,但这只是一个符号的定义,实际上都是 int 型的。
enum color { red, green, blue, }; enum color clr=green;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。 clr=5; int main() { printf("%d\n", green);// 5 return 0; }{}中的内容是枚举类型的可能取值,也叫 枚举常量。 枚举常量都是有值的,默认从0开始依次递增1,当然在定义的时候也可以赋初值。 枚举的优点
增加代码的可读性和可维护性和#define定义的标识符比较枚举有类型检查,更加严谨。防止了命名污染(封装)便于调试使用方便,一次可以定义多个常联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以 联合也叫共用体)
联合类型的声明
union Un { char c; int i; };联合变量的定义
union Un un;计算联合体变量的大小
int main() { printf("%d\n", sizeof(un));//4 return 0; }联合大小的计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。 union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; int main() { printf("%d\n", sizeof(union Un1));//8 printf("%d\n", sizeof(union Un2));//16 return 0; }