详解C语言结构体,枚举,联合体的使用
作者:蒋灵瑜的流水账 发布时间:2021-10-12 09:37:01
一、匿名结构体
struct
{
char name[20];
int age;
}s1;
匿名结构体对象s1过了这一行即销毁。
二、结构体的自引用
1、声明时不要自己引用自己
struct Node
{
int data;
struct Node next;//错误的,严禁自己引用自己
};
struct Node
{
int data;
struct Node* next;//正确的引用方式
};
2、结构体重命名时不能使用重命名
typedef struct
{
int data;
Node* next;//错误的,不要再重命名中使用重命名
}Node;
typedef struct Node
{
int data;
struct Node* next;//正确的
}Node;
博主在学数据结构的时候踩过这个坑,在结构体重命名的时候成员变量的类型就使用了重命名,导致整个程序不认识这个成员变量的类型(但是vs在typedef这里不报错,而是在每个使用这个类型的地方报错!!!)。后来把这个成员变量的类型修改为重命名之前的类型,整个程序就可以运行了。(如上图的正确写法)
三、结构体内存对齐规则
第一个成员在与结构体变量偏移量为0的地址处。
后续成员变量要放到各自的对齐数的倍数上。对齐数 = 编译器默认对齐数与该成员类型大小的较小值。(vs中默认对齐数是8,gcc没有默认对齐数)
结构体最终大小为最大对齐数的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
1、结构体内存计算
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
char c1在结构体变量的零偏移量处分配内存
int i的对齐数为4,所以跳过3个字节,在4的整数倍地址处分配内存
char c2的对齐数为1,使用下一个字节空间即可
目前已使用9字节
由于该结构体中所有成员变量中最大的成员类型大小为4字节,所以最大内存对齐数为4字节,结构体总大小为最大对齐数的整数倍。所以该结构体内存为12字节。
可以使用宏offsetof来观察结构体成员在内存中的偏移量:
2、结构体嵌套
struct S3//16
{
double d;
char c;
int i;
};
struct S4//32
{
char c1;
struct S3 s3;
double d;
};
char c1在结构体变量的零偏移量处分配内存
struct S3 s3按照其最大内存对齐数(此处为8)进行对齐
double d按照其最大内存对齐数(8)进行对齐
S4的最大内存对齐数为8,所以结构体的最终大小为32
3、通过调整结构体成员顺序,压缩内存
通过上述例子可以发现,结构体成员之间有很大的空间浪费,哪怕是拥有相同结构体成员的两个结构体类型,其在内存中所占据的空间也不相同,所以为了空间的节省,在不影响数据结构的情况下,有目的的把字节占用小的成员变量放在一起,达到节省空间的目的。
四、存在内存对齐的原因
1. 平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因(空间换时间)
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
五、修改默认对齐数
#pragma pack(2)//把默认对齐数改成2
struct S
{
char c1;
int i;
short c2;
};
#pragma pack()//恢复默认对齐数为8
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
#pragma pack(num)修改默认对齐数,该结构体的内存大小由12字节降低为8字节。
默认对齐数尽量为2的次方。
六、结构体传参
结构体传参要传地址。
传址调用优于传值调用的原因是地址占4/8个字节。
但是传值调用参数需要压栈,当结构体过大时,参数压栈的系统开销较大。
七、位段
位段是在结构体中实现的。
位段的成员可以是 int、unsigned int、signed int或者是char类型
位段的空间增长方式为每次增长4个字节(int)或1个字节(char)
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
1、位段在内存中的存储
1.1位段中char类型的存储方式(vs中舍弃剩余空间)
struct S//占用3个字节
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
int main()
{
printf("%d\n", sizeof(struct S));
struct S s= { 0 };
s._a = 10;//1010,截断为010
s._b = 12;//1100
s._c = 3;//0011
s._d = 4;//0100
return 0;
}
通过调用内存发现,&s中存储的16进制数字为62 03 04,那么可以发现s在内存中的存储方式如下图:
在vs环境中,char成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将剩余的比特位舍弃。
1.2位段中int类型的存储方式(vs中利用剩余空间)
struct A//占4个字节
{
int a : 2;
int b : 3;
int c : 4;
};
int main()
{
struct A s = { 0 };
s.a = 12;//1100,截断为00
s.b = 13;//1101,截断为101
s.c = 14;//1110
return 0;
}
通过调用内存发现,&s中存储的16进制数字为d4 01 00 00,那么可以发现s在内存中的存储方式如下图:
在vs环境中,int成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将继续存储,存不下的二进制位将存放至下一个字节的右侧。
注意:位段冒号后面的数字只能小于等于类型大小(例如char a:9是错误的)
2、位段的跨平台问题
1.int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
八、枚举
1、枚举的定义
enum color
{
RED,//枚举常量
BLUE,
YELLOW
};
不赋值那么默认从0开始,后续枚举成员的值递增1
enum color
{
RED=1,
BLUE,
YELLOW
};
只需要对第一个成员进行赋值,后续枚举成员的值递增1
在写枚举成员的时候建议全大写,博主在写通讯录枚举了exit,使用时vs提示该命名和exit函数冲突。
2、枚举的优点
增加代码的可读性和可维护性
枚举使用时有类型检查,#define定义的标识符没有
防止了命名污染(封装)
便于调试(#define定义宏在预处理时是直接替换)
使用方便,一次可以定义多个常量
九、联合体(共用体)
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。
1、联合体大小的计算
#include <stdio.h>
union un
{
char arr[5];
int a;
}u;
int main()
{
printf("%d", sizeof(u));//8
return 0;
}
联合体的大小至少是最大成员的大小。
最大内存对齐数的整数倍要大于等于最大成员的大小。
(这里最大成员是arr,占5个字节,最大内存对齐数是4,所以需要为祖国联合体开辟8个字节空间)
2、使用联合体判断计算机的大小端字节序
#include <stdio.h>
union un
{
int m;
char n;
}u;
int check_sys()
{
u.m = 1;
return u.n;
}
int main()
{
int a = check_sys();
if (a == 1)
printf("小端存储\n");
else
printf("大端存储\n");
return 0;
}
来源:https://blog.csdn.net/gfdxx/article/details/125797150


猜你喜欢
- springmvc下载中文文件名称为下划线springboot项目中,在下载文件的时候,通过封装ResponseEntity,将文件流写入b
- 通过代码操作防火墙的方式有两种:一是代码操作修改注册表启用或关闭防火墙;二是直接操作防火墙对象来启用或关闭防火墙。不论哪一种方式,都需要使用
- 前言我们在开发Web应用时,肯定要为用户提供上传的功能,比如用户上传一张图像作为头像等。为了能上传文件,我们必须将表单的method设置为P
- 线程中start方法与run方法的区别在线程中,如果start方法依次调用run方法,为什么我们会选择去调用start方法?或者在java线
- 您好,我是贾斯汀,欢迎又进来学习啦!【学习背景】学习Java的小伙伴,都知道想要提升个人技术水平,阅读JDK源码少不了,但是说实话还是有些难
- 本文实例讲述了android动态布局之动态加入TextView和ListView的方法。分享给大家供大家参考。具体实现方法如下:packag
- 本文实例讲述了Android编程重写ViewGroup实现卡片布局的方法。分享给大家供大家参考,具体如下:实现效果如图:实现思路1. 重写o
- 本文实例为大家分享了java启动线程的方法,供大家参考,具体内容如下1.继承Threadpublic class java_thread e
- Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。Field 成员变量的介
- idea无法切换分支报错idea拉取项目后,master分支配置完项目基础配置后,生成.iml等文件不受git管理后无法检出其他分支报错如下
- 一、线程优先级的介绍java 中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。java 中有
- 具体代码如下所示:import java.io.File;public class Scan { public static v
- 从Java 5开始,Java语言对方法参数支持一种新写法,叫 可变长度参数列表,其语法就是类型后跟...,表示此处接受的参数为0到多个Obj
- 一、ConcurrentLinkedQueue介绍并编程中,一般需要用到安全的队列,如果要自己实现安全队列,可以使用2种方式:方式1:加锁,
- 数据传输在Android开发过程中,我们常常通过Intent在各个组件之间传递数据。例如在使用startActivity(android.c
- AsyncTask,顾名思义,异步任务。说到异步,最简单的理解就是不同步。再复杂一点理解,就得举例子了。假设我要去火车站买票,刚到火车站我突
- 为了解决以下两个问题:1、单JAR包应用查看日志需要的时候如果需要远程访问服务器登录查看日志,那样相对比较麻烦2、生产环境为了解决BUG需要
- 本文实例讲述了Java Scanner类用法及nextLine()产生的换行符问题。分享给大家供大家参考,具体如下:分析理解:Scanner
- 写在最前端1.SpringAOP中共有六种通知类型,只要我们自定义一个类实现对应的接口,它们全都是org.springframework.a
- 0.介绍预览针对需要在IOS手机上接入原生微信支付场景,调用微信进行支付。如图:1.资料准备1.1 账号注册打开https://open.w