目录
1 . 结构体类型的声明
1.1 结构的声明
1.2 结构体变量的创建与初始化
1.3 结构体的特殊声明
1.4 结构体的自引用
2. 结构体内存对齐
2.1 对齐规则
2.2 为什么存在内存对齐
2.3 修改默认对齐数
3. 结构体传参
4.结构体实现位段
4.1 位段的内存分配
1 . 结构体类型的声明
1.1 结构的声明
struct tag
{member-list;
}variable-list;
假如描述一个学生
struct Stu
{int age;char name[20];char sex[5];char id[20];
}
1.2 结构体变量的创建与初始化
struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};
int main()
{//按照结构体成员的顺序初始化struct Stu s = { "张三", 20, "男", "20230818001" };printf("name: %s\n", s.name);printf("age : %d\n", s.age);printf("sex : %s\n", s.sex);printf("id : %s\n", s.id);//按照指定的顺序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥" };printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id : %s\n", s2.id);return 0;
}
1.3 结构体的特殊声明
在声明结构体的时候,可以进行不完全声明(又称匿名结构体)
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], * p;
上述两个结构体在声明的时候省略了结构体标签
在某些情况下,如果只是想使用一次结构体,就可以使用匿名结构体
在此基础上,下面代码合法吗
p = &x;
1.4 结构体的自引用
结构体中包含一个类型为该结构体本身成员是否可行?
看下列代码
struct Node
{int data;struct Node next;
}
分析一下不难发现,其实是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结
构体变量的大小就会无穷大。
struct S
{int n;struct S* next;
};
但是如果时包含和自己相同类型的指针,是可行的,在x86或x64的环境下,指针的大小无非就是
4/8字节。
在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看
typedef struct
{int data;S* next;
}S;
2. 结构体内存对齐
看一段代码
struct S1
{char c1;int i;char c2;
}s;int main()
{printf("%zd \n",sizeof(s));return 0;
}
如果只按成员的大小来看的话,该结构体应该只占用6个字节就够了
但程序运行起来后可以发现是12个字节。
这就涉及到结构体内存对齐。
2.1 对齐规则
struct S2
{char c1;char c2;int i;
};
一共8个字节,按照规则3来说,为最大对齐数的整数倍,那就是2* 4 = 8个字节
struct S3
{double d;char c;int i;
};
一共15个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 4 = 16个字节
struct S4
{char c1;struct S3 s3;double d;
};
其中嵌套了s3,已经知道s3的大小是16个字节,其中最大对齐数是8,那么就从偏移量8开始占16个字节。
一共32个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 8 = 32个字节
2.2 为什么存在内存对齐
struct S1
{char c1;int i;char c2;
}s;struct S2
{char c1;char c2;int i;
};
同样的成员类型,我们可以发现S1占12个字节,S2就只占8个字节了。
2.3 修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对齐数。
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{printf("%d\n", sizeof(struct S));return 0;
}
3. 结构体传参
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}
print2更好,因为print1在传参时是传值调用,这个值有多大就得开辟多大的空间,仅是一个data数
组就要了4000个字节的空间。
而print2在传参的时候是传址调用,传地址过去,大小无非就是4 / 8个字节,效率更高。
4.结构体实现位段
4.1 位段的定义
位(二进制位)
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};struct B
{int _a;int _b ;int _c ;int _d ;
};
int main()
{printf("%zd\n", sizeof(struct A));printf("%zd\n", sizeof(struct B));return 0;
}
4.1 位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
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("%zd\n", sizeof(s));return 0;
}
4.3 位段的应用
下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这
里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小⼀些,对
网络的畅通是有帮助的。