c++概念—内存管理

news2025/4/12 8:41:50

文章目录

  • c++内存管理
    • c/c++的内存区域划分
    • 回顾c语言动态内存管理
    • c++动态内存管理
      • new和delete的使用
      • new和delete的底层逻辑
        • operator new函数和operator delete函数
        • new和delete的实现
        • 操作方式不匹配的情况
        • 定位new
      • new/delete和malloc/free的区别

c++内存管理

在以往学习c语言的过程中,我们就已经学过对堆上的内存空间进行管理,就是使用malloc、calloc、realloc函数开辟空间,使用free函数进行释放空间。

当然c++作为对c语言的升级语言,是兼容c的用法的。但是在c++中使用这几个函数还是有一些缺陷的,所以c++引入了新的方式进行管理内存。具体的将会在后面进行讲解。

c/c++的内存区域划分

我们经常说局部变量存储在栈区,函数调用要开辟函数栈帧,说明内存区域有个地方是栈区。而我们经常使用的动态内存管理函数管理的是堆上的内容,其实堆也是内存空间中的一个分区。现在让我们来看看c/c++的内存区域划分:
在这里插入图片描述
大致是分为这么五个空间。内核空间是系统使用的,我们写的代码是无法访问这个区域的。
栈区是向下增长的,内部会存储一些局部变量或者函数栈帧。
堆区向上增长,这个部分的内存是可以让程序员自行分配和释放的。
数据段存放的是全局变量和静态变量,代码段存储的是常量。

正常来说,我们写的代码系统会自动处理到对应的区域,而堆区不一样。堆区是专门留给程序员自行分配的一块空间。我们所谓的动态内存管理,其实就是管理堆区的内存。其他地方我们是无法访问或者直接使用的。

为了更好理解以上内容,下面举一个例题加深理解:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}

选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____

char2在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?____

第一个部分是很简单的,直接看图回答就可以了:
globalVar为全局变量,staticGlobalVarstaticVar为静态变量,存储在数据段
localVar是开辟在main函数的局部变量,存储在栈区
num1单独出现是数组首元素的地址,这个数组也是main函数的临时变量,所以也在栈区

第二个部分会比较难一点:
char2本质是个定义在main函数中的数组,在栈区
*char2很多人会误以为是”abcd“这个常量字符串的首元素,其实并不然,因为在main函数中开辟数组char2,将常量字符串复制给char2而已。所以*char2其实是被复制的那个的首元素,main函数里面那个,所以是在栈区

pchar3本质上也是main函数中定义的一个指针变量,所以存储在栈区
const char* pChar3 = "abcd";这种写法是把常量字符串的地址(首元素的地址)给pchar3,所以对pchar3的解引用*pChar3是在常量区的那个"abcd"的首元素,所以*pchar3数据段

ptr1仍然是开辟在栈区上的一个指针变量,存储在栈区
但是我们知道,ptr1指向的空间其实是在堆区上开辟的,也就是说,内部存储的数据是存放在堆区的,所以对ptr1解引用*ptr1是在堆区

可以再画个图梳理一下:
在这里插入图片描述

回顾c语言动态内存管理

由于这部分内容已经讲过,所以就简单带过一下。
c语言中提供了三个函数用于开辟堆区内存:malloccallocrealloc

三者的区别就是,malloc是一次性开辟内存空间,但不做初始化。calloc相比于malloc就是多了一个对内存中所有内容初始化为0的操作。而realloc是可以动态扩容的。

而c语言对于堆区内存释放是使用free函数的,应当养成良好习惯,用完内存如果后续不使用了就及时释放,以防内存泄漏。

对于更具体的内容可以参考另一篇文章:c语言之动态内存管理

c++动态内存管理

这个部分开始,将重点介绍c++中是如何对堆区的内存进行动态管理的。

new和delete的使用

c++中提供了两个操作符,即newdelete。很明显,看意思就能知道:
new是用来开辟内存的。delete是释放内存的。

先来看new的用法:

