第三讲 | C/C++内存管理完全手册

news2025/3/25 22:17:41

C/C++内存管理

  • 一、 C/C++内存分布
  • 二、 C语言中动态内存管理方式:malloc/calloc/realloc/free
  • 三、 C++内存管理方式
    • 1. new/delete操作内置类型
    • 2. new和delete操作自定义类型
  • 四、operator new和operator delete函数(重点)
  • 五、new和delete的实现原理
    • 内置类型
    • 自定义类型
  • 六、定位new表达式(placement-new) (了解)
  • 七、malloc/free和new/delete的区别

一、 C/C++内存分布

如图,从C/C++语言角度对内存区域进行划分;若从操作系统角度对内存区域进行划分,会将常量区叫做代码段,会将静态区叫做数据段:

在这里插入图片描述

指针:内存地址按字节为单位的编号。
空指针是有效的地址,是第0个字节的地址,系统默认会把这块地址省去,不用这块地址,所以不能访问空指针(不能对空指针解引用)。

C/C++内存管理认知:需要明确定义的不同变量分别存储在内存的哪个区域。

我们来看下面的一段代码和相关问题:

在这里插入图片描述

在这里插入图片描述

  1. 全局变量、全局/局部静态变量在静态区(数据段)。

数组名含义:

  1. 表示整个数组:sizeof(数组名)、数组名单独存在
  2. 首元素的地址:数组名进行运算,例如,解引用运算*char2表示首元素

num1、char2都表示局部数组,函数栈帧销毁了,它们也跟着销毁。常量字符串在常量区,常量字符串(5个字节)的内容拷贝给char2,函数栈帧销毁与常量字符串无关,函数栈帧销毁后常量字符串还在常量区。在常量区的都不能修改。

  1. 局部变量、局部数组都在函数栈帧中,即在栈上。

  2. const修饰的变量在栈上。但是pChar3所在的一行不涉及const,因为这里const修饰的是*pChar3,与pChar3无关,pChar3与ptr1都是大小为4个字节的局部指针变量,在栈上。pChar3中存储常量字符串的地址,pChar3指向常量字符串,*pChar3 == 'a',字符’a’在常量区。ptr1存储动态开辟空间的起始地址,动态开辟在堆上,即*ptr1在堆上。

  3. 加一题:这里b是常变量,是可以修改b的值的,取b的地址&b,&b是const int* 类型,强制类型转换成int*后解引用修改。打印出a、b的地址,发现是挨着的,也就说明const修饰的变量是在栈上,即b在栈上。

为什么输出结果是挨着得呢?
因为栈区就是从高地址到低地址分配的,一般内存有空间的话,就是按照顺序分配。

#include <iostream>
using namespace std;
int main()
{
	int a = 1;
	const int b = 0;
	cout << &a << endl;
	cout << &b << endl;
	return 0;
}

在这里插入图片描述


栈:向下增长。在栈中定义的变量,后定义的地址会越来越小;后调用的函数地址也是会越来越小。

堆:向上增长。后malloc()出来的地址会比先malloc()出来的地址大。

整个内存中栈、堆需要的空间最大,所以它们统一向中间生长,实际上堆的空间最大,因为动态开辟要预留空间。栈一般不需要太多空间,Linux下一个栈帧默认只开8M(800万Byte),超过就会栈溢出。

1MB = 1024KB = 1024 * 1024 Byte = 10^6Byte

重点学习堆,因为堆是需要手动管理的,而其他内存区域是自动管理的。

在这里插入图片描述

【说明】

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux没学到这块,现在只需要了解一下)
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量。

二、 C语言中动态内存管理方式:malloc/calloc/realloc/free

void Test()
{
	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
	// 这里需要free(p2)吗?
	free(p3);
}

