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

C语言自定义数据类型详解(二)——结构体类型(下)

书接上回,前面我们已经给大家介绍了如何去声明和创建一个结构体,如何初始化结构体变量等这些关于结构体的基础知识。下面我们将继续给大家介绍和结构体有关的知识:

今天的主题是:结构体大小的计算并简单了解一下位段的相关知识。

目录

一、结构体的内存对齐

(1)发现并提出问题: 

(2)内存对齐规则:

(3)解决问题:

附一:offsetof宏的介绍:

(4)为什么会存在结构体的内存对齐:

附二:带来的启发:

附三:修改编译器的默认对齐数:

附四:关于前面问题的解答:

二、关于位段:

(1)搭建应用场景:

(2)位段的概念性理解:

(3)位段的内存分配:


学习指南:如果你是期末备考的大学生,简单学习博主的结构体类型(上)的内容就够了。如果你对自己有更高的要求,希望系统深入了解结构体知识可以继续跟上博主的步伐啦!

一、结构体的内存对齐

(1)发现并提出问题: 

如果要谈结构体大小的计算,就不得不提结构体的底层特性——内存对齐。什么是内存对齐呢?我们先来看一下,下面这两个例子:

#include<stdio.h>
struct S1
{char c1;char c2;int i;
};int main()
{printf("%zd\n", sizeof(struct S1));return 0;
}

 这个结构体大小是多少呢?是不是 1 + 1 + 4(sizeof(char) + sizeof(char) + sizeof(int))的结果呢?,我们不妨在自己的机器上运行一下。

下面这张截图是在博主2022 Visual Studio这款IDE上运行的结果:

这个结果挺莫名奇妙的,我一时也想不清楚这个结果为啥是这个。嗯……我们现在不妨再来做一个事情,我们继续声明和创建一个S2的结构体,然后呢,这个结构体有和S1一样的成员变量(c1,c2,i)。然后只是改一下它们之间的顺序,我们来看一下这个结构体变量的大小又是多少。如图所示:

#include<stdio.h>
struct S1
{char c1;char c2;int i;
};
struct S2
{char c1;int i;char c2;
};int main()
{printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));return 0;
}

运行截图:

 这也太不可思议了,我仅仅是改了一下结构体各个成员变量的顺序,竟然就改变了整个结构体变量的大小。显然种种迹象均表明:结构体变量的大小并不是各个成员变量大小的简单相加

那这具体是怎回事呢,欸,我们接着往下探索!

(2)内存对齐规则:

前面我们通过实验发现,结构体变量的大小不是其各个成员变量的简单相加。其实其本质的原因就在于有内存对齐的底层特性存在。

那关于结构体的内存对齐有哪些规则呢?这里博主总结了关于内存对齐这一块,大致有以下四条规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处;
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;
  3. 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍;
  4. 如果嵌套了结构体,嵌套结构体要对齐到自己的最大对齐数的整数倍处,其他规则照旧。

上述规则中的一些概念,我们这里做简单的阐释。首先关于什么是对齐数

对齐数 = Min(编译器默认的一个对齐数,成员变量自身的大小)

一般来说:结构体的每一个成员变量都有自己的一个对齐数,不同大小的成员变量一般有不同的对齐数,其中我们把一个结构体它所有成员变量中最大的那个对齐数,称之为最大对齐数

特别地,如果成员变量是一个数组。那这里成员变量的大小,注意啦,不是指数组的大小,而是数组中单个元素的大小。我们下面以VS 2022为例子(编译器的默认对齐数是8),来说明这个问题:

struct S
{char c;       //8 < 1 —— 对齐数是1int  i;       //8 < 4 —— 对齐数是4char name[12];//8 < 12 ?No,No,No!    8 < 1 ——对齐数是1
};

另外关于编译器的默认对齐数,如果通过查阅相关编译器的技术文档,你能够查到该编译器的默认对齐数那当然再好不过了。但是如果说你实在查不到,那你就忽略这个编译器的默认对齐数(或者说假设它是无穷大)。如果说此时发现假设和实际结果不相一致,我们通过对结果进行分析,也可以推导出编译器的默认对齐数的大小。