int main() {
	//动态内存申请一个int的空间
	int* ptr1 = new int;

	//动态内存申请5个int空间
	int* ptr2 = new int[5];
	return 0;
}

变量声名的时候还是一样的,对于后面的用法,就是new + 数据类型。如果要开辟多个,就需要加入方括号。就很像开辟一个数组。

当然c++是支持再开辟内存的时候进行初始化的:

int main() {
	//动态内存申请一个int的空间
	int* ptr1 = new int;
	//开辟一个int空间初始化为8
	int* ptr3 = new int(8); 
	//动态内存申请5个int空间
	int* ptr2 = new int[5];
	//开辟五个int空间进行初始化
	int* ptr4 = new int[5] {1, 2, 3, 4, 5};
	return 0;
}

对于多个空间的初始化,其实和数组的行为是一致的:
若不进行初始化,则是随机值。若进行初始化,必须从前往后依次初始化,没有初始化的位置会默认为0。其实就是和数组类似。使用是十分简单的。

当然new可开辟的不只是内置数据类型,也可以是自定义类型,所以试试对类开辟内存:

class A {
public:
	A(int a = 0, int b = 0,int c = 0) {
		cout << "A()" << endl;//可以证明调用了默认构造函数
		_a = a;
		_b = b;
		_ptr = new int(c);
	}
private:
	int _a;
	int _b;
	int* _ptr;
};

int main() {
	A* PtrA = new A[4]{ (1,2,3),(1,2),(1) };
	return 0;
}

我们来看一下对于这个A类的开辟是怎么样的:
在这里插入图片描述
从结果来看,我们发现开辟四个类对象的空间,调用了四次A类的默认构造函数。也就是说对于自定义类型,使用new操作符是会自行调用其默认构造函数。那么也就是说,我们再开辟空间的同时就可以完成类的初始化工作了。

我们来看看我们传入的参数是否奏效:
在这里插入图片描述
我们想要像这样一次性的赋值,发现是失败的。因为这是一个逗号表达式,取最后的值进行赋值。也就是PrtA[i] = int类型值,同时也发生了隐式类型转换。

所以要一次性传入多个值,就需要按照上一篇文章再类型转换中的写法:将所有的参数放在以恶搞大括号内进行传参:
在这里插入图片描述
这样做就成功了。这是需要特别注意的。

讲完了new的用法,我们再来看看delete的用法:
delete就是对开辟的内存空间进行释放:

int main() {
	int* p1 = new int(5);
	int* p2 = new int[5] {0};
	delete p1;
	delete[] p2;  
	return 0;
}

用法就是delete后面直接跟要释放的空间的起始地址。但是需要注意的是:如果删除的是多个空间,那就需要使用delete[]进行匹配,否则会出现问题。这个我们等下会将,先记住用法即可。

那对于自定义类型呢:

class A {
public:
	A(int a = 0, int b = 0, int c = 0) {
		cout << "A()" << endl;//可以证明调用了默认构造函数
		_a = a;
		_b = b;
		_ptr = new int(c);
	}
	~A() {
		cout << "~A()" << endl;//可以证明调用了析构函数
		_a = _b = 0;
		delete _ptr;
	}
private:
	int _a;
	int _b;
	int* _ptr;
};

在这里插入图片描述
从结论来看,对自定义类型使用delete的时候会自动调用其析构函数。

我们进入调试看看:
在这里插入图片描述
我们可以看到很明显是成功执行了对析构函数的调用的。

所以new相比于malloc等函数是更加有优势的。因为c语言中学习的那几个函数只能一次性开辟空间,或者初始化的内容为0,有时候可能不太满足要求。特别是对自定义类型数据,而使用new则解决了这个问题。

delete再释放内存的时候还会自动调用自定义类型的析构函数,如果直接使用<kbd<free函数,那么像图中这种情况,类中也是有内存开辟的,但是直接使用<kbd<free只能针对于类类型的空间释放,其内部还是需要手动释放或者手动调用析构函数。所以<kbd<delete也是更加有优势。

