【逐步剖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)中的例子为例,在为结构体成员a和b分配空间时,我们看到其实是从右向左进行内存分配的,即:
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 Roll 和 enum 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…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