那话说回来,我们还是希望大家记一些常见的关于编译器的默认对齐数:像VS系列都是8,然后gcc系列是无穷大,无穷大的另一种理解就是:对齐数 = 成员变量自身的大小。

(3)解决问题:

OK,当然啦,说了这么多,我们就可以分析和解决前面所提到的那个问题。我们这里假设是在VS 2022的环境下,也就是说编译器的默认对齐数是8。

#include<stdio.h>
struct S1
{char c1;char c2;int i;
};
struct S2
{char c1;int i;char c2;
};int main()
{printf("%zd\n", sizeof(struct S1));printf("%zd\n", sizeof(struct S2));return 0;
}

对于结构体struct S1而言,它的内存布局即为下图所示:

分析:

红色块是c1的内存空间,旁边的数字对应的是这个内存块相较于结构体起始地址的偏移量。

由于c1是第一个成员,因此偏移量为0。而且c1只需要1个字节的空间,综上红色块就是c1的内存空间。

绿色块是c2的内存空间,这是因为: 8(编译器默认对齐数)< sizeof(c2) == 1,因此对于c2来说它的对齐数就是1,也就是说c2内存的起始位置在偏移量为1的倍数的位置上,显然就要我们绿色块的位置上。

蓝色块是i的内存空间,这是因为:8(编译器默认对齐数)< sizeof(i) == 4,因此对于i来说它的对齐数就是4,偏移量为4的倍数的位置,恰好就是对应我们蓝色块的起始位置。然后往下延展4个字节,这是因为sizeof(i) == 4。

最后,显然最大对齐数就是4,此时c1 + c2 + i的内存大小是8,正好就是4的倍数,因此结构体的内存大小是8。

对于结构体struct S2而言,它的内存布局即为下图所示:

分析:

c1,i,c2的分析略,这里我们简单说一下结构体的大小分析。对于这个结构体我们不难推导出最大对齐数——4,然后此时c1 + i + c2的内存大小是9,9不是最大对齐数4的倍数,于是向下延展至12个字节,此时正好是4的倍数,由此结构体变量的大小是12。

如果你现在觉得自己强得可怕,不妨试着继续挑战一下,下面这两个结构体大小的计算:

struct S3
{char c1;int i;char name[12];
};
struct S4
{int i;struct S3 s;char c1;
};

对于结构体struct S3而言,大家只需要注意博主给大家提醒的如果成员变量是一个数组。那对齐数那里所说的成员变量的大小,不是指数组的大小,而是数组中单个元素的大小。因此最后结构体的最大对齐数也不是12,而是4。结构体整体的大小就是20。如图所示:

其次对于结构体struct S4而言,它里面嵌套了其他的结构体struct S3 s,我们只要注意s的对齐数是这个结构体的最大对齐数。就可以了。如果所有的一些没有问题,最后struct S4的大小就是28。如图所示:

附一:offsetof宏的介绍:

offsetof是一个宏,有了offsetof我们的用户,只需要给出一个结构体,然后再给出这个结构体的一个成员变量,你就可以获得:这个成员变量距离结构体起始位置的偏移量。然后如果你想使用这个宏,请#include<stddef.h>

我们这里以前面的struct S4结构体为例子,来看一下这个宏具体是如何使用的:

#include<stdio.h>
#include<stddef.h>
struct S3
{char c1;int i;char name[12];
};
struct S4
{int i;struct S3 s;char c1;
};int main()
{printf("struct S4 的i 的相较于起始位置的偏移量:%zd\n", offsetof(struct S4, i));printf("struct S4 的s 的相较于起始位置的偏移量:%zd\n", offsetof(struct S4, s));printf("struct S4 的c1的相较于起始位置的偏移量:%zd\n", offsetof(struct S4, c1));return 0;
}

运行截图:

而这些和我们所分析的结果是一致的。

有小伙伴可能对这个宏的底层实现比较感兴趣,我们这里也把这个offsetof宏的一种实现方式给大家放在下面了:

#define offsetof(type, member) ((size_t)&((type*)0)->member)

它的基本策略就是把地址为0的位置当作是结构体的起始地址,然后结构体的成员member再取地址就是这个结构体成员相较于起始位置的偏移量了。

(4)为什么会存在结构体的内存对齐:

C语言的结构体,为什么会有内存对齐的特性。有一种说法是说如果结构体的内存是对齐的话,会带来更好的内存访问效率。

OK,为了更好的说明这个问题,我们先来假定一些场景:

假如我们的用户创建了结构体S,并定义了一个该结构体的变量s,如图所示:

struct S
{char c;int i;
}s;

然后我们假定我们拥有一台32位的机器,注意这里的32位是指这台机器的机器字长是32位,机器字长32位也就意味着这台机器的CPU一次性要从内存拿32bit的数据,然后能一次性对32bit的数据进行处理(现代CPU对数据的各种处理都是基于机器字长为单位的)

 然后如果说我不是内存对齐的,我这次想要处理变量s中的一个成员i,那这个过程可以用下图进行阐述:

这个图片告诉我们:如果说我读的数据是i,我们的CPU会从结构体变量s的起始地址处开始读数据(如果可以CPU可以选择不这么做,显然这里我们不得不这么做),一次读4Byte数据(4Byte == 32bit,图中绿框所标识的范围),显然我第一次只能拿到i的前3个字节的内容,我需要再读一遍才能完整地读取到i。也就是说: 如果内存是不对齐的,对i变量的读取CPU要读两次。而且对于变量i来说,还有一些没必要的东西被读进来了。

紧接着,我们继续探讨,如果说,我们的内存的是对齐的。那这个时候的过程则为下图所示:

显然,这时我们发现,如果说CPU要对i进行处理,它完全可以做到只需要读一次,就可以将变量i完整地读进来。

从另一个角度,当你的数据是对齐的情况下,我们内存控制器和CPU的设计呢,会更加简单。因为显然的一点,如果内存对齐的情况下,我们的CPU不需要去处理那种跨边界的数据请求;非对齐的情况下进行访问,出现跨边界的数据请求时可能需要多次内存操作才能获取到所需要的数据。这增加了我们硬件设计的复杂性并带来了潜在的延迟。

因此我们这里可也以认为是说:这种结构体存储成员的策略,是一种利用空间效率来换取时间效率的做法。

这是博主能给大家介绍的一种为什么说会存在有内存对齐的一个原因。当然呢,也有其他地方有说法是说:这种内存对齐的策略会有利于硬件平台的移植……嗯,我们不希望在这里把这个问题搞得更复杂了,因此这些其他的原因就留给感兴趣的读者朋友去探索一下吧,不再进行深入的探讨。

附二:带来的启发:

在设计结构体时,如果我既希望满足对齐,又希望所浪费的空间尽可能的少。我们应该做到:让占用空间小的成员尽量集中在一起。比如说下面两个结构体的设计,显然struct S1的设计优于struct S2的设计:

 struct S1{char c1;char c2;int i;};
struct S2{char c1;int i;char c2;};
附三:修改编译器的默认对齐数:

C语言提供了#pragma pack(对齐数)这种格式的预处理指令,来对编译器的默认对齐数进行修改。理论上,对齐数都是大于0的正整数,特别地,如果指令格式里面的对齐数不写或写成0(其他的数据类型或负数往往会被认为非法),那就表示使用默认对齐数。

我们来看一个例子:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//先将编译器的默认对齐数设置为1:
#pragma pack(1)
struct S1
{char c;int i;
};
//再将编译器的默认对对齐数设置回来:
#pragma pack()
struct S2
{char c;int i;
};int main()
{printf("sizeof(struct S1) == %zd\n", sizeof(struct S1));printf("sizeof(struct S2) == %zd", sizeof(struct S2));return 0;
}

运行截图:

我们会发现,如果编译器的默认对齐数是1,带来的感觉就是没有进行内存对齐。当然无论怎么说,这应该和我们所预期的结果(推理的结果和实际结果一致)是一致的。

附四:关于前面问题的解答:

我们在C语言自定义数据类型详解(一)——结构体类型(上)-CSDN博客里面留了一个问题:为什么说结构体的传参,一般情况下是传址优于传值。现在我们对于一个结构体它的大小是如何计算的应该都已经是小case了,回过头来思考这个问题:如果我结构体传址的话,就如果是指针的话,那该参数对于函数栈的消耗要么是4Byte,要么是8Byte(x86是4,x64是8)。而一个结构体变量的大小往往会超过8个字节(你想一个char,一个int,然后还有内存对齐,这个大小已经就有8Byte了,更不要说更复杂的结构体)。这相应地,所带来的压栈消耗也会更大。