其实realloc()并不是那么的高效,扩容的时候有原地扩容和异地扩容,上面的例子中从16B扩容到40B,若原来空间之后有足够的24B空间,那么就可以原地扩容,此时,p2、p3是一样的,free(p3)就相当于把p2指向的空间也给释放了,所以不用再释放一次,但是p2这时是个野指针;若是原来空间之后没有足够的24B空间(分配给别人了),那么就异地扩容,p3指向新的空间,用完肯定要手动释放这块空间,p2指向的原来的空间会自动释放给操作系统,所以不用手动free()释放,不过这时p2是个野指针。
在这里插入图片描述

【面试题】

  1. malloc/calloc/realloc的区别? https://blog.csdn.net/Future_yzx/article/details/145299472?spm=1001.2014.3001.5506
  2. malloc的实现原理? glibc中malloc实现原理 : https://www.bilibili.com/video/BV117411w7o2/?spm_id_from=333.788.videocard.0&vd_source=dc7a926badd09458939fa8246d82a9e3

三、 C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

C、C++内存管理方式区别:

  1. malloc()、free()等是库里的函数
  2. new、delete关键字是操作符

1. new/delete操作内置类型

new默认不会初始化,与malloc()没什么区别。但是语法上new支持初始化,有初始化的方式:

申请和释放单个元素的空间,使用new和delete操作符,初始化用();申请和释放连续的空间,使用new[]和delete[],初始化用{}。注意要匹配起来使用。

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);
	// 动态申请3个int类型的空间
	int* ptr6 = new int[3];
	// 动态申请10个int类型的空间并初始化
	int* ptr7 = new int[10] { 1, 2, 3, 4 };//与数组一样,后面的6个空间默认初始化为0
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
	delete[] ptr7;
}

在这里插入图片描述

2. new和delete操作自定义类型

new/delete 和 malloc/free在申请内置类型的空间时作用几乎是一样的。

C中malloc()、free()对自定义类型不适用,malloc()出来的对类对象中的成员变量没法初始化。

new/delete 和 malloc/free第一大区别:在申请自定义类型的空间时,如果只是new 类,new会调用默认构造(无默认构造函数会报错); 如果是 new 类(2),这就是在调用非默认构造函数;所以new 的顺序就是,1、 先开辟空间(实际上就是调用operator new函数) 2、 调用构造函数 。不是只能调用默认构造。delete除了会释放空间之外还会调用析构函数。而malloc与free不会调用函数的。

#include <iostream>
using namespace std;
class A 
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	// 第一大区别:开辟自定义类型的空间时new/delete会额外调用默认构造函数/析构函数,而malloc()/free()不会
	A* p1 = (A*)malloc(sizeof(A));
	// 没有对象,new一个。this指针就是对象的地址,即p2
	A* p2 = new A(1);
	free(p1);
	delete p2;

	// 开辟内置类型的空间时几乎是一样的
	int* p3 = (int*)malloc(sizeof(int));
	int* p4 = new int;
	free(p3);
	delete p4;

	int* p5 = (int*)malloc(10 * sizeof(int));
	int* p6 = new int[10];
	free(p5);
	delete[] p6;
	return 0;
}

在这里插入图片描述

new ListNode(1)相当于之前学习初阶数据结构时buyNode()函数的功能:申请结点空间 + 初始化结点。

#include <iostream>
using namespace std;
struct ListNode
{
	int _val;
	ListNode* _next;
	ListNode(int val)
		:_val(val)
		, _next(nullptr)
	{}
};
int main()
{
	ListNode* node1 = new ListNode(1);
	ListNode* node2 = new ListNode(2);
	ListNode* node3 = new ListNode(3);
	ListNode* node4 = new ListNode(4);
	node1->_next = node2;
	node2->_next = node3;
	node3->_next = node4;

	ListNode* cur = node1;
	while (cur)
	{
		cout << cur->_val << " -> ";
		cur = cur->_next;
	}
	cout << "nullptr" << endl;
	return 0;
}