所以以后尽量都会使用newdelete方式进行动态内存的管理。

new和delete的底层逻辑

本部分将会重点讲解newdelete的底层逻辑,理解底层逻辑是非常重要的。

operator new函数和operator delete函数

引入:
newdelete是用户进行动态内存申请和释放的操作符
operator newoperator delete才是系统提供的全局函数
new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

先看看这两个函数的源码:

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}

/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

看着很复杂,但是实际上我们开始看到了一些眼熟的内容。如在operator new函数中发现了malloc函数 、在operator delete函数中发现了free函数,也就是说,这两个函数底层逻辑其实还是走的c语言那套,只不过是进行了更高级的包装。因为刚刚说到了对于自定义的类型是要进行自行调用默认构造函数和析构函数的。

当然有的人会问了,这个都没有返回值啊,怎么样检查内存开辟是否成功呢?
这里先稍微提及一下:这两个函数对于内存开辟是否成功并不是通过返回值来检查的。

c语言中malloc那几个函数在开辟失败的时候会返回空指针,所以我们会通过返回值检查。但是c++中,如果一旦出现问题,系统会抛异常,即可以认为将一个异常的信号抛出。那我们就可以尝试去捕获,然后调用对应函数就可以知道出现什么问题了:

int main() {
	int count = 0;
	try
	{
		int** ptr = new int* [100000];
		int i = 0;
		while (1) {
			ptr[i] = new int[100000];
			count++;
			cout << count << endl;
		}
	}
	catch (const std::exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

这里我们来试探一下,在x64平台下,每次开辟100000个int类型数据的空间能开辟多少次:
在这里插入图片描述
这里我们就是使用了抛异常和捕获异常的方式来判断内存是否开辟成功。当连续开辟这么多个字节到61048次的时候,抛出了异常。我们捕获,使用其内部函数what进行查看是什么问题。这个bad allocation就是内存不足了。

但是我们发现可以开辟那么多空间,我们平常写的练习或者刷题目是很难达到这个内存不足的要求的。所以我们当前情况下可以先只进行了解抛异常机制。平常练习可以先不用对内存开辟进行检查,因为基本上都是成功的。

new和delete的实现

对于内置类型:
如果申请的是内置类型的空间,newmallocdeletefree基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]delete[]申请的是连续空间,而且new在申
请空间失败时会抛异常,malloc会返回NULL。区别就在于检查机制。

对于自定义类型:
new的原理
1.调用operator new函数申请空间
2.在申请的空间上执行构造函数,完成对象的构造

delete的原理
1.在空间上执行析构函数,完成对象中资源的清理工作
2.调用operator delete函数释放对象的空间

new T[N]的原理
1.调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
象空间的申请
2.在申请的空间上执行N次构造函数

delete[]的原理
1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
放空间

对于开辟一个空间或者释放一个空间的情况其实是很简单的,只不过在开辟空间的时候会调用其默认构造函数,释放的时候会调用其析构函数。

对于自定义类型,如果是开辟多个空间,其实就是对应使用operator new[]operator delete[]这两个全局函数。只不过就是多调用了几次的默认构造函数和析构函数罢了,不算复杂,真正到底层实现也是会调用多次的freemalloc

而对于开辟多个空间和释放多个空间就需要特别注意了。特别是对于new T[]这个操作,如果自定义类中写了析构函数,系统会多分配四个字节的空间放在这个连续空间的头部,记录的是这一串空间的元素个数。这个等下我们在操作方式不批撇的情况下还会细讲。

操作方式不匹配的情况

先说结论:
进入了c++板块后,我们基本上都是使用newdelete进行动态的内存管理的。而且要做到使用匹配。这样子是不会出现问题的。

现在来看操作不匹配的情况下:
我们前面说到:newdelete的底层最终都是会调用mallocfree,我们现在来看一下下面几种情况:

int main() {
	int* ptr = new int[5] {1, 2, 3, 4, 5};
	free(ptr);
	return 0;
}

我们直接使用free对开辟的空间进行释放,请问会报错或者导致内存泄露吗?
我们来看看结果:
在这里插入图片描述
很明显,这是没有任何问题的。所以对于内置类型的数据,直接使用free也是可以的。因为delete的底层也就是调用了free,只不过需要有一些检查。

那我们再来看看如果是自定义类型数据呢?
在这里插入图片描述
发现好像也挺正常的,但是我们得想到一个问题,如果类中的某个成员变量是指向一块资源的,那就很麻烦了。因为我们调用free只是对开辟的三个B类对象的空间进行释放了,但是其内部还是有资源的话,那么指向的那块资源是无法被释放的。这就导致内存泄漏。

所以一定要注意配套使用,这样子就很难出现问题了。

当然,newdelete也需要注意对应的配套:
newdelete是配对的,new[]delete[]是配对的。

我们来看下面一段代码的运行情况:

class B {
public:
	B(int a = 0, int b = 0) {
		cout << "B" << endl;
		_a = a;
		_b = b;
	}
private:
	int _a;
	int _b;
};

int main() {
	B* p = new B[3]{ {1,2,3},{1,2},{1} };
	delete p;
	return 0;
}

在这里插入图片描述
这是可以正常运行的,是不是说可以不用匹配使用呢?

答案没有那么简单,我们再来看下面这个代码:

class A {
public:
	A(int a = 0, int b = 0) {
		cout << "A" << endl;
		_a = a;
		_b = b;
	}
	~A() {
		cout << "~A" << endl;
		_a = _b = 0;
	}
private:
	int _a;
	int _b;
};
int main() {
	A* p = new A[3]{ {1,2},{1} };
	delete p;
	return 0;
}

我们来看看运行起来是什么效果:
在这里插入图片描述
程序竟然崩溃了,这是为什么呢?

这就要说到在上一个点讲到的:
对于自定义类型数据,如果类中写了析构函数,那么在开辟多个空间的时候,系统会多开四个字节在头部存储空间的个数。

我们可以进入监视窗口看看:
在这里插入图片描述
x86平台下,系统会多开4个字节。但是我们使用delete的操作的话,如果没有析构函数,那么编译器会优化,直接就不开那么多空间。

注意x64平台下会开8个字节的空间,但是原理和x86平台一样,所以以x86平台下进行举例。
在这里插入图片描述
而使用delete即编译器会在第一个元素开始的位置进行释放。delete[]会在所有空间(包括记录数据个数的那个空间)的开始位置开始释放。

然而因为堆区上的内存是不能只释放其中一部分的。所以编译器就会报错了。

所以我们最好的做法就是进行匹配使用,这样子就不会出什么问题了

定位new

定位new也称为replacement_new,主要还是配合内存池来使用。由于当前还没有相应的知识,所以当前先只做一些了解即可。

内存池是池化技术的一种,即有时候对于某些情况下,某个功能可能需要多次的申请访问内存和释放内存,就需要一直向堆来申请。这是十分麻烦的,效率可能不太高。

所以就专门想出一个办法,从堆中专门划分一块内存空间给某功能进行使用。如果不够了可以再增加。这就有点像一个池子,从中取内存和释放内存。

那这就需要使用operator new这个函数了,对专门的内存池进行开辟内存。
其用法其实和malloc函数是类似的:

int main(){
 // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
   A* p1 = (A*)malloc(sizeof(A));
   new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
   p1->~A();
   free(p1);
   
   A* p2 = (A*)operator new(sizeof(A));
   new(p2)A(10);
   p2->~A();
   operator delete(p2);
   return 0;
}

operator new是没有办法像new操作符那样直接调用类的默认构造函数和析构函数的,只是可以专门的对某块位置开辟内存空间。

还需要注意一下使用operator new函数后如何对指向的空间进行初始化。
格式:new(指向空间的指针)类名
如果类中构造函数有参数的话,传参就在后面再加入一个括号输入参数
格式为:new(指向空间的指针)类名(参数1,参数2…)

当然,当前先只做了解即可。本篇文章主要是对内存管理的基本用法和注意点进行讲解。

new/delete和malloc/free的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
    要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2330368.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无人机双频技术及底层应用分析!

一、双频技术的核心要点 1. 频段特性互补 2.4GHz&#xff1a;穿透力强、传输距离远&#xff08;可达5公里以上&#xff09;&#xff0c;适合复杂环境&#xff08;如城市、建筑物密集区&#xff09;&#xff0c;但易受Wi-Fi、蓝牙等设备的干扰。 5.8GHz&#xff1a;带宽更…

【电视软件】小飞电视v2.7.0 TV版-清爽无广告秒换台【永久更新】

软件介绍 小飞电视是一款电视端的直播软件&#xff0c;无需二次付费和登录&#xff0c;资源丰富&#xff0c;高清流畅。具备开机自启、推送功能、自定义直播源、个性化设置及节目预告等实用功能&#xff0c;为用户带来良好的观看体验。基于mytv开源项目二改&#xff0c;涵盖央…

Valgrind——内存调试和性能分析工具

文章目录 一、Valgrind 介绍二、Valgrind 功能和使用1. 主要功能2. 基本用法2.1 常用选项2.2 内存泄漏检测2.3 详细报告2.4 性能分析2.5 多线程错误检测 三、在 Ubuntu 上安装 Valgrind四、示例1. 检测内存泄漏2. 使用未初始化的内存3. 内存读写越界4. 综合错误 五、工具集1. M…

学习MySQL第七天

夕阳无限好 只是近黄昏 一、子查询 1.1 定义 将一个查询语句嵌套到另一个查询语句内部的查询 我们通过具体示例来进行演示&#xff0c;这一篇博客更侧重于通过具体的小问题来引导大家独立思考&#xff0c;然后熟悉子查询相关的知识点 1.2 问题1 谁的工资比Tom高 方…

Spring启示录、概述、入门程序以及Spring对IoC的实现

一、Spring启示录 阅读以下代码&#xff1a; dao package org.example1.dao;/*** 持久层* className UserDao* since 1.0**/ public interface UserDao {/*** 根据id删除用户信息*/void deleteById(); } package org.example1.dao.impl;import org.example1.dao.UserDao;/**…

电机的了解到调试全方面讲解

一、什么是电机 电机是一种将电能转换为机械能的装置,通常由定子、转子和电磁场组成。 当电流通过电机的绕组时,产生的磁场会与电机中的磁场相互作用,从而使电机产生旋转运动。电机广泛应用于各种机械设备和工业生产中,是现代社会不可或缺的重要设备之一。 常见的电机种…

笔试专题(七)

文章目录 乒乓球筐&#xff08;哈希&#xff09;题解代码 组队竞赛题解代码 删除相邻数字的最大分数&#xff08;线性dp&#xff09;题解代码 乒乓球筐&#xff08;哈希&#xff09; 题目链接 题解 1. 两个哈希表 先统计第一个字符串中的字符个数&#xff0c;再统计第二个字…

【嵌入式学习3】UDP发送端、接收端

目录 1、发送端 2、接收端 3、UDP广播 1、发送端 from socket import *udp_socket socket(AF_INET,SOCK_DGRAM) udp_socket.bind(("127.0.0.1",3333))data_str "UDP发送端数据" data_bytes data_str.encode("utf-8") udp_socket.sendto(d…

Linux 系统 SVN 源码安装与配置全流程指南

Linux系统SVN源码安装与配置全流程指南 一、环境准备 系统要求 CentOS 7及以上版本需安装GCC编译工具链 依赖项 APR/APR-UTIL&#xff08;Apache可移植运行库&#xff09;SQLite&#xff08;嵌入式数据库&#xff09;zlib&#xff08;数据压缩库&#xff09; 二、下载及安装…

Redis 的五种数据类型面试回答

这里简单介绍一下面试回答、我之前有详细的去学习、但是一直都觉得太多内容了、太深入了 然后面试的时候不知道从哪里讲起、于是我写了这篇CSDN帮助大家面试回答、具体的深入解析下次再说 面试官你好 我来介绍一下Redis的五种基本数据类型 有String List Set ZSet Map 五种基…

关于类模板STL中vector容器的运用和智能指针的实现

代码题&#xff1a;使用vector实现一个简单的本地注册登录系统 注册&#xff1a;将账号密码存入vector里面&#xff0c;注意防重复判断 登录&#xff1a;判断登录的账号密码是否正确 #include <iostream> #include <cstring> #include <cstdlib> #in…

Opencv计算机视觉编程攻略-第十一节 三维重建

此处重点讨论在特定条件下&#xff0c;重建场景的三维结构和相机的三维姿态的一些应用实现。下面是完整投影公式最通用的表示方式。 在上述公式中&#xff0c;可以了解到&#xff0c;真实物体转为平面之后&#xff0c;s系数丢失了&#xff0c;因而无法会的三维坐标&#xff0c;…

git修改已经push的commit的message

1.修改信息 2.修改message 3.强推

2026考研数学张宇武忠祥复习视频课,高数基础班+讲义PDF

2026考研数学武忠祥老师课&#xff08;网盘&#xff09;&#xff1a;点击下方链接 2026考研数学武忠祥网课&#xff08;最新网盘&#xff09; 一、基础阶段&#xff08;3-5个月&#xff09; 目标&#xff1a;搭建知识框架掌握基础题型 教材使用&#xff1a; 高数&#xff1a;…

C++使用Qt Charts可视化大规模点集

引言 数据可视化是数据分析和决策过程中的重要环节。随着数据量的不断增长&#xff0c;如何高效地可视化大规模数据集成为了一个挑战。Qt Charts 提供了一个强大的工具集&#xff0c;用于创建直观的数据可视化图表。本文将探讨如何使用 C 和 Qt Charts 可视化大规模点集&#…

质检LIMS系统在生态修复企业的实践 生态修复行业的质量管控难题

一、生态修复行业的质量管控新命题 在生态文明建设的大背景下&#xff0c;生态修复企业面临着复杂的环境治理挑战。土壤改良、水体净化、植被恢复等工程&#xff0c;均需以精准的实验数据支撑决策。传统实验室管理模式存在数据孤岛、流程非标、合规风险高等痛点&#xff0c;而…

Spring Cloud之服务入口Gateway之Route Predicate Factories

目录 Route Predicate Factories Predicate 实现Predicate接口 测试运行 Predicate的其它实现方法 匿名内部类 lambda表达式 Predicate的其它方法 源码详解 代码示例 Route Predicate Factories The After Route Predicate Factory The Before Route Predicate Fac…

《AI大模型应知应会100篇》第6篇:预训练与微调:大模型的两阶段学习方式

第6篇&#xff1a;预训练与微调&#xff1a;大模型的两阶段学习方式 摘要 近年来&#xff0c;深度学习领域的一个重要范式转变是“预训练-微调”&#xff08;Pretrain-Finetune&#xff09;的学习方式。这种两阶段方法不仅显著提升了模型性能&#xff0c;还降低了特定任务对大…

java后端对时间进行格式处理

时间格式处理 通过java后端&#xff0c;使用jackson库的注解JsonFormat(pattern "yyyy-MM-dd HH:mm:ss")进行格式化 package com.weiyu.pojo;import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import …

汽车BMS技术分享及其HIL测试方案

一、BMS技术简介 在全球碳中和目标的战略驱动下&#xff0c;新能源汽车产业正以指数级速度重塑交通出行格局。动力电池作为电动汽车的"心脏"&#xff0c;其性能与安全性不仅直接决定了车辆的续航里程、使用寿命等关键指标&#xff0c;更深刻影响着消费者对电动汽车的…