1.结构体
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
struct tag{
member-list
}variable-list;
例如描述学生
struct Stu{
char name[20];
int age;
char sex[20];
char id[20];
}s4,s5,s6;
struct Stu{
char name[20];
int id;
char sex[20];
char id[20];
};//注意要加分号
struct Stu s1;
struct Stu s2;
s4,s5,s6与s1,s2的区别在于s4,s5,s6为全局变量
特殊声明:
struct{
int a;
char b;
float c;
}x;
struct{
int a;
char b;
float c;
}*p;
//匿名结构体类型,匿名结构体一定为全局变量
p=&x;//类型不兼容,编译器会将上面两个声明当作完全不同的类型
结构体的自引用
struct Node{
int data;
struct Node*next;
};
typedef struct Node{
int data;
Node*next;
}Node;
//如果将struct后面的Node给省略掉,就会导致还未重命名Node就使用Node
这样用typedef重命名后,之后使用结构体变量 struct Node就可以直接用Node
结构体变量的定义和初始化
struct stu{
char name[15];
int age;
};//定义
struct stu s1={“abcdef”,3};//初始化
struct stud{
int x;
char name[20];
struct stu;
};//结构体的嵌套定义
struct stud s b1={3,“abcdef”,{“cdeff”,20}};//结构体的嵌套初始化
2. 结构体内存对齐
规则:1.结构体的第一个成员永远放在0偏移处
2.从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍数(这个对齐数是:成员自身大小和默认对齐数的较小值)
在VS环境下,默认对齐数是8,gcc环境下,没有默认对齐数,对齐数就是成员大小
3.当成员全部存放进去后,结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍,如果不够,则浪费空间对齐
4.如果嵌套了结构体,那嵌套的结构体要对齐到自己结构体成员的最大对齐数的整数倍处,整个结构体的大小必须是最大对齐数的整数倍,最大对齐数包括嵌套结构体内部的成员变量
struct s1{
char c1;
int i;
char c2;
};
该结构体大小为8
struct s1{
char c1;
char c2;
int i;
};
该结构体大小为8
struct s3{
double d;
char c;
int i;
};
该结构体大小为16
struct s4{
char c1;
struct s3 b2;
double b;
}
该结构体大小为32
为什么存在内存对齐
1.平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因
数据结构(尤其是栈)应尽可能在自然边界上对齐
为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问
struct s{
char c;
int a;
}s1;
如果是32位机器,一次读取4个字节,如果存在对齐则一次就可以访问,不存在就需要两次访问
总的来说,结构体的内存对齐是以空间换时间的做法
既要满足对齐,又要节省空间,让占用空间小的成员尽量聚在一起
修改默认对齐数
#pragma pack(8) 修改默认对齐数为8
#pragma pack() 恢复默认对齐数
例:#pragma pack(1)
struct s2{
char c1;
int i;
char c2;
};
s2结构体的大小为6
结构体传参
函数传参时,结构体是需要压栈的,会有时间与空间上的开销
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销太大,会导致性能下降
所以结构体传参时,要传结构体地址,传结构体指针
2.位段
1.位段的成员可以是 int,unsigned int,signed int或者是char(属于整型类型家族) 类型
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)来开辟的
3.位段涉及很多不确定因素,位段是不跨平台的,应避免使用位段
不确定因素在于,位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义,位段中最大位的数目不能确定,16位机器最大16,32位机器最大32,写27在最大位为16的机器上会出问题)
4.位段的成员名后边有一个冒号和数字,冒号后面的数字代表分配给这个成员几个二进制位(vs环境,分配到的内存位从右向左使用,分配的内存剩余的比特位不够实用时,浪费掉)
注意10的二进制为1010,但分配给a的二进制位只有3位,故只会保留010
12只会保留1100
例题
#define MAX_SIZE A+B
struct _Record_Struct
{
unsigned char Env_Alarm_ID :4; //占第一个字节的前4位
unsigned char Para1 :2; //占第一个字节的前第5、6位
unsigned char state; //占第二个字节的全部
unsigned char avail:1; //占第三个字节的前1位
}*Env_Alarm_Record;
struct _Record_Struct *pointer = (struct _Record_Struct*)malloc
(sizeof(struct _Record_Struct) * MAX_SIZE);
当A=2, B=3时,pointer分配(9 )个字节的空间
3*2+3=9
int main()
{
unsigned char puc[4];
struct tagPIM
{
unsigned char ucPim1;
unsigned char ucData0 : 1;
unsigned char ucData1 : 2;
unsigned char ucData2 : 3;
}*pstPimData;
pstPimData = (struct tagPIM*)puc;
memset(puc,0,4);
pstPimData->ucPim1 = 2;
pstPimData->ucData0 = 3;
pstPimData->ucData1 = 4;
pstPimData->ucData2 = 5;
printf(" %02x %02x %02x\n",puc[0], puc[1], puc[2], puc[3]);//%x以十六进制位打印
return 0;
}
memset初始化后puc的内容为 0000 0000 0000 0000 0000 0000 0000 0000,每四个二进制位是一个十六进制位
ucPim1占用一个字节 0000 0010
ucData0占用1个字节,但存放3的二进制位为11所以只存入1
ucData1占用2个字节,但存放4的二进制位为100只存放00
ucData3占用3个字节,存放5的二进制位为101可以
所以第二个字节二进制位为
0010 1001
打印结果为 02 29 00 00
3.枚举
enum Day{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
枚举的可能取值,默认是从0开始递增的
enum Day 是枚举类型,{}中的内容是枚举类型的可能取值,也叫枚举常量
这些可能取值都是有初值的,默认从0开始,一次递增1,当然在定义时也可以赋初值
enum Day S=Mon;
enum Day S=2;//在c中允许,在c++中不允许
4.联合体(共用体)
联合是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
union un{
char c;
int i;
}//联合体的声明
union un U;//联合体的定义
sizeof(“%d”,sizeof(un));//4个字节
更改联合体其中一个内容时,别的内容也会更改,所以在同一时间只能使用一个内容
联合体的大小
1.联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小
2.当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
union Un1{
char c[5];
int i;
} //大小为8
union Un2{
short c[7];
int i:
} //大小为16
用联合体判断机器大小端
union un{
char c;
int i;
}
un.i=1;
if(un.c==1){
printf("大端");
}
else printf("小端");