当前位置: 首页 > news >正文

【逐步剖C】-第十章-自定义类型之结构体、枚举、联合

一、结构体

前言:有关结构体的声明、定义、初始化以及结构体的传参等结构体的基本使用在文章【逐步剖C】-第六章-结构体初阶中已进行了详细的介绍,需要的朋友们可以看看。这里主要讲解的是有关结构体的内存问题。

1. 结构体的内存对齐

(1)对齐规则与结构体大小的计算

我们知道,一个结构体内部可能有多个不同类型的成员,那么对于整个结构体而言,它的大小怎么计算呢?这就要涉及到结构体内存对齐这个重要知识点了
这里先放上结构体内存对齐的规则
(1)结构体的第一个成员,对齐到与该结构体成员对比偏移量为0的地址处;
(2)从第二个成员开始,每个成员都要对齐到(偏移量为)其对应对齐数的整数倍的地址处

  • 每个成员对应的对齐数 = 编译器默认的一个对齐数 与 该成员大小 的较小者。
    VS中默认的值为8 Linux gcc中没有默认的对齐数,对齐数就是结构体成员的自身大小

(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
(4)如果有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

下面将结合代码并配合示意图进行讲解。
代码1

struct S1
{char ch1;int i;char ch2;
};
printf("%d\n", sizeof(struct S1));

运行结果:
在这里插入图片描述
解释

  • 首先,结构的第一个成员为ch1,其类型为char,那么根据规则的第一条,其将对齐到偏移量为0的地址处,示意图如下:
    在这里插入图片描述

  • 其次,结构的第二个成员为i,其类型为int,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为int类型的大小为4个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员i对齐数为4,故成员i将在成员ch1的基础上对齐到(偏移量为)4的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 接着,结构体的第三个成员为ch2,其类型为char,同第二个成员i,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为char类型的大小为1个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员的对齐数为1,故成员ch2将在前面成员的基础上对齐到(偏移量为)1的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 最后,根据规则3,在所有成员都对齐到对应地址处的基础上,整个结构体的总大小为所有成员中最大对齐数的整数倍,也就是4(成员i的对齐数)的整数倍,故整个结构体的大小为12

代码2:

struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));

运行结果:
在这里插入图片描述

解释

  • 首先,结构的第一个成员为c1,其类型为char,那么根据规则的第一条,其将对齐到偏移量为0的地址处,示意图如下:
    在这里插入图片描述

  • 其次,结构的第二个成员为c2,其类型为char,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为char类型的大小为1个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员的对齐数为1,故成员c2将在成员c1的基础上对齐到(偏移量为)1的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 接着,结构体的第三个成员为i,其类型为int,同第二个成员c2,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为int类型的大小为4个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员的对齐数为4,故成员i将在前面成员的基础上对齐到(偏移量为)4的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 最后,根据规则3,在所有成员都对齐到对应地址处的基础上,整个结构体的总大小为所有成员中最大对齐数的整数倍,也就是4(成员i的对齐数)的整数倍,故整个结构体的大小为8

代码3:

struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3));

运行结果:
在这里插入图片描述

解释

  • 首先,结构的第一个成员为d,其类型为double,那么根据规则的第一条,其将对齐到偏移量为0的地址处,又因为其本身大小为8个字节,故其在内存中的示意图如下:
    在这里插入图片描述

  • 其次,结构的第二个成员为c,其类型为char,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为char类型的大小为1个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员的对齐数为1,故成员c将在成员d的基础上对齐到(偏移量为)1的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 接着,结构体的第三个成员为i,其类型为int,同第二个成员c,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为int类型的大小为4个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员的对齐数为4,故成员i将在前面成员的基础上对齐到(偏移量为)4的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 最后,根据规则3,在所有成员都对齐到对应地址处的基础上,整个结构体的总大小为所有成员中最大对齐数的整数倍,也就是8(成员d的对齐数)的整数倍,故整个结构体的大小为16

代码4

struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));