因此我们说结构体传参,传址往往优于传值。

二、关于位段:

(1)搭建应用场景:

A公司最近购入了四台机器。小明作为该公司的程序员被派遣了一个任务:要求建立一个数据结构用于维护四台机器的各自状态,这其中就包括了机器各自的id(0,1,2,3),机器的运行时间time,标识机器是否正常运行的状态指针state……这些可能就是作为维护人员,我们需要去关心的一些参数:

于是小明据此声明了下面这种结构,用来维护工厂里的这四台机器:

struct Machine
{int id;     //唯一标识一台机器的idint time;   //机器的运行时间int state; //机器目前的状态:1表示正常运行,0表示不正常
}

但是小明后来在使用过程中也注意到了一个问题,在这个结构体中,有一些成员的取值是非常有限的,比如说一台机器的id只有0,1,2,3四种可能的结果,一个机器的状态也就正常和故障两种。

而对于这样的成员变量,我们用int来存,嗯……可以是可以,但是我实际上能用到的也就2个二进制位而已(00:0,01:1,10:2,11:3)。也就是我当前的这种结构,id中有30bit都是无效的。

有小伙伴说用char,当然啦,这也行,但是有没有其他的方法呢?OK,这里跟着博主的步伐来认识一下我们接下来的主角——位段

(2)位段的概念性理解:

在某些应用场景中,为了尽可能地节省空间,我们的C语言结构体一般都有能够实现位段的能力。

位段的声明和结构体非常类似,但是有两个本质上的区别:

  1. 位段的成员只能是int,unsigned int signed int。C99之后支持了更多的数据类型,但是基本上就是int,char这样的数据类型。
  2. 在位段的成员名的后面,往往有一个冒号一个数字用来表示该成员变量的大小。以bit为单位。

也就是说,位段的成员虽然冠以int自称,但是它的实际大小往往低于int类型的4Byte。我们可以来看一个例子:

//我们这里在位段的成员名前面都加了下划线_,仅仅是为了便于区分它和一般的结构体成员变量,即这仅仅是个命名习惯罢了:
struct S
{int _a : 2;  //_a是位段成员,占2  bit位int _b : 5;  //_b是位段成员,占5  bit位int _c : 10; //_c是位段成员,占10 bit位int d;
};

(3)位段的内存分配:

对于位段成员的内存分配,C语言也没有提供具体的标准,但是一个大致的方向是:当你定义了一个位段成员时,它会先给你1Byte或4Byte空间(这一般取决于你前面的类型写的是int还是char),然后你不够了,编译器会继续再给你1Byte或4Byte空间。

什么意思呢?就拿我们前面定义的结构体来说,我们先把它拿过来:

#include<stdio.h>
//我们这里在位段的成员名前面都加了下划线_,仅仅是为了便于区分它和一般的结构体成员变量,即这仅仅是个命名习惯罢了:
struct S
{int _a : 2;  //_a是位段成员,占2  bit位int _b : 5;  //_b是位段成员,占5  bit位int _c : 10; //_c是位段成员,占10 bit位int d;
};int main()
{printf("sizeof(struct S) == %zd",sizeof(struct S));return 0;
}

首先编译器看你需要一个int类型的成员变量,于是它会先给你开辟4Byte空间。显然4Byte = 32bit,这对于_a,_b,_c显然是够用了的。因此_a,_b,_c会公用这4byte的空间。加上之后的int类型的b变量,这个结构体最终的大小大概就是8Byte左右。

显然这比你直接用4个int所需要的内存少多了。如图所示:

这就是一种简单地分析位段大小的一种方式。这里面其实还有很多的细节,如图所示:

具体到内部,_a要2bit,这2bit开辟,是从左往右,还是从右往左是C语言标准没有规定的。

另外,如果说是这样的:

#include<stdio.h>
//我们这里在位段的成员名前面都加了下划线_,仅仅是为了便于区分它和一般的结构体成员变量,即这仅仅是个命名习惯罢了:
struct S
{int _a : 15;  //_a是位段成员,占15 bit位int _b : 12;  //_b是位段成员,占12 bit位int _c : 10;  //_c是位段成员,占10 bit位
};

