进阶C语言 第四章-------《自定义类型》 (结构体、枚举、联合)知识点+完整思维导图+深入细节+通俗易懂+基本练习题+建议收藏(二)

简介: 进阶C语言 第四章-------《自定义类型》 (结构体、枚举、联合)知识点+完整思维导图+深入细节+通俗易懂+基本练习题+建议收藏(二)

2.位段

2.1位段的基本知识

知识点:

位段是一种附于结构体之上的一种改变结构体成员所占内存的功能(类似结构体的一种新的结构体),位(比特位);

位段的用法是在结构体成员后加上冒号和数字;位段的成员一般只是int / unsigned int / signed int 、char(整形家族);

位段的意义对于数值来说有时候一些数值并不一定需要一个整形的大小(32bit)才能放的下,他可能只需几个bit即可放的下,所以通过位段的形式来对所开辟的空间进行限制来节约空间


细节:

一般来说位段里的成员类型都是相同的或者是其有无符号位的类型

通过其结构体成员的类型来依次开辟空间4byte(int)、1byte(char),只有当不够的时候才会再次开辟

位段的使用,对于后面的数字来说不能超过其自身类型大小int (32bit)char(8bit)

因为位段涉及这许多的不确定因素,所以位段是不跨平台的,如果是可移植程序中要慎用(避免)

通过代码进行具体讲解:

 #define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct A
{
  int a : 2;
  int b : 5;
  int c : 10;
  int d : 30;
};
int main()
{
  printf("%d", sizeof(struct A));//最终打印出8,而不是16
  return 0;
}

对于上面的代码为什么最终大小是8:

首先其全部是int类型,所以先开辟32bit,a的所需大小是2bit,所以还剩30,b:5,所以还剩25、c:10 ,还剩15,当到d时,所剩的空间已不够,所以就需要才开辟32bit,所以最终开辟64bit(8byte)

对于第一次开辟的空间最后还剩下的15bit 是否使用 这C语言中并没有严格的规定 所以就导致位段有不确定因素

对于位段的跨平台问题

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

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题,此时整形的最大bit是16)。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义(vs2019右到左)

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


练习:

当开辟了一个32bit的空间第一个 成员变量的值 从左边开始放还是从右边开始放也就是对于位段在内存中是如何存成员的数据的?

以及当空间不够时,到底是利用剩余的空间再加上新开辟的空间还是直接只用新开辟的空间?                                                                                                                                        下面通过调试的方法来看

image.png

通过调试的方法我们可以得到在vs2019环境下:

(小端存储):实际应该是 04 03 62 此时位段是将前名的成员从右往左开始放的,并对于前面剩下的空间也不会利用(最后的03 04 就能看出:04(d)并没有用到前面剩下的空间0)


数据的存储问题

对于开辟好的空间存放成员的数据时他会从右往左一次放进对应的bit

如果之前开辟的空间不够后他不会再利用那些不够的空间,而是直接将数据放在新空间内

image.png

对于b.d 我就不写了方法是一样的,先将要存的数字转化成二进制,在通过所分配的bit 将这个二进制放到内存中,如果空间不够就开辟新空间(新空间开辟的地址大于老空间)

image.png

  最终大小就是3byte


3.枚举(enum)

对也就是一一列举,对一些值进行列举:如一周的星期,一年的月份

将这些周进行列举,创建一个枚举常量来确定这个实际的事

3.1枚举的定义

知识点:

语法如下

enum name

{

       e1,

       e2,e3

};

对于枚举名(name)一般起的有意义点,枚举常量(e1,e2,e3)间用逗号隔开最后一个不需要加,而枚举常量是枚举变量的可能取值(enum name s = e1)

//如星期
enum Day
{
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};

细节:


每个枚举常量都有对应可代替的数值,当你不初始化时他默认从0开始递增image.png


image.pngimage.png这样你就可以通过Mod来代替0;

当然你还有自定义开始的值,从后开始跟着递增,如:

image.png

所以你在整月份时就可以将 Jan = 1 然后以此递增

不能在枚举外的其余地方进行枚举常量的修改

对于枚举定义常量相较于#define定义常量的区别:

增加了可读性

有了类型检查                                                                                                                

将常量进行了封装让其不能被轻易更改

可以调试

方便使用,一次定义多个常量

对于枚举的大小,因为其内容可以代表整形所以其大小也和整型一样(4byte)

4.联合体(共用体union)

知识点:

联合体同样也是一种自定义类型,这种类型定义的变量也包含一系列的成员,而这些成员不同于结构体他们是共用同一块空间的(他就想一个自习室,大家都可以用,不过大家的用法不同,你可能要用到电脑、而别人只看书空间共享,当使用方法可能不同,并且假如上一个人在使用完后没有拿自己的电脑,就会导致下一个人来对时候电脑还在这个空间内)。

联合体的大小

联合体这块空间的大小是至少等于一系列成员中最大的成员的类型大小

并且这块空间的大小也要对齐其中最大对齐数的整数倍

细节:

 #define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
union A
{
  char arr[6];
  int a;
};
int main()
{
  printf("%d", sizeof(union A));//8
  return 0;
}

image.png

附:对于联合体所共用的空间,当前一个成员用过后,如果另一个成员使用时并不覆盖的话仍然会存留之前的数据,并不会自动销毁


练习:

写一个函数来实现判断计算机是大端还是小端

union A
{
  char arr;
  int i;
};
int Judge_system(union A* a)
{
  a->i = 1;
  return a->arr;
}
int main()
{
  union A a = {0};
  int ret = Judge_system(&a);
  if (ret == 0)
  {
    printf("大端\n");
  }
  else
  {
    printf("%d小端\n",ret);
  }
  return 0;
}

本章完。预知后事如何,暂听下回分说。

相关文章
|
22天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
30 10
|
21天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
26天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
26天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
6天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
21 6
|
26天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
34 10
|
19天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
25天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
53 7
|
25天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
29 4