运行结果:
在这里插入图片描述

解释:

  • 首先,结构的第一个成员为c1,其类型为char,那么根据规则的第一条,其将对齐到偏移量为0的地址处,示意图如下:
    在这里插入图片描述

  • 其次,第二个成员s3为另一个结构体类型的变量,该类型就是我们代码3中的结构体类型S3,这里就涉及到结构体嵌套的情况,那么结合规则4与对代码3的分析我们可得,成员s3对齐到(偏移量为)自己结构中最大对齐数(也就是结构S3中成员d的对齐数,为8)的整数倍的地址处,又因为成员本身的大小为16个字节,故内存中的参考示意图如下:
    在这里插入图片描述

  • 接着,结构体的第三个成员为d,其类型为double,根据规则的第二条,其将对齐到(偏移量为)它对应的对齐数的整数倍的地址处,又因为double类型的大小为8个字节,编译器(VS)默认的对齐数为8,取二者中的较小者,故该成员的对齐数为8,故成员i将在前面成员的基础上对齐到(偏移量为)8的整数倍的地址处,示意图如下:
    在这里插入图片描述

  • 最后,根据规则3,在所有成员都对齐到对应地址处的基础上,整个结构体的总大小为所有成员中最大对齐数的整数倍,也就是16(成员s3的对齐数)的整数倍,故整个结构体的大小为32

(2)内存对齐的意义

通过如上讲解,大家可能想问:内存示意图中那些空出来的白色部分去哪了呢
答案是:这部分的内存会为了完成结构体的对齐而浪费掉
大家可能会追问到:那么为了内存对齐而浪费这些内存空间真的有必要吗?
那么接下来为大家介绍一下结构体内存对齐的意义

  • 为什么会存在结构体对齐呢?
    通过大部分资料可总结出如下两个主要原因:
  • 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
    定类型的数据,否则抛出硬件异常。
  • 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
    问。

其中对与性能原因中的两次访问可以这么理解:
如上面代码2中的这个结构体

struct S1
{char c1;int i;char c2;
};

看下面这张内存示意图的对比:
在这里插入图片描述
假设以4个字节(一般是成员最大对齐数)为单位进行内存访问,那么对于内存对齐的情况,由于中间的内存空间是不用的,所以每次访问都能完整地读取到每个成员的内存信息

  • 访问地址0 1 2 3得到成员ch1的信息
  • 访问地址4 5 6 7得到成员i的信息
  • 访问地址8 9 10 11得到成员ch2的信息

而对于内存内存不对齐的情况,由于内存空间都是连在一起的,所以在每次访问内存时都可能会出现“割裂” 访问的情况

  • 访问地址0 1 2 3时既有成员ch1的信息,又有成员i的信息;故对于成员i而言,需要两次访问才能完整地得到成员i内存中的信息

如此看来,采用内存对齐后更方便了对结构体成员内存的访问
所以对于内存对齐,总的来说就是,牺牲一定的空间来换取时间上的效率

那么我们在设计结构体的时候我们如何在内存对齐的情况下尽量地节省空间呢?
答案是:让占用空间小的成员尽量集中在一起。让上面的代码(1)与代码(2),结构体成员的类型相同,但由于结构体成员在结构体中的位置不同,导致最终结构体大小也不同。

(3)修改默认对齐数

上面提到,不同编译器可能有不同的默认对齐数,对这个默认对齐数,其实我们可以通过一个预处理指令#pragma对其进行更改,请看下面这段代码:

#pragma pack(8)//设置默认对齐数为8
struct S1
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认#pragma pack(1)//设置默认对齐数为1
struct S2
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认int main()
{printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));return 0;
}

程序输出结果:
在这里插入图片描述
这里的分析方法呢和(1)部分中的内容是相同的,唯一的区别就在于编译器默认对齐数的改变导致了结构体成员最大对齐数的改变,这里就不在赘述啦。
所以,我们可以判断结构体对齐方式是否合适,从而自己更改合适的默认对齐数。