这里我们当然知道说这个结构体是8byte,然后_a和_b用了前4Byte的27bit位。问题是来给_c分配空间的时候:我们知道前面4Byte用了,它还有5bit没有。这5bit会不会给到_c,然后再从后面的4Byte拿出5bit,凑成最终的5bit呢。

还是说这前面4Byte遗留下的5bit不要了,_c的10bit全部在后面的4Byte里面。这件事情也是我们C语言标准所没有规定的事情。

换句话说,位段内部在给成员分配空间时,它的不确定性因素实在是太多了。这带来的直接问题便是:位段的跨平台性非常差!!!因为没有标准,每个编译器的厂家都是自己的那一套。因此我们实际中用位段其实用得非常非常少,这是希望大家注意到的一点。

博主在这里介绍也仅仅只是希望大家以后在纯C里面看到种定义结构体成员的方式能够看得懂。

至此,关于结构体所有相关的周边知识就介绍完啦,我们下期再见!🙂

相关文章:

C语言自定义数据类型详解(二)——结构体类型(下)

书接上回&#xff0c;前面我们已经给大家介绍了如何去声明和创建一个结构体&#xff0c;如何初始化结构体变量等这些关于结构体的基础知识。下面我们将继续给大家介绍和结构体有关的知识&#xff1a; 今天的主题是&#xff1a;结构体大小的计算并简单了解一下位段的相关知识。…...

DeepSeek学术写作测评第二弹:数据分析、图表解读,效果怎么样?

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 针对最近全球热议的DeepSeek开源大模型&#xff0c;娜姐昨天分析了关于论文润色、中译英的详细效果测评&#xff1a; DeepSeek学术写作测评第一弹&#xff1a;论文润色&#…...

深入理解 Python 中的 `__all__`:控制模块的公共接口

在 Python 编程中&#xff0c;模块化设计是构建可维护和可扩展代码的关键。模块不仅帮助我们组织代码&#xff0c;还能通过隐藏实现细节来提高代码的可读性和安全性。Python 提供了多种机制来控制模块的可见性&#xff0c;其中 __all__ 是一个非常重要但常被忽视的特性。本文将…...

虚幻基础07:蓝图接口

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录 作用原理事件函数 作用 实现对象间的通知。 A 通知 B 做什么。 原理 将接口抽象为蓝图&#xff0c;使得任意蓝图都能直接访问。 只需要再传入对象地址&#xff0c;就能执行对象的功能。 事件 黄色&#xff1a;…...

数据结构---哈希表

基本概念 哈希函数&#xff08;Hash Function&#xff09;是一种将输入的数据&#xff08;通常是任意大小的&#xff09;映射到固定大小的输出&#xff08;通常是一个固定长度的值&#xff09;的函数。这个输出值通常称为“哈希值”&#xff08;Hash Value&#xff09;或“哈希…...

DataWhale组队学习 leetCode task4

1. 滑动窗口算法介绍 想象你正在用一台望远镜观察一片星空。望远镜的镜头大小是固定的&#xff0c;你可以通过滑动镜头来观察不同的星区。滑动窗口算法就像这台望远镜&#xff0c;它通过一个固定或可变大小的“窗口”来观察数组或字符串中的连续区间。 滑动操作&#xff1a;就像…...

【ESP32】ESP-IDF开发 | WiFi开发 | UDP用户数据报协议 + UDP客户端和服务器例程

1. 简介 UDP协议&#xff08;User Datagram Protocol&#xff09;&#xff0c;全称用户数据报协议&#xff0c;它是一种面向非连接的协议&#xff0c;面向非连接指的是在正式通信前不必与对方先建立连接&#xff0c; 不管对方状态就直接发送。至于对方是否可以接收到这些数据内…...

【PyQt5】数据库连接失败: Driver not loaded Driver not loaded

报错内容如下&#xff1a; 可以看到目前所支持的数据库驱动仅有[‘QSQLITE’, ‘QMARIADB’, ‘QODBC’, ‘QODBC3’, ‘QPSQL’, ‘QPSQL7’] 我在网上查找半天解决方法未果&#xff0c;其中有一篇看评论反馈是可以使用的&#xff0c;但是PyQt5的版本有点低&#xff0c;5.12…...