在这里插入图片描述
new/delete 和 malloc/free第二大区别:malloc()与new开辟空间失败的机制不一样及其处理错误的机制不一样,不过一般情况下不会开辟失败,导致开辟空间失败的原因就是空间不够。

malloc()与new开辟空间失败的机制及其处理机制:malloc()会返回空,解决办法就是手动检查。new失败的机制不是返回空指针,而是会抛异常(后续《异常》章节会讲到),解决办法就是在调用链的任意一层有处理就行。

#include <iostream>
using namespace std;
int main()
{
	//手动检查
	int* ptr = (int*)malloc(sizeof(int));
	if (ptr == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	return 0;
}

抛出的异常会往捕获异常的方向走,在当前函数没有捕获就会往下一层走。在当前层/外层有捕获就行,若直到main()函数里都没有捕获就会报错,最后main()函数返回值为非0,程序会异常退出。

在这里插入图片描述
32位平台下,堆总共是4G。32位下,指针大小是4个字节,编址成2^32个指针,一个指针就是一个字节的地址,2^32个指针就有2^32个字节,即2^32byte == 4G

1G = 1024MB = 1024 * 1024 KB = 1024 * 1024 * 1024 Byte = 2^30Byte
1024 = 2^10

#include <iostream>
using namespace std;
double Divide(int a, int b)
{
	
	// 当b == 0时抛出异常
	if (b == 0)
	{
		string s("Divide by zero condition!");
		throw s;
	}
	else
	{
		return ((double)a / (double)b);
	}
}
int main()
{
	size_t x = 0;
	int* ptr1 = nullptr;
	try
	{
		do {
			// ptr1 = (int*)malloc(10 * 1024 * 1024);// ()里是字节个数10MB
			// 失败了抛异常
			//ptr1 = new int[500 * 1024 * 1024];
			ptr1 = new int[10 * 1024 * 1024];// []里是对象个数
			if (ptr1)
				//x += 10 * 1024 * 1024;// malloc()的
				x += 10 * 1024 * 1024 * 4;

			cout << ptr1 << endl;
		} while (ptr1);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	cout << x << endl;
	cout << x/(1024*1024) << endl;

	//try {
	//	cout << Divide(10, 0) << endl;
	//}
	//catch (const string& s)
	//{
	//	cout << s << endl;
	//}

	return 0;
}

在这里插入图片描述

四、operator new和operator delete函数(重点)

这两个函数不是对new、delete操作符的重载,它们就是全局的库函数。

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

从汇编的角度看,运算符new、delete本质/核心机制就是分别调用两个函数:new(operator new和构造函数)、delete(析构函数和operator delete):
在这里插入图片描述

new底层为什么不直接调用malloc()呢?而是要通过operator new函数?因为malloc()失败返回的是空,而C++这种面向对象的机制里要求库里面失败了不再用返回空、返回错误码去表达错误,而是要通过异常,那么malloc()失败了返回空就不符合机制,解决办法:用operator new函数对malloc()封装一下就行,这样operator new实际上就是在通过malloc来申请空间。

通过下面两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

总结:

  1. new 的顺序就是,1、 先开辟空间(在堆上调用operator new函数,实际上就是在堆上调用malloc()函数) 2、 调用构造函数 。
  2. delete的顺序是,1、先调用析构函数(对申请资源的释放) 2、释放空间(在堆上调用operator delete函数,实际上就是在堆上调用free()函数)。
/*
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)
	//若p == 0,malloc失败,_callnewh回调机制尝试进行回调,若没有回调成功就会抛异常,抛bad_alloc
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);//是宏,相当于throw
		}
	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));
	//operator delete函数核心调用_free_dbg,最后一行宏函数,实际还是调用free()
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}
/*
free的实现
*/
//宏函数,实际还是调用free()
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

operator delete是与operator new配对的。

若先operator delete,会导致找不到堆上的空间了。正确开空间、销毁空间步骤:

在这里插入图片描述

五、new和delete的实现原理

内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
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来释放空间

在这里插入图片描述

new[]会多开4个字节,存储数据是20个字节,但是会在头上多开4个字节。多开的4个字节里面会存储对象个数。

通过p4指针往前偏移4个字节,会把这24个字节空间申请出来,把存储数值取出来,就知道了有多少个对象,再对每个对象依次调用析构函数,最后整块空间都会被释放(包括存储对象个数的空间)。但是若把析构函数屏蔽了,空间大小就是20个字节了,编译器认为A默认生成的析构函数啥事都不做,那么多开4个字节存储个数就白存储了,相当于编译器做了优化,就不存储对象个数了。

在这里插入图片描述

new delete、new[] delete[]、malloc() free()各自一定要匹配使用。没有自己写析构。没有匹配使用,但是不会内存泄漏。delete[]是知道要调用多少次析构函数,delete[]调用operator delete[],operator delete[]中调用operator delete,operator delete还是会调用free(),最终是调用free(),free()底层会记录空间有多大 。

若把析构函数注释解开,会报错。原因不是内存泄漏,而是释放的位置不对。因为写了析构,会额外存储对象个数,最后返回的是上图中p4箭头的位置,用delete,而不是用delete[]话,会从这个位置调用一次析构释放,会认为这里只有一个对象,也不会认为前面有4个字节存储对象个数,一个是析构没有调用对,还有一个是析构的位置也不对。不匹配使用是否报错是不一定的,所以,一定要匹配使用。malloc() free()不能分段申请、分段释放。

六、定位new表达式(placement-new) (了解)

定位new表达式:对指针指向的已经分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

构造函数不可以显示调用,都是自动调用的。析构函数可以显示调用。operator new和operator delete函数可以显示调用,因为是库里面的函数。

#include <iostream>
using namespace std;
class A 
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	// 等价于new
	// 申请空间
	A* p1 = (A*)operator new(sizeof(A));//相当于A* p1 = (A*)malloc(sizeof(A));
	// 构造函数不支持这样显示调用
	//p1->A(1);
	// 定位new表达式显示调用构造函数
	new(p1)A(10);

	// 等价于delete
	p1->~A();
	operator delete(p1);//相当于free(p1);
	return 0;
}

被替换成向内存池申请内存和释放内存时就会这么写。stl_list中就会有这个机制,提高数据结构申请释放内存的效率就建立出内存池的机制。内存池申请的时候就要显示调用构造和析构。

自己去显示的开空间(堆上),但是没有构造函数初始化、析构,构造借助定位new显示调用,析构显示调用

开空间在堆上开,会借助内存池的概念

申请内存、连接、线程需要很多消耗,效率低,那么怎么样才能高效呢?建立一个池子,想要的系统资源放进去,需要的话直接拿。类比妈妈在家中掌管钱财,你每天的开销都要向妈妈要钱,但是每需要开销一次钱都要向妈妈要钱就很麻烦,效率也很低,那么这时你就可以建立自己的池化技术(类比微信钱包或银行卡),妈妈就可以把你这个月的生活费打到这里了,这样每次需要钱的时候就可以向池子里拿,花钱的效率就变高了。
在这里插入图片描述

七、malloc/free和new/delete的区别

realloc()只能初始化为0,而new初始化就相对灵活。

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

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

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

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

相关文章

2021年蓝桥杯第十二届CC++大学B组真题及代码

目录 1A&#xff1a;空间&#xff08;填空5分_单位转换&#xff09; 2B&#xff1a;卡片&#xff08;填空5分_模拟&#xff09; 3C&#xff1a;直线&#xff08;填空10分_数学排序&#xff09; 4D&#xff1a;货物摆放&#xff08;填空10分_质因数&#xff09; 5E&#xf…

秒杀业务优化之从分布式锁到基于消息队列的异步秒杀

一、业务场景介绍 优惠券、门票等限时抢购常常出现在各类应用中&#xff0c;这样的业务一般为了引流宣传而降低利润&#xff0c;所以一旦出现问题将造成较大损失&#xff0c;那么在业务中就要求我们对这类型商品严格限时、限量、每位用户限一次、准确无误的创建订单&#xff0c…

纯vue手写流程组件

前言 网上有很多的vue的流程组件&#xff0c;但是本人不喜欢很多冗余的代码&#xff0c;喜欢动手敲代码&#xff1b;刚开始写的时候&#xff0c;确实没法下笔&#xff0c;最后一层一层剥离&#xff0c;总算实现了&#xff1b;大家可以参考我写的代码&#xff0c;可以拿过去定制…

WPS宏开发手册——使用、工程、模块介绍

目录 系列文章前言1、开始1.1、宏编辑器使用步骤1.2、工程1.3、工程 系列文章 使用、工程、模块介绍 JSA语法 第三篇练习练习题&#xff0c;持续更新中… 前言 如果你是开发人员&#xff0c;那么wps宏开发对你来说手拿把切。反之还挺吃力&#xff0c;需要嘻嘻&#xf…

django入门教程之request和reponse【二】

接上节&#xff1a;入门【一】 再创建一个orders子应用&#xff0c;python manager.py startapp orders&#xff0c;orders目录中新建一个urls.py文件。结构如图&#xff1a; 通过上节课&#xff0c;我们知道在views.py文件中编写函数时&#xff0c;有一个默认入参request&…

RAG优化:python从零实现[吃一堑长一智]循环反馈Feedback

本文将介绍一种有反馈循环机制的RAG系统,让当AI学会"吃一堑长一智",给传统RAG装了个"后悔"系统,让AI能记住哪些回答被用户点赞/拍砖,从此告别金鱼记忆: 每次回答都像在玩roguelike:失败结局会强化下次冒险悄悄把优质问答变成新知识卡牌,实现"以…

【Linux】VMware17 安装 Ubuntu24.04 虚拟机

目录 安装教程 一、下载 Ubuntu 桌面版iso映像 二、安装 VMware 三、安装 Ubuntu 桌面版 VMware 创建虚拟机 挂载 Ubuntu ISO 安装 Ubuntu 系统 安装教程 一、下载 Ubuntu 桌面版iso映像 链接来自 清华大学开源软件镜像站 ISO文件地址&#xff1a;ubuntu-24.04.2-des…

WPS宏开发手册——JSA语法

目录 系列文章2、JSA语法2.1、打印输出2.2、注释2.3、变量2.4、数据类型2.5、函数2.6、运算符2.7、比较2.8、if else条件语句2.9、for循环2.10、Math对象&#xff08;数字常用方法&#xff09;2.11、字符串常用方法2.12、数组常用方法 系列文章 使用、工程、模块介绍 JSA语…

word中指定页面开始添加页码

第一步&#xff1a; 插入页码 第二步&#xff1a; 把光标放到指定起始页码处 第三步&#xff1a; 取消链接到前一节 此时关掉页脚先添加分节符 添加完分节符后恢复点击 第四步&#xff1a; 设置页码格式&#xff0c;从1开始 第五步&#xff1a; 删掉不要的页码&#xff0c…

Python实现deepseek接口的调用

简介&#xff1a;DeepSeek 是一个强大的大语言模型&#xff0c;提供 API 接口供开发者调用。在 Python 中&#xff0c;可以使用 requests 或 httpx 库向 DeepSeek API 发送请求&#xff0c;实现文本生成、代码补全&#xff0c;知识问答等功能。本文将介绍如何在 Python 中调用 …

文档处理控件Aspose.Words 教程:.NET版中增强的 AI 文档摘要功能

Aspose.Words是一个功能强大的 Word 文档处理库。它可以帮助开发人员自动编辑、转换和处理文档。 自 24.11 版以来&#xff0c;Aspose.Words for .NET 提供了 AI 驱动的文档摘要功能&#xff0c;使用户能够从冗长的文本中快速提取关键见解。在 25.2 版中&#xff0c;我们通过使…

19,C++——11

目录 一、 C11简介 二、 新增的列表初始化 三、 新增的STL容器 四、 简化声明 1&#xff0c;auto 2&#xff0c;decltype 3&#xff0c;nullptr 五、右值引用 1&#xff0c;左值引用和右值引用 2&#xff0c;两种引用的比较 3&#xff0c;左值引用的使用场景 4&…

风尚云网|前端|前后端分离架构深度剖析:技术革新还是过度设计?

前后端分离架构深度剖析&#xff1a;技术革新还是过度设计&#xff1f; 作者&#xff1a;风尚云网 在数字化转型浪潮中&#xff0c;前后端分离架构已成为现代Web开发的主流模式。但这项技术真的是银弹吗&#xff1f;本文将从工程实践角度&#xff0c;剖析其优势与潜在风险&am…

CMS网站模板设计与用户定制化实战评测

内容概要 在数字化转型背景下&#xff0c;CMS平台作为企业内容管理的核心载体&#xff0c;其模板架构的灵活性与用户定制能力直接影响运营效率。通过对WordPress、Baklib等主流系统的技术解构发现&#xff0c;模块化设计理念已成为行业基准——WordPress依托超过6万款主题库实…

搭建个人博客教程(Hexo)

如何快速搭建一套本地的博客系统呢&#xff1f;这里有一套gitNode.jsHexo的部署方案来进行解决。 安装git Git 是一款免费开源的分布式版本控制系统&#xff0c;由 Linus Torvalds 于 2005 年为 Linux 内核开发设计。它通过本地仓库和远程仓库实现代码管理&#xff0c;支持分支…

Docker 可视化工具 Portainer

Docker 可视化工具 Portainer安装 官方安装地址&#xff1a;https://docs.portainer.io/start/install-ce/server/docker/wsl 一&#xff0c;首先&#xff0c;创建 Portainer Server 用来存储数据库的卷&#xff1a; docker volume create portainer_data二&#xff0c;然后…

数据库基础知识点(系列二)

1&#xff0e;关系数据模型由哪三个要素组成。 答&#xff1a;关系数据模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。 2&#xff0e;简述关系的性质。&#xff08;关系就是一张二维表格&#xff0c;但不是任何二维表都叫关系&#xff09; 答&#xff1a;(1…

如何进行灌区闸门自动化改造-闸门远程控制系统建设

改造背景 操作效率低‌&#xff1a;人工启闭耗时耗力&#xff0c;单次操作需2-3人配合&#xff0c;耗时长。 ‌水资源浪费‌&#xff1a;依赖经验估算放水量&#xff0c;易导致漫灌或供水不足。 ‌管理滞后‌&#xff1a;无法实时监控水位、流量&#xff0c;故障响应延迟。 …

【算法笔记】图论基础(二):最短路、判环、二分图

目录 最短路松弛操作Dijkstra朴素Dijkstra时间复杂度算法过程例题 堆优化Dijkstra时间按复杂度算法过程例题 bellman-ford时间复杂度为什么dijkstra不能处理负权边&#xff1f;dijkstra的三个步骤&#xff1a;反例失效的原因 算法过程例题 spfa时间复杂度算法过程例题spfa求最短…

EMS小车技术特点与优势:高效灵活的自动化输送解决方案

北成新控伺服技术丨EMS小车调试视频 EMS小车是一种基于单轨运行的电动输送系统&#xff0c;通过电力驱动实现物料的高效搬运和输送&#xff0c;具有高效灵活、节能环保、多功能集成、行业适配性强等特性&#xff0c;广泛应用于汽车制造、工程机械、家电生产、仓储物流等行业自动…