2. 结构体实现位段

(1)位段的定义

  • 什么是位段呢?

位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。———百度百科

  • 位段的声明方式与规则:

    • 位段的成员必须是 int、unsigned int 或signed char
    • 位段的成员名后边有一个冒号和一个数字

    如下面这段代码:

struct A
{int a:2;int b:5;int c:10;int d:30;
};

结构体A就是一个位段类型,那么位段A的总大小该如何计算呢?请看下面一部分。

(2)位段的内存分配

  • 这里先以上面的结构体A作为例子进行分析,然后再介绍一些位段内存分配的细节问题
    由位段的概念可知,我们可以以位(bit)为单位来指定结构体成员所占的内存长度
    对于结构体A而言,其占用内存的情况,可以按以下过程进行理解:
    • 第一个成员,int a:2;,编译器先申请32个比特位,然后给成员a分配2个比特位,分配完成后,剩余30个比特位
    • 第二个成员,int b:5;,编译器再从剩余的30个比特位中给成员b分配5个比特位,分配完成后,剩余25个比特位
    • 第三个成员,int c:10;,编译器再从剩余的25个比特位中给成员c分配10个比特位,分配完成后,剩余15个比特位
    • 第四个成员,int d:30;,编译器需要为其分配30个比特位,但剩余的比特位不够分配的需求,编译器会重新申请32个比特位,并用这新申请的32个比特位来为其进行内存分配
      此时就会产生一个问题:第一次申请中的剩余的15个比特位去哪了呢
      可能舍弃了,也可能保存起来了,这里没有明确的标准规定,所以是不确定的。
    • 回到过程中,至此,对每个成员的内存分配就完成了,那么由如上过程我们得到,为成员分配内存时共申请两次32个比特位,总计也就是64个比特位8个字节。故结构体A最终的大小就为8个字节。我们也可通过打印进行验证:

代码:

int main()
{struct A{int a : 2;int b : 5;int c : 10;int d : 30;};printf("%d\n", sizeof(struct A));return 0;
}

结果:
在这里插入图片描述
注:上面所谓内存分配的“过程”仅是一种理解方式,实际中的内存空间是一次就开辟好了的,这一点需要注意。

  • 接下来说明一下位段在内存分配中的一点细节问题:
    请看下面这一段代码:
struct S
{char a:3;char b:4;char c:5;char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

在(1)中我们介绍了一下内存分配的 “过程”,接下来将通过编译器的调试带大家看看在对结构体成员进行赋值时,内存中内容的实际变化(PS:接下来的内容需要用到机器大小端字节序存储的知识点,如果有不太了解的朋友可以看看这篇文章:【逐步剖C】-第七章-数据的存储)。

  • 执行完语句struct S s = {0};的初始状态:
    在这里插入图片描述
    解释:可以看到整个结构体在内存中只占了3个字节的长度,这里同样可以用(1)中的 “分配过程” 来理解:编译器先申请8个比特位(1个字节)PS:内存的开辟形式编译器会按需分配,最后的小结中还会提到)给成员a分配3个比特位后剩余5个比特位,接着给成员b分配4个比特位后还剩1个比特位;接着编译器新申请8个比特位分配给成员c剩3个比特位;由于剩余比特位又不够成员d的分配,故最后编译器又再申请了8个字节分配给成员d
    综上,编译器一共申请了3个字节的内存。故整个结构体在内存中的大小就为3个字节。

继续往下,

  • 执行完赋值语句s.a = 10;后:
    在这里插入图片描述
    解释
    可以看到,内存中第一个字节的内容被改为了02,但按理来说成员a被赋值为了10,第一个字节的内容应该是0a(十六进制)呀。这就是位段的效果了,我们知道,按8个比特位来看,10的二进制序列为:
0000 1010

对于成员a而言,其在内存中其实只占用了前3个比特位的内容,即010,理解起来就是 “截断往里存”,故在内存中二进制序列的实际情况为:

0000 0010