Unity游戏(Assault空对地打击)开发(1) 创建项目和选择插件

目录 前言 创建项目 插件导入 地形插件 前言 这是游戏开发第一篇&#xff0c;进行开发准备。 创作不易&#xff0c;欢迎支持。 我的编辑器布局是【Tall】&#xff0c;建议调整为该布局&#xff0c;如下。 创建项目 首先创建一个项目&#xff0c;过程略&#xff0c;名字请勿…...

Rust:如何动态调用字符串定义的 Rhai 函数?

在 Rust 中使用 Rhai 脚本引擎时&#xff0c;你可以动态地调用传入的字符串表示的 Rhai 函数。Rhai 是一个嵌入式脚本语言&#xff0c;专为嵌入到 Rust 应用中而设计。以下是一个基本示例&#xff0c;展示了如何在 Rust 中调用用字符串传入的 Rhai 函数。 首先&#xff0c;确保…...

A星算法两元障碍物矩阵转化为rrt算法四元障碍物矩阵

对于a星算法obstacle所表示的障碍物障碍物信息&#xff0c;每行表示一个障碍物的坐标&#xff0c;例如2 , 3; % 第一个障碍物在第二行第三列&#xff0c;也就是边长为1的正方形障碍物右上角横坐标是2&#xff0c;纵坐标为3&#xff0c;障碍物的宽度和高度始终为1.在rrt路径规划…...

【C++】设计模式详解:单例模式

文章目录 Ⅰ. 设计一个类&#xff0c;不允许被拷贝Ⅱ. 请设计一个类&#xff0c;只能在堆上创建对象Ⅲ. 请设计一个类&#xff0c;只能在栈上创建对象Ⅳ. 请设计一个类&#xff0c;不能被继承Ⅴ. 请设计一个类&#xff0c;只能创建一个对象&#xff08;单例模式&#xff09;&am…...

单细胞分析基础-第一节 数据质控、降维聚类

scRNA_pipeline\1.Seurat 生物技能树 可进官网查询 添加链接描述 分析流程 准备:R包安装 options("repos"="https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packages("BiocManager",update = F,ask =…...

多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude

多项日常使用测试&#xff0c;带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude 注&#xff1a;因为考虑到绝大部分人的使用&#xff0c;我这里所用的模型均为免费模型。官方可访问的。ChatGPT这里用的是4o Ai对话&#xff0c;编程一直以来都是人们所讨论的话题。Ai的出现…...

每日一题-判断是否是平衡二叉树

判断是否是平衡二叉树 题目描述数据范围题解解题思路递归算法代码实现代码解析时间和空间复杂度分析示例示例 1示例 2 总结 ) 题目描述 输入一棵节点数为 n 的二叉树&#xff0c;判断该二叉树是否是平衡二叉树。平衡二叉树定义为&#xff1a; 它是一棵空树。或者它的左右子树…...

FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验

文章目录 FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验概述笔记my_fltk_test.cppfltk_test.hfltk_test.cxx用adjuster工程试了一下&#xff0c;好使。END FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验 概述 用fluid搭建UI…...

《多阶段渐进式图像修复》学习笔记

paper&#xff1a;2102.02808 GitHub&#xff1a;swz30/MPRNet: [CVPR 2021] Multi-Stage Progressive Image Restoration. SOTA results for Image deblurring, deraining, and denoising. 目录 摘要 1、介绍 2、相关工作 2.1 单阶段方法 2.2 多阶段方法 2.3 注意力机…...

AWScurl笔记

摘要 AWScurl是一款专为与AWS服务交互设计的命令行工具&#xff0c;它模拟了curl的功能并添加了AWS签名版本4的支持。这一特性使得用户能够安全有效地执行带有AWS签名的请求&#xff0c;极大地提升了与AWS服务交互时的安全性和有效性。 GitHub - okigan/awscurl: curl-like acc…...

QT使用eigen

QT使用eigen 1. 下载eigen https://eigen.tuxfamily.org/index.php?titleMain_Page#Download 下载后解压 2. QT引入eigen eigen源码好像只有头文件&#xff0c;因此只需要引入头文件就好了 qt新建项目后。修改pro文件. INCLUDEPATH E:\222078\qt\eigen-3.4.0\eigen-3.…...

