一、结构体的基础知识
结构体的定义:结构体是一些值的集合,这些值被称为成员变量,它们可以是一些不同类型的变量。
例如下面是一个结构体声明,Student这个结构体有name[20], age, score三种成员变量。
struct Student
{
//成员变量
char name[20];//名字
int age; //年龄
double score; //分数
};
在以上结构体的声明中,结构体的名字即Student可以省略,但是省略后必须在声明结构体类型的同时定义结构体变量,否则这个匿名声明的结构体就没用了,如下:
//结构体的匿名声明
struct
{
char name[20];
int age;
double score;
}s1,s2;
结构体变量的定义和初始化都可以在结构体类型声明时同时进行,也可以分开进行。
struct Student
{
char name[20];
int age;
double score;
}s1 = { "张三",20,60.5 },s2;
s2 = { "王五",19,45.7 };
struct Student s3 = { "李四",18,75.0 };
结构体的成员变量也可以是结构体变量本身,这叫做结构体的自引用,它是通过结构体指针实现的。
结构体的成员变量还可以是其他结构体变量,这是结构体的嵌套。
struct S
{
int a;
char c;
struct S* ps1;//结构体的自引用
};
struct A
{
float f;
struct S s2;//结构体的嵌套调用
};
二、结构体大小的计算
用操作符sizeof可以计算出int, float, char等类型所占字节的个数,我们来看看sizeof是否能算出结构体所占空间的大小。
在运行下面的代码前,我们先来猜测一下运行结果是多少,vs平台int 占4个字节,char占1个字节,double占8个字节,那么结构体SS的大小应该是13个字节,可是在运行以下代码后,我们和会发现SS竟然占16个字节,这是为什么呢?
#include<stdio.h>
struct SS
{
int a;
char b;
double c;
};
int main()
{
printf("%d\n", sizeof(struct SS));//结果是16
return 0;
}
我们知道,不同数据类型在内存中有不同的存储方式,比如数组在内存中就是连续存储的,而结构体存储的不同在于它在内存中严格遵守内存对齐。
内存对齐的规则:
1.结构体第一个成员存在与结构体变量偏移量为0的地址处;
2.结构体的其他成员要对齐到与结构体变量的偏移量是对齐数的整数倍的地址处;
(vs平台默认对齐数是8,某成员变量的对齐数=平台默认对齐数和该成员本身大小,两者中取较小值)
3.结构体的大小是最大对齐数(所有成员变量的对齐数中取最大值)的整数倍;
4.如果结构体中有嵌套,那么嵌套结构体对齐到自己最大对齐数的整数倍处,而结构体的整体大小就是最大对齐数(包含嵌套结构体的对齐数)的整数倍。
根据内存对齐的规则,我们可以计算结构体的大小。
关于平台默认对齐数的修改:
如果认为平台默认的对齐数不妥,可以利用#pragma这个预处理指令修改对齐数
#pragma pack(4);//修改默认对齐数为4
……
#pragma pack( );//取消修改的默认对齐数,还原为默认
三、位段
位段类似于结构体,却又和结构体不同,主要有以下两个不同:
1.位段成员只有int, unsigned int, char类型;
2.位段成员名后有一个冒号和数字,数字表示位段成员所占比特位。
如:
//位段
struct B
{
int a : 4; //成员变量a占4个比特位
char c : 2;
int i : 1;
};
相对于结构体,位段可以更好的节省空间,比如以上位段B先开辟4个字节的空间,成员变量a占4个bit,c占2个bit,i占1个bit,一共占8个bit,所以位段B占据4个字节。
(注:位段是按照第一个成员的类型开辟空间的,第一个成员是int (char)就开辟4(1)个字节。)
但是位段也有明显的缺点,因为C标准并未规定位段中有2个成员时,当第二个成员较大无法被容纳在第一个成员剩余的空间时,这个剩余的空间是利用还是抛弃,不同平台有不同的处理方式。再说,16位机器下位段成员的最大位数是16,32位机器是32位,将32位机器下的含有位段的程序移植到16位机器,就有可能造成错误。
因此,位段是不跨平台的,在设计跨平台程序时要尽量避免使用位段。
位段的应用:
在传递数据时,我们不仅要传递数据本身的信息,还要传递数据的发送位置、目的位置等,就想写一封信,不仅要注明发信人姓名、地址,还要写清楚收信人姓名、地址,此时采用位段封装该数据就十分方便。