这样换算成十六进制就是图中的02了。

  • 执行完赋值语句s.b = 12;后:
    在这里插入图片描述
    解释
    可以看到第一个字节的内容变为了62,其中的原因和上面一样:
    首先,12的二进制序列为:
0000 1100

由于成员b只占用4个比特位的内容,所以截断为1100,并在成员a的基础上往里存,也就是内存中的二进制序列变为:

0111 0010

在这里插入图片描述
这样换算成十六进制就是图中的62了。

  • 执行完赋值语句s.c = 3;后:
    在这里插入图片描述
    解释:可以看到,第二个字节中的内容变为了03,由初始状态的解释可以知道,在为成员c分配空间时新申请了一个字节,成员c占用5个比特位的内存所以 “截断” 为00011往里存,那么在前面的基础上,我们从16个比特位来看,此时内存中的二进制序列如下:
0000 0011 0110 0010

在这里插入图片描述

  • 最后,执行完赋值语句s.d = 4;后:
    在这里插入图片描述
    解释:可以看到,第三个字节的内容变为了04。同样由初始状态的解释可以知道,在为成员d分配空间时又新申请了一个字节,成员d占用4个比特位的内存所以 “截断” 为0100往里存,那么在前面的基础上,我们从24个比特位来看,此时内存中的二进制序列如下:
0000 0100 0000 0011 0110 0010

在这里插入图片描述
这里大家可能会发现,实际二进制序列的内容和编译器上显示出来的内容好像是反着的,即:

0000 0100 0000 0011 0110 0010
对应转为16进制应为:
04 03 62
而编译器显示的内容为:
62 03 04

这是因为当前机器的存储形式为小端字节序存储(低位的数据存储在低地址,高位的数据存储在高地址),对于这部分的详细介绍感兴趣的朋友们可以看看这部分开始时提到的那篇文章,下面是示意图:
在这里插入图片描述

小结

  • 位段的成员可以是 int、unsigned int 、signed int 或者是 char (属于整形家族)类型
  • 位段的空间上是按照需要以4个字节( int )(32个比特位)或者1个字节( char )(8个比特位)的方式来开辟的(按需分配)

(3)位段的一些问题

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。)且一般来说,位的指令不能超过自身类型的大小。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的
  • 位段是不存在对齐的

解释一下其中的第三点
以(2)中的例子为例,在为结构体成员ab分配空间时,我们看到其实是从右向左进行内存分配的,即:

0111 0010

在这里插入图片描述
但严格来说,分配的方式是标准未定义的,即也有可能从左向右进行内存分配,即:

0101 1000

在这里插入图片描述

总结

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在(严格来说,位段是不跨平台的) ;位段涉及很多不确定因素,故注重可移植的程序应该避免使用位段。

(4)位段的应用

位段主要运用于数据在网络中的运输 (PS:这里仅简单说明一下位段的作用,更多有关数据在网络中的运输等计算机网络的知识由于博主仍在学习,这里就不做过多介绍啦)
数据在网络中运输时,会在数据之上再封装数据,以确保数据的准确运输。那么用来封装的数据肯定不能都像int等类型一样使用固定字节的大小。我们可以把网络想像为高速公路,若全为大卡车则非常容易造成拥挤,而通过位段可以到达 “缩小” 的作用,从而减少流量的压力
总结来说就是,位段的使用有利于应对网络拥堵的问题

二、枚举

顾名思义很好理解,就是把某个事物所有可能的情况进行一一列举,如:掷骰子总共会出现六种情况;一周总共有七天等等。

1. 枚举类型的定义

如上的两个例子作为枚举变量我们就可以定义为如下形式,请看:

enum Roll
{One,Two,Three,Four,Five,Six
};enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};

以上的enum Rollenum Day就是枚举类型,花括号中的数据就是枚举类型可能的取值,称为枚举常量,那么在定义一个枚举类型和枚举变量时有以下这么几个需要注意的点
(1)定义枚举类型时,枚举常量间是使用逗号进行分隔的,且最后一个枚举常量不需要加逗号;
(2)枚举常量是有默认值的,默认从0开始,一次递增1,也可以在定义的时候就进行赋初值,如:

enum Roll
{One = 1,Two = 2,Three = 3,Four = 4,Five = 5,Six = 6
};

若只给其中一个枚举常量赋了初值,那么该枚举常量前的枚举常量仍采用默认值;而其后的枚举常量将以其为基准,一次递增1;如:

enum Roll
{One,Two,Three = 24,Four,Five,Six
};

其中枚举常量One和Two的值为0,1;而枚举常量Four,Five,Six的值分别为25,26,27
(3)和定义结构体变量相同,若没有使用typedef关键字进行类型重命名,那么在定义变量时就需要写全定义,如用枚举类型定义一个名为Dice的枚举变量时,正确的写法为:enum Roll Dice;;而不能写为:Roll Dice;
(4)只能用枚举常量给枚举变量赋值,也不能直接更改枚举常量的值(因为是“常量”)

2. 枚举的使用

这里简单展现一下枚举的使用,请看:

enum Roll
{One = 1,Two,Three,Four,Five,Six
};int main()
{enum Roll dice = One;printf("Roll:%d\n", dice);dice = Six;printf("Roll:%d\n", dice);return 0;
}

输出结果:
在这里插入图片描述

3. 枚举的优点

从枚举的使用可以看出,枚举的使用其实和#define定义常量非常类似,那么相比之下我们使用枚举有什么优点呢?
枚举的优点主要为以下几点:

(1)增加代码的可读性和可维护性
(2)和#define定义的标识符相比,枚举有类型检查,更加严谨。
(3)一定程度上实现了封装,防止了命名污染
(4)便于调试
(5)使用方便,一次可以定义多个常量

额外补充一点:其实#define定义的常量在编译期间就已经被替换为所定义的值了,此时从整体代码的视角看来就会有些许的 “分裂” 感。
如:

#define Max 100
int main()
{int m = Max;return 0;
}

上面代码中,我们希望表达的是变量m中存着定义的最大值Max;但代码经过编译后,如上代码就变为了:

#define Max 100
int main()
{int m = 100;	//Max直接替换为了100return 0;
}

如此一来,代码其实就不能很好地表达出我们所希望表达的意思了。

三、联合

1. 联合类型的定义

联合类型和结构体类型相似,包含着一系列的成员,但独有的特征是:这些成员共用同一块内存空间(故联合类型也被称为共用体或联合体)
联合体可通过如下方式进行定义,请看:

//联合类型的定义
union Un
{char c;int i;
};//联合变量的定义
union Un un;

2. 联合的特点

开头提到了,联合类型的特点其实就是联合体中的成员会共用同一块内存空间。
可以用这样一段简单的代码进行验证,请看:

union Un
{int i;char c1;
};int main()
{union Un un;printf("%p\n", &(un.i));printf("%p\n", &(un.c1));return 0;
}

输出结果:
在这里插入图片描述
可以看出,联合体中的两个成员所占用的内存空间的地址是相同的,也就是说,两个成员共用同一块内存空间。

那么再请大家看一下下面代码会输出什么结果呢?

union Un
{int i;char c1;
};int main()
{union Un un;un.i = 0x11223344;printf("%x\n", un.i);un.c1 = 0x55;printf("%x\n", un.i);return 0;
}

输出结果:
在这里插入图片描述
解释
我们还是通过调试的方法来进行说明:

  • 初始状态:
    在这里插入图片描述
  • 执行完语句un.i = 0x11223344;后:
    在这里插入图片描述
    由于成员i的类型为整型,故内存中4个字节的内容被改为了44 33 22 11这里数据的顺序和打印出来的数据之所以反过来是因为当前机器的存储方式为小端存储(在介绍位段时也已提到,这里不再赘述啦)。
  • 执行完语句un.c1 = 0x55;后:
    在这里插入图片描述
    由于成员c1的类型为字符型,故原内存中第一个字节的内容被改为了55
    故最后以十六进制输出就为11223355