揭示Baklib企业内容管理系统CMS的核心功能与应用价值

内容概要 企业内容管理系统&#xff08;CMS&#xff09;是指通过一系列工具和技术&#xff0c;帮助企业高效地创建、存储、管理和分发数字内容的系统。这些系统在现代企业运作中发挥着至关重要的作用&#xff0c;尤其是在信息量大、业务流程复杂的环境中。Baklib作为一个突出的…...

如何跨互联网adb连接到远程手机-蓝牙电话集中维护

如何跨互联网adb连接到远程手机-蓝牙电话集中维护 --ADB连接专题 一、前言 随便找一个手机&#xff0c;安装一个App并简单设置一下&#xff0c;就可以跨互联网的ADB连接到这个手机&#xff0c;从而远程操控这个手机做各种操作。你敢相信吗&#xff1f;而这正是本篇想要描述的…...

flume和kafka整合 flume和kafka为什么一起用?

‌Flume和Kafka一起使用的主要原因是为了实现高效、可靠的数据采集和实时处理。‌‌12 实时流式日志处理的需求 Flume和Kafka结合使用的主要目的是为了完成实时流式的日志处理。Flume负责数据的采集和传输,而Kafka则作为消息缓存队列,能够有效地缓冲数据,防止数据堆积或丢…...

java.util.Random类(详细案例拆解)(已完结)

前言&#xff1a; 小编打算近期更俩三期类的专栏&#xff0c;一些常用的专集类&#xff0c;给大家分好类别总结和详细的代码举例解释。 今天是除夕&#xff0c;小编先祝贺大家除夕快乐啦&#xff01;&#xff01; 今天是第六个 java.lang.Math 包中的 java.util.Random类 我…...

Java后端之AOP

AOP&#xff1a;面向切面编程&#xff0c;本质是面向特定方法编程 引入依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>示例&#xff1a;记录…...

【信息系统项目管理师-选择真题】2008上半年综合知识答案和详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7~8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16~20题】【第21题】【第22题】【第23题】【第24题】【第25题…...

go到底是什么意思:对go的猜测或断言

go这个单词&#xff0c;简单地讲&#xff0c;表示“走或去”的意思&#xff1a; go v.去&#xff1b;走 认真想想&#xff0c;go是一个非常神秘的单词&#xff0c;g-和o-这两个字母&#xff0c;为什么就会表达“去&#xff1b;走”的意思呢&#xff1f;它的字面义或本质&…...

零刻SER7接口及配置跑分

今天入手了一台迷你机-零刻SER7 &#xff0c;不得不说这机身是真的小啊&#xff0c;相比于传统台式机&#xff0c;它几乎不占空间&#xff0c;可以轻松放置在桌面、电视柜甚至背包中&#xff0c;非常适合需要频繁移动或空间有限的用户。尽管体积小巧&#xff0c;但零刻SER7在性…...

【Java基础-41.5】深入解析Java异常链:构建清晰的错误追踪体系

在Java编程中&#xff0c;异常处理是保证程序健壮性和可维护性的重要部分。然而&#xff0c;在实际开发中&#xff0c;异常往往不是孤立发生的&#xff0c;而是由一系列相关的异常引发的。为了更好地理解和处理这种复杂的异常场景&#xff0c;Java引入了 异常链&#xff08;Exc…...

【Python实现机器遗忘算法】复现2023年TNNLS期刊算法UNSIR

【Python实现机器遗忘算法】复现2023年TNNLS期刊算法UNSIR 1 算法原理 Tarun A K, Chundawat V S, Mandal M, et al. Fast yet effective machine unlearning[J]. IEEE Transactions on Neural Networks and Learning Systems, 2023. 本文提出了一种名为 UNSIR&#xff08;Un…...

Object类(3)

大家好&#xff0c;今天继续给大家介绍一下object类中的方法&#xff0c;那么话不多说&#xff0c;来看。 hashcode()这个方法,帮我们算了一个具体的对象位置,这里面涉及到数据结构,简单认为它是个内存地址,然后调用Integer.toHexString ()将这个地址以16进制输出。 该方法是一…...