• C语言:结构体


    目录

    结构体类型的声明

    匿名结构体

    全局结构体变量

    嵌套结构体

    访问结构体成员

    结构的自引用

    结构体变量的定义和初始化

    结构体内存对齐

    结构体内存对齐规则

    修改默认对齐数 #pragma pack(n) 

    offsetof 求结构体成员相对于结构体开头的偏移量的宏。 

    为什么存在内存对齐?

    结构体传参

    结构体实现位段(位段的填充&可移植性)

    什么是位段

    位段的内存分配规则

    位段的跨平台问题

    位段的应用


    结构体类型的声明

    匿名结构体

    1. /* 匿名结构体类型——只能用一次,即在声明的时候顺带创建一个变量 */
    2. struct
    3. {
    4. char name[20];
    5. int age;
    6. } s4;

    全局结构体变量

    1. struct Stu
    2. {
    3. char name[20];
    4. int age;
    5. } s1, s2; // s1 s2是全局变量
    6. struct Stu
    7. {
    8. char name[20];
    9. int age;
    10. } s1 = {"zhanghai", 22}; // 声明并初始化全局变量s1

    嵌套结构体

    1. struct Score
    2. {
    3. int score;
    4. char str[20];
    5. };
    6. struct Stu
    7. {
    8. char name[20];
    9. int age;
    10. struct Score s;
    11. };
    12. int main()
    13. {
    14. // 初始化局部变量s3
    15. struct Stu s3 = {"zhanghai", 22, {100, "q"}};
    16. printf("%s %d %d %s", s3.name, s3.age, s3.s.score, s3.s.str); // zhanghai 22 100 q
    17. return 0;
    18. }

    访问结构体成员

    1. #include
    2. /* 本例论如何声明结构体,并使用三种方式访问结构体各成员 */
    3. struct MyType
    4. {
    5. /* 成员 */
    6. char name[20];
    7. int age;
    8. char sex[10];
    9. char tele[12];
    10. };
    11. /* 接受结构体指针地址,struct MyType 类型int,表示一种类型; *p是因为要指针赋值给指针变量时需加* */
    12. void myPrintf(struct MyType *p)
    13. {
    14. /* *p 表示使用*将指针变量p解引用,得到 *p == obj(可参考09_pointer) */
    15. printf("%s %d %s %s\n", (*p).name, (*p).age, (*p).sex, (*p).tele); // "zhanghai", 20, "nan", "15556201597"
    16. /* p 表示指针变量,结构体指针变量 -> 成员名,也就是结构体指针变量若想访问成员,需要操作符 -> */
    17. printf("%s %d %s %s\n", p->name, p->age, p->sex, p->tele); // "zhanghai", 20, "nan", "15556201597"
    18. }
    19. int main()
    20. {
    21. struct MyType obj = {"zhanghai", 20, "nan", "15556201597"};
    22. // 结构体对象.成员名
    23. printf("%s %d %s %s\n", obj.name, obj.age, obj.sex, obj.tele); // "zhanghai", 20, "nan", "15556201597"
    24. // 将结构体指针传入
    25. myPrintf(&obj);
    26. return 0;
    27. }

    结构的自引用

    1. struct Node
    2. {
    3. int data;
    4. struct Node* next;
    5. };
    1. typedef struct Node
    2. {
    3. int data;
    4. struct Node* next;
    5. }Node;

    结构体变量的定义和初始化

    1. struct Point
    2. {
    3. int x;
    4. int y;
    5. } p1; //声明类型的同时定义变量p1
    6. struct Point p2; //定义结构体变量p2
    7. //初始化:定义变量的同时赋初值。
    8. struct Point p3 = {x, y};
    9. struct Stu        //类型声明
    10. {
    11. char name[15];//名字
    12. int age;      //年龄
    13. };
    14. struct Stu s = {"zhangsan", 20};//初始化
    15. struct Node
    16. {
    17. int data;
    18. struct Point p;
    19. struct Node* next;
    20. } n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
    21. struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

    结构体内存对齐

    结构体内存对齐规则

    1. 第一个成员在与结构体变量偏移量为0的地址处。

    2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。(vscode没有编译器对齐数,对齐数就是成员自身大小)

    3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

    4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

     

    1. struct S1
    2. {
    3. char c1;
    4. int i;
    5. char c2;
    6. };
    7. struct S2
    8. {
    9. char c1;
    10. char c2;
    11. int i;
    12. };
    13. struct S3
    14. {
    15. double d;
    16. char c;
    17. int i;
    18. };
    19. printf("%d\n", sizeof(struct S1)); // 12
    20. printf("%d\n", sizeof(struct S2)); // 8
    21. printf("%d\n", sizeof(struct S3)); // 16

    修改默认对齐数 #pragma pack(n) 

    1. /* 修改编译器默认对齐数为4,请看结构体规则第2点 */
    2. #pragma pack(4)
    3. struct S
    4. {
    5. int i; // 0
    6. double d; // 4 8 ——> 4
    7. };
    8. #pragma pack()
    9. #pragma pack(1)
    10. struct Sd
    11. {
    12. char c;
    13. int i;
    14. char c1;
    15. };
    16. #pragma pack()
    17. printf("%d\n", sizeof(struct S)); // 12 (修改对齐数之前是16)
    18. printf("%d\n", sizeof(struct Sd)); // 6

    offsetof 求结构体成员相对于结构体开头的偏移量的宏。 

    1. struct S1
    2. {
    3. char c1;
    4. int i;
    5. char c2;
    6. };
    7. struct S3
    8. {
    9. double d;
    10. char c;
    11. int i;
    12. };
    13. // S1
    14. printf("%d\n", offsetof(struct S1, c1)); // 0
    15. printf("%d\n", offsetof(struct S1, i)); // 4
    16. printf("%d\n", offsetof(struct S1, c2)); // 8
    17. // s3
    18. printf("%d\n", offsetof(struct S3, d)); // 0
    19. printf("%d\n", offsetof(struct S3, c)); // 8
    20. printf("%d\n", offsetof(struct S3, i)); // 12

    结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。 

    为什么存在内存对齐?

    1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些

    2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐

    的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

    总体来说: 结构体的内存对齐是拿空间来换取时间的做法

    结构体传参

    函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

    1. /* 结构体传参 */
    2. struct SC
    3. {
    4. int data[1000];
    5. int num;
    6. };
    7. // 结构体传参
    8. void print1(struct SC s)
    9. {
    10. printf("%d\n", s.num);
    11. }
    12. // 结构体地址传参
    13. void print2(struct SC *ps)
    14. {
    15. printf("%d\n", ps->num);
    16. }
    17. struct SC s = {{1, 2, 3, 4}, 1000};
    18. print1(s); // 传结构体
    19. print2(&s); // 传地址

    结构体实现位段(位段的填充&可移植性)

    什么是位段

    位段的声明和结构是类似的,有两个不同:
    1. 位段的成员必须是 int、unsigned int 或signed int 。
    1. 位段的成员名后边有一个冒号和一个数字。
    1. struct A
    2. {
    3. int _a : 2;
    4. int _b : 5;
    5. int _c : 10;
    6. int _d : 30;
    7. };

     A就是一个位段类型。

    位段的内存分配规则

    1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

    2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

    3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

    4. int 位段被当成有符号数还是无符号数是不确定的。

    5. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

    6. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

    7. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

    1. struct A
    2. {
    3. /* 上来二话不说先开辟4个字节空间,也就是 32个bit位 */
    4. int _a : 2; // _a占2个bit位
    5. int _b : 5; // _b占5个bit位
    6. int _c : 10; // _c占10个bit位
    7. /* 到这里发现 只剩 15 个bit位,不够存放_d的30个bit,那么会再次开辟4个字节用来存放 */
    8. int _d : 30; // _d占30个bit位
    9. };
    10. printf("%d\n", sizeof(struct A)); // 8 未使用位段就是16个字节

    位段的跨平台问题

    1. int 位段被当成有符号数还是无符号数是不确定的。
    2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    3. 器会出问题。
    4. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
    5. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

    总结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在

    位段的应用

  • 相关阅读:
    Debian 12.5 一键安装 Oracle 19C 单机
    SOC项目AHB_SD_HOST控制器设计
    排序——交换排序
    软件测试中常见的难题
    使用SDKMAN在Linux系统上安装JDK
    【数据结构•并查集】
    34_ue4进阶末日生存游戏开发[初步拾取功能]
    vscode 提升小程序开发效率的必备插件与工具
    【运维知识进阶篇】集群架构-Nginx高可用Keepalived
    java刷题笔记2
  • 原文地址:https://blog.csdn.net/dabaooooq/article/details/134497697