在上面所提到的那篇介绍大小端的文章中,给出了判断当前机器存储方式的一种方法,这里根据联合类型的特点再提供一种判断方法,请看:

int check_sys()			//大端返回0,小端返回1
{union Un un;un.i = 1;return(un.c == 1);
}

解释
若机器的存储方式为小端存储,那么语句un.i = 1;就会将内存中的内容改为01 00 00 00

低地址----------------------->高地址
//小端存储:
01 00 00 00//大端存储:
00 00 00 01

那么此时成员c(大小为一个字节)中的内容也就为01,故可直接对成员c中的值进行判断,并返回判断结果即可

那么对于一个联合体而言,它所占用内存空间的大小究竟应该如何分配(计算)呢?
请朋友们继续往下看。

3. 联合大小的计算

这里先放上联合体大小计算的规则

(1)联合体的大小至少为最大成员的大小
(2)联合体也是存在内存对齐的,若最大成员的大小不是最大对齐数的整数倍时,就要对齐到(偏移量为)最大对齐数的整数倍的地址处

下面结合例子进行说明:

union Un1
{char c[5];int i;
};
union Un2
{short c[7];int i;
};printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));

程序输出结果:
在这里插入图片描述
解释

  • 对于联合体union Un1
    成员char c[5]大小5个字节对齐数1
    成员int i大小4个字节,对齐数为4
    故最终联合体的大小需对齐到(偏移量为)最大对齐数(也就是4)的整数倍的地址处,且需要保证联合体的大小至少为最大成员的大小,故最终联合体的大小为8个字节
  • 对于联合体union Un2
    成员short c[7]大小14个字节对齐数2
    成员int i大小4个字节对齐数4
    故最终联合体的大小需对齐到(偏移量为)最大对齐数(也就是4)的整数倍的地址处,且需要保证联合体的大小至少为最大成员的大小,故最终联合体的大小为16个字节

总结:在计算联合体的大小时,关键在于注意区分最大成员的大小最大对齐数的概念;前者除了关注类型还需关注个数,而后者可只关注类型。

本章完。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

相关文章:

【逐步剖C】-第十章-自定义类型之结构体、枚举、联合

