1.结构体:结构体和数组一样都是元素的集合,与数组有所区别的是结构的每个成员可以是不同类型的变量。
(1)结构体的声明如下:
struct tag
{
member - list;//结构体的成员列表
}variable-list;//结构体类型变量列表
//比如用结构体来描述一个学生
struct student
{
char name[20];
int age;
double stature;
double weight;
}
除上述常见声明外,结构体还有不完全声明
struct {
int a;
double b;
char c;
}x;//这种声明方式定义了类型为结构体的变量x
struct {
int a;
double b;
char c;
}*a;
a=&x//在编译器中是不合法的,编译器会把上面两个声明当成不同的类型
(2)结构体的自引用。
就是和单链表中的结点一样,结构体自引用时,需要用结构体类型的指针指向下一个结构体。代码如下:
struct LNode
{
int data;
struct LNode* next;
}x;
//如果使用typedef 自引用类型如下
typedef struct LNode{
int data;
struct LNode*next;
}LNode;
//注意如下方法时错误的
typedef struct LNode{
int data;
LNode*next;
}LNOde;
(3)结构体变量的定义和初始化
#include
int main(void)
{
struct student {
int age;
char name[20];
double weigth;
}a, b;//声明类型的同时定义变量a,b;
struct student c, d;//定义结构体变量c,d;
a = { 10,"zhangsan",50.4 };//对结构体变量的初始化
struct student e = { 12,"wangwu",45 };//定义一个结构体变量的同时进行初始化
struct LNode {
int data;
struct student o;
struct LNode* next;
}LNode;
LNode = { 3,{23,"jdfsjd",34},NULL };//结构体嵌套初始化
return 0;
}
(3)结构体内存对齐
#include
int main(void)
{
struct student1 {
char k;
char l;
int i;
}a;
printf("%d\n", sizeof(a));
struct student3 {
char k;
int i;
char l;
}c;
printf("%d\n", sizeof(c));
return 0;
}
上述程序的运行结果是8和12
从上述例子可以看出结构体的大小不是简单的相加,而是要遵守结构体的对齐规则。
结构体的对齐规则如下:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数=编译器默认的一个对齐数与该成员大小的较小值(VS中的默认值是8)
3.结构体总大小为最大对齐数(每个成员对齐数的最大值)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍数,结构体的整体大小就是所有最大对齐数(含结构体的对齐数)的整数倍。
结合上述规则来解释一下为什么编译器会打印出8和12:
对于结构体1:第一个元素的大小是1字节,与默认对齐数相比较小,存储在于结构体变量偏移量为0的地址处,第二个元素的大小是1字节,与默认对齐数相比较小,由于1是1的整数倍,第二个元素不浪费空间,直接存储在第一格元素之后,第三个元素的大小是4个字节,与默认对齐数相比较小,但由于2不是4的整数倍,所以第三个元素存储的时候需要浪费2个字节存储在4的位置处,这样存储第一个结构体此时占用8个字节,而8也是最大对齐数4的整数倍。所以该结构体的大小是8个字节。
对于结构体2:第一个元素的大小是1字节,与默认对齐数相比较小,存储在与结构体变量偏移量为0的地址处,第二个元素的大小是4个字节与默认对齐数相比较小,由于规则2的要求需要存储在4的位置处,第三个元素的大小是1字节与默认对齐数相比较小,需要存储在8的位置处。这样存储第二个结构体的大小是9个字节,由于9不是4的整数倍,需要浪费3个空间,占用12个字节。所以该结构体的大小是12个字节。
来练习一个嵌套的结构体,代码如下
#include
int main(void)
{
struct s3 {
double d;
char c;
int i;
};//该结构体的大小是16
struct s4 {
char t;
struct s3 s3;//s3中最大的对齐数是8
double p;
};
printf("%d", sizeof(s4));//32
return 0;
};
会打印32
为什么会存在内存对齐?
1.平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些·特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对其的内存访问仅需要一次访问。
这是用空间换取时间的做法。在设计结构体时,我们可以通过让占用空间小的成员尽量集中在一起的方法来减少可能的空间的消耗。
修改默认对齐数:
#pragma pack(4)//设置默认对齐数为4
#pragma pack()//取消设置的默认对齐数,还原为默认。
(4)结构体传参
#include
struct s {
int a;
float b;
};
void print1(struct s p)//结构体传参
{
printf("%d\n", p.a);
}
void print2(struct s* p) //结构体地址传参
{
printf("%d", p->a);
}
int main(void)
{
struct s a = { 5,3.2 };
print1(a);
print2(&a);
return 0;
}
//函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。
//如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
2.位段
位段的声明和结构体是类似的,有两个不同:
1.位段的成员必须是 int unsigned int 或signed int.
2.位段的成员名后边有一个冒号和一个数字。
比如
struct A {
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
位段的内存分配
1.位段的成员可以是int unsigned int signed int 或者是char(属于整形家族)类型。
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
3.枚举
枚举就是把可能的取值一一列举。
enum Day {
Mon,
Tues,
wed,
Thur,
Fri,
Sat,
Sun
};//注意枚举中要用逗号分隔开元素