文章目录
一、结构体
1.1结构体变量的定义和初始化
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
struct Point
{
int x;
int y;
};
struct Node
{
int data;
struct Point P;
struct Node* next;//结构体正确的自引用方式
}n1 = {
10, {
4, 5}, NULL };//结构体嵌套初始化
struct Node n2 = {
20, {
5, 6}, NULL };
1.2结构体内存对齐(计算结构体的大小)
1.2.1结构体内存对齐规则
1、结构体的第一个成员放在0偏移处。
2、从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍处。这个对齐数是:成员自身的大小和默认对齐数的较小值。
备注:vs环境下,默认对齐数是8。
gcc环境下,没有默认对齐数,没有默认对齐数时,对齐数就是成员自身大小。
3、当成员全部存放进去后,结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍。如果不够,则浪费空间对齐。
4、如果嵌套了结构体,嵌套的结构体成员要对齐到自己的成员的最大对齐数的整数倍处。最后总大小必须是最大对齐数的整数倍,最大对齐数包含嵌套的结构体成员自己的成员中的对齐数。
struct s3
{
double d;
char c;
int i;
};
struct s4
{
char c1;
struct s3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct s3));
printf("%d\n", sizeof(struct s4));
return 0;
}
1.2.2存在结构体内存对齐的原因
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而访问对齐的内存时仅需要作一次访问。
总体来说,结构体的内存对齐是用空间来换取时间的做法。为了既满足对齐,又节省空间,我们在设计结构体的时候,应让占用空间小的成员尽量集中在一起。例如:
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
char c2;
int i;
};
s1和s2类型的成员一模一样,但s2比s1所占的空间更小。
1.2.3修改默认对齐数
结构体内存在对齐方式不合适的时候,我们可以自己修改默认对齐数。
#pragma pack(8)//设置默认对齐数为8
struct s1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认对齐数
1.3结构体传参
结构体传参的时候,应传结构体的地址。
struct s1
{
char c1;
int i;
char c2;
};
struct s1 s = {
'A', 100, 'a' };
void print1(struct s1 s)//传结构体传参
{
printf("%d\n", s.c1);
}
void print2(struct s1* ps)//传结构体地址传参
{
printf("%d\n", ps->c1);
}
int main()
{
print1(s);
print2(&s);
return 0;
}
应选用print2函数,因为函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销过大,会导致性能下降。
二、结构体实现位段
2.1位段的内存分配
1、位段的成员是int,unsigned int,signed int或者char(属于整型家族)类型。
2、位段在空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的(使用的)。
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = {
0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d\n", sizeof(struct S));
return 0;
}
2.2位段不跨平台的原因
1、位段的int成员被当成有符号数还是无符号数是不确定的。
2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器上会出现问题。)
3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4、当一个结构包含两个位段成员,第二个位段成员比较大,无法容纳于第一个位段成员剩余的位时,是舍弃剩余的位还是利用剩余的位,这是不确定的。
总结:位段可以很好地节省空间,但有跨平台的问题存在。
三、枚举
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
enum Sex
{
MALE,//枚举类型的可能取值,也叫枚举常量
FEMALE = 5,//这些可能取值是有值的,默认从0开始,一次递增1
SECRET//在定义时也可以赋初值
};
int main()
{
enum Sex s = FEMALE;//只能拿枚举常量给枚举变量赋值
printf("%d \n", MALE);
printf("%d \n", FEMALE);
printf("%d \n", SECRET);
return 0;
}
枚举的优点:
1、增加代码的可读性和可维护性。如写游戏时枚举常量EXIT本质是0,看到的是退出;枚举常量PLAY本质是1,看到的是玩游戏。
2、和#define定义的常量相比枚举有类型检查,更加严谨。
3、和#define定义的常量相比便于调试。
4、和#define定义的常量相比使用方便,一次可以定义多个常量。