一、结构体 前言:有关结构体的声明、定义、初始化以及结构体的传参等结构体的基本使用在文章【逐步剖C】-第六章-结构体初阶中已进行了详细的介绍,需要的朋友们可以看看。这里主要讲解的是有关结构体的内存问题。 1. 结构体的内存对齐 (1&…...

Windows Server 2016 中文版、英文版下载 (updated Mar 2023)

Windows Server 2016 Version 1607,2023 年 3 月更新 请访问原文链接:https://sysin.org/blog/windows-server-2016/,查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org 本站将不定期发布官方原版风格月度更…...

Linux 4G 通信实验

目录4G 网络连接简介高新兴ME3630 4G 模块实验ME3630 4G 模块简介ME3630 4G 模块驱动修改ME3630 4G 模块ppp 联网测试前面我们学习了如何在Linux 中使用有线网络或者WIFI,但是使用有线网络或者WIFI 有 很多限制,因为要布线,即使是WIFI 你也得…...

华为OSPF技术详细介绍,保姆级,谁都能看懂(一)

目录 1、简介 2、OSPF基本原理 3、OSPF的特点 4、OSPF区域 5、路由器的类型 6、OSPF5种报文 7、后半部分内容 1、简介 OSPF(Open Shortest Path First,开放最短路径优先)是一个基于链路状态的内部网关协 议。目前针对IPv4协议使用的是OS…...

行人车辆检测与计数系统(Python+YOLOv5深度学习模型+清新界面)

摘要:行人车辆检测与计数系统用于交通路口行人及车辆检测计数,道路人流量、车流量智能监测,方便记录、显示、查看和保存检测结果。本文详细介绍行人车辆检测,在介绍算法原理的同时,给出Python的实现代码、PyQt的UI界面…...

SM3哈希算法的FPGA实现 I

SM3哈希算法的FPGA实现 I SM3哈希算法的FPGA实现 I一、什么是SM3哈希算法?二、SM3哈希算法的具体内容1、填充2、迭代与压缩3、计算拼凑值三、参考文档语言 :verilog 仿真工具: Modelsim EDA工具:quartus II 一、什么是SM3哈希算法…...

【数据结构与算法】线性表--数组

文章目录一、前言二、数组的概念三、数组的操作数组的插入数组的删除四、容器与数组五、问题:为何数组要从0开始编号,而不是1开始呢?六、总结一、前言 常见的数据结构如下图,本文主要讲解数据结构线性表--数组。 二、数组的概念 …...

剑指offer排序专题

剑指offer排序专题 jz3 数组中重复的数字描述 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组[…...

已解决Cannot open D:\Soft\Python36\Scripts\pip3-script.py

已解决Cannot open D:\Soft\Python36\Scripts\pip3-script.py 文章目录报错问题报错翻译报错原因解决方法1:easy_install 来安装pip解决方法2:本地安装pip《100天精通Python》专栏推荐白嫖80g Python全栈视频报错问题 粉丝群里面的一个小伙伴遇到问题…...

3 步走,快速上手 API 接口测试

开始 API 接口测试之前,我们需要弄清接口测试的含义: 接口测试就是根据接口清单,模拟客户端向服务端发送请求数据,并获取响应数据后,查看响应数据是否符合预期的过程。 整个过程可以分为三个步骤: 第一步&…...

爬虫-day1-正则表达式作业

利用正则表达式完成下面的操作: 一、不定项选择题 能够完全匹配字符串"(010)-62661617"和字符串"01062661617"的正则表达式包括(ABD ) A. r"\(?\d{3}\)?-?\d{8}" B. r"[0-9()-]" C. r"[0-9(-)]*\d*&…...

【半监督医学图像分割 2023 CVPR】RCPS

文章目录【半监督医学图像分割 2022 CVPR】RCPS摘要1. 介绍2. 相关工作2.1 医学图像分割2.1 半监督学习2.3 对比学习3. 方法3.1 整体概述3.2 纠正伪监督3.3 双向Voxel对比学习。4. 实验【半监督医学图像分割 2022 CVPR】RCPS 论文题目:RCPS: Rectified Contrastive …...

【UVM实战练习项目】2、UVM验证环境基本框架搭建(实例一)(纯软件环境,方便日后测试使用)

本节基于DUT完成UVM验证环境的基本框架搭建,实现对UVM理论知识点进行巩固练习,具体内容包括:如何创建激励、如何建立sequencer、如何连接sequencer和driver,如何集成agent、如何构建env等。 正式开始之前让我们再来回顾下搭建验证环境的过程:首先进行数据建模sequence_ite…...

【web前端初级课程】第四章 什么是JavaScript

目录 一、JavaScript在前端的三种写法 二、常见的弹框 三、变量 四、常量 五、数据类型 六、运算符 七、循环及函数 八、相关练习 前言 JavaScript是一个面向对象的,弱数据类型的,解释型的,动态脚本语言。 面向对象更符合我们对事物…...

数字中国建设进行时:吉林大学党委常务副书记冯正玉一行调研实在智能

两会前夕,中共中央、国务院印发了《数字中国建设整体布局规划》,明确了加快数字中国建设的重点任务。《规划》强调,要加强整体谋划、统筹推进,把各项任务落到实处。在强化人才支撑的第四要点上,指出统筹布局一批数字领…...

面试官灵魂拷问[二]:SQL 语句中 where 条件后写上 1=1 是什么意思?

面试官灵魂拷问系列又来更新啦! “SQL 语句中 where 条件后写上 11 是什么意思?” 这玩意就跟很多新语言支持尾部逗号的原理一样的。 比如 Kotlin 支持数组写成 [1, 2, 3, 4, ] ,注意4后边那个逗号,为什么呢?因为当你增加一个项…...

进程与线程的关系

一、 进程 进程(Process)是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完成过程,这个过程也是进程本身从产生、发展至消亡的过程。 操作系统同时管理一个计算机系统中的多个进程,让计算机…...

自定义异常

自定义异常 使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。在程序中使用自定义异常类,大体可分为以下几个步骤: 创建自定义异常类。在…...

基于springboot物资管理系统(程序+数据库)

大家好✌!我是CZ淡陌。一名专注以理论为基础实战为主的技术博主,将再这里为大家分享优质的实战项目,本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路…...

蓝桥杯Web组备赛笔记6

目录 一、ElementUI 1、安装 2、简单使用 3、例子 4、其他内容的学习 二、echarts 1、简介 2、考点 3、安装 4、配置项:使用echarts的三步走 5、13届蓝桥真题(3)布局切换 6、数据格式处理:14届蓝桥模拟赛 1 期&#x…...

python控制语句

🍋在本次的博客当中,我们来认识一下python语言的新的部分——python语言的控制语句。在我们的python语言当中控制语句大致分为三类:1.选择语句,2.循环语句,3.跳转语句。当我们在编写代码的时候可以根据代码的逻辑的需求…...

华为OD机试题【最小叶子节点】用 Java 解 | 含解题说明

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典本篇题目:最小叶子节点 题目 二叉树也可…...

【linux】多线程控制详述

文章目录一、进程控制1.1 POSIX线程库1.2 创建线程pthread_create1.2.1 创建一批线程1.3 终止线程pthread_exit1.4 线程等待pthread_jion1.4.1 线程的返回值(退出码)1.5 取消线程pthread_cancel1.6 C多线程1.7 分离线程pthread_detach二、线程ID值三、线…...

SpringCloud学习-实用篇01

以下内容的代码可见:SpringCloud_learn/day01 1.认识微服务 单体架构和分布式架构 体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署 优点:架构简单,部署成本低缺点:耦合度高 分布式架构&#…...

如何使用python删除一个文件?好用到上头.....

人生苦短,我用python 若想利用python删除windows里的文件, 这里需要使用os模块 那接下来就看看利用os模块是如何删除文件的吧~ 具体实现方法如下! 更多学习资料:点击此处跳转文末名片获取 os.remove(path) 删除文件 path. 如果path是一…...

java学习笔记——权限修饰符、内部类

2.1 概述 在java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限, public:公共的 protected:受保护的 default:默认的 private:私有的 2.2 不同权限的…...

Java设计模式(十二)—— 状态模式

状态模式定义如下:允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类。 适合状态模式的情景如下: 对象的行为依赖于它的状态,并且它必须在运行时根据状态改变它的行为。需要编写大量的条件分支语句来决定…...

功能测试自动化成功的7个因素

随着软件开发的不断发展,对高效和有效测试的需求也在不断增加。最关键的测试类型之一是功能测试,它确保软件执行其设计的任务。功能测试对于软件开发过程至关重要,而自动化对于实现更快、更可靠的结果也很重要。 为什么功能测试很重要&#x…...

基于openssl 自行签发https 协议证书 ,同时支持nginx配置

1准备工作 准备一台有openssl环境的主机即可,openssl版本暂时无要求。本次环境采用centeros7.6自带openssl。另外,准备一个nginx。 2证书签发 目录 1准备工作 2证书签发 2.1生成根秘钥 2.2生成根证书 2.2.1根证书格式转换 2.3生成私钥key 2.4生…...

Window Terminal 安装 Oh My Posh 美化

Reference Oh-My-Posh 官方文档Windows Terminal 官方文档手把手 Windows Terminal 美化 安装 微软商店搜Windows Terminal安装即可。 Oh My Posh winget 找不到 winget : 无法将“winget”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 解决方法:添加…...