C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(上)

简介: C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)

本篇将对C语言自定义类型进行讲解

1.结构体(struct)

前面简单讲过结构体,这里将会把前面结构体还没讲完的知识继续补充。复习链接:

1.1 结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.2 结构的声明

 
struct tag  //结构体关键字+标签  合起来是结构体类型
{
    member - list;   //成员变量列表
}variable - list;   //变量列表

例如描述一个学生:

 
struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}; //分号不能丢

1.3 匿名结构体

//匿名结构体类型(一次性使用)

 
struct
{
    int a;
    char b;
    float c;
    double d;
} s;
 
struct
{
    int a;
    char b;
    float c;
    double d;
} *ps;

对于上面的代码如果进行如下操作,是非法的

 
int main()
{
    ps = &s; // error
    return 0;
}

1.4 结构的自引用

介绍:结构体中包含一个类型为该结构体本身的成员,包含同类型的结构体指针(不是包含同类型的结构体变量)

 
struct A
{
    int i;
    char c;
};
 
struct B
{
    char c;
    struct A sa;
    double d;
};

注意事项1:结构体不能自己包含自己,不能包含同类型的结构体变量

 
struct N
{
    int d;
    struct N n; //结构体里不能存在结构体自己类型的成员
};

为了加深理解,先引入一下数据结构的一些知识:

注意事项2:结构体自引用时,不要用匿名结构体:

 
struct  // 如果省略结构体名字
{
    int data;
    struct Node* next; // 这里的 struct Node* 是哪里来的?
};

即使使用 typedef 重新取名为 Node,也是不行的。因为要产生 Node 必须先有结构体类型之后才能重命名 Node,即先 Node* next 定义完成员之后才 typedef 才能对这个类型重命名为 Node。

所以这种方式仍然是不行的:

 
typedef struct
{
    int data;
    Node* next; // 先有鸡还是先有蛋???
} Node;

正确方法:

 
typedef struct Node
{
    int data;
    struct Node* next;
} Node;

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

有了结构体类型,那如何定义变量,其实很简单。

 
struct S
{
    char c;
    int i;
} s1, s2; // 声明类型的同时创建变量
 
int main()
{
    struct S s3, s4;
 
    return 0;
}

创建变量的同时赋值(初始化)

 
struct S
{
    char c;
    int i;
} s1, s2;
 
int main()
{
    struct S s3 = {'x', 20};
//                  c    i
 
    return 0;
}

结构体包含结构体的初始化方法

 
struct S
{
    char c;
    int i;
} s1, s2;
 
struct B
{
    double d;
    struct S s;
    char c;
};
 
int main()
{
    struct B sb = {3.14, {'w', 100}, 'q'};
    printf("%lf %c %d %c\n", sb.d, sb.s.c, sb.s.i, sb.c);
    
    return 0;
}

1.6 结构体内存对齐

现在已经掌握了结构体的基本使用了。

深入讨论一个问题:计算结构体的大小。

这也是一个特别热门的考点: 结构体内存对齐

首先得掌握结构体的对齐规则:

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

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

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8)

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

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

 
//练习1
#include <stdio.h>
struct S
{
     char c1;
     int i;
     char c2;
};
int main()
{
    struct S s = {0};
    printf("%d\n", sizeof(s));//12
    return 0;
}

d71165441c954d48a56e90802fb82d14.png

 
//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));//8
 
//练习3
struct S3
{
    double d;//8
    char c;//1
    int i;//4
};
printf("%d\n", sizeof(struct S3));//16
 
//练习4-结构体嵌套问题
#include <stdio.h>
struct S4
{
    double d;
    char c;
    int i;
};
struct S5
{
    char c1;
    struct S4 s4;
    double d;
};
int main()
{
    struct S5 s5 = {0};
    printf("%d\n", sizeof(s5));
    return 0;
}
//上面第4点: 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
//结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//此题嵌套的结构体对齐到自己的最大对齐数就是8(所以浪费了下面的7个字节)且32是8的倍数

cb442a1d516c479ab65048040b9bddbe.png 为什么存在内存对齐? 大部分的书籍都是这样说的:

1. 平台原因(移植原因)

不是所有的硬件平台都能访问任意地址上的任意数据的;

某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;

而对齐的内存访问仅需要一次访问。

总体来说:

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

那在设计结构体的时候,既要满足对齐,又要节省空间,如何做到:

让占用空间小的成员尽量集中在一起。

 
//例如:
struct S1
{
    char c1;
    int i;
    char c2;
};
 
struct S2
{
    char c1;
    char c2;
    int i;
};
//S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。
//S1大小是12 而S2大小是8

1.7 修改默认对齐数

之前我们见过了 #pragma 这个预处理指令,这里再次使用,可以改变默认对齐数。

 
#include <stdio.h>
// 默认对齐数是8
#pragma pack(2) // 把默认对齐数改为2(一般改为2的几次方)
struct S
{
    char c1; //1
    int i; // 4
    char c2; // 1
};
#pragma pack() // 取消
int main()
{
    printf("%d\n", sizeof(struct S)); //修改默认对齐数后12变为8
    return 0;
}

c8a1f24c6c344813ad7aaf98ab972c78.png

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


百度笔试题:

写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明

注:这里还没学习宏,可以放在宏讲解完后再自己实现。

考察: offsetof 宏的实现

该宏用于求结构体中一个成员在该结构体中的偏移量。

头文件: stddef.h

使用方法演示:

 
#include <stdio.h>
#include <stddef.h>
struct S
{
    char c1; //1
    int i; // 4
    char c2; // 1
};
int main()
{
    printf("%d\n", offsetof(struct S, c1));//0
    printf("%d\n", offsetof(struct S, i));//4
    printf("%d\n", offsetof(struct S, c2));//8
    return 0;
}

C语言进阶⑮(自定义类型)(结构体+枚举+联合体)(结构体实现位段)(中):https://developer.aliyun.com/article/1513097

目录
相关文章
|
22天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
30 10
|
22天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
26天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
26天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
存储 C语言
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
【C语言】——define和指针与结构体初识
【C语言】——define和指针与结构体初识
|
存储 C语言
C语言初识-关键字-操作符-指针-结构体
C语言初识-关键字-操作符-指针-结构体
63 0
【C语言】指针,结构体,链表
【C语言】指针,结构体,链表
|
存储 算法 Linux
初识C语言【补】——指针、结构体
初识C语言【补】——指针、结构体
100 0
初识C语言【补】——指针、结构体
|
存储 编译器 C语言
初识C语言系列-5-完结篇-#define,指针,结构体(二)
初识C语言系列-5-完结篇-#define,指针,结构体
初识C语言系列-5-完结篇-#define,指针,结构体(二)