【C++修炼之路】内存管理

news2024/11/23 12:25:55

👑作者主页:@安 度 因
🏠学习社区:StackFrame
📖专栏链接:C++修炼之路

文章目录

  • 一、C/C++ 内存分布
  • 二、考题
  • 三、C语言动态内存管理方式
  • 四、C++内存管理方式
    • 1、对内置类型
    • 2、对自定义类型
  • 五、C++对动态管理的升级
  • 六、operator new/operator delete函数
  • 七、new/delete 的实现原理
    • 1、内置类型
    • 2、自定义类型
  • 八、定位new表达式(placement-new)
  • 九、内存泄漏
    • 1、内存泄漏分类
    • 2、如何检测内存泄漏
    • 3、如何避免内存泄漏
    • 4、补充

如果无聊的话,就来逛逛 我的博客栈 吧! 🌹

一、C/C++ 内存分布

划分是为了更加高效的管理

image-20230207173207635

说明

  1. 栈又叫堆栈,函数调用建立的栈帧,非静态局部变量、函数参数、返回值等,栈是向下增长的
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的;程序运行过程中按需求申请和释放空间,比如我们实现的数据结果都是在堆开空间
  4. 数据段(静态区)–存储全局数据和静态数据
  5. 代码段(常量区)–可执行的代码/只读常量(常量字符串);代码段是从操作系统/程序角度说的,常量区是从语法角度命名说的

补充

  1. 栈不大,Linux 32位下 8M;静态区和常量区不大,堆很大,32位下约 2G
  2. 函数编译完成后为指令,都在代码段
  3. 对于栈、数据、代码段,是自动控制的;堆是手动控制的
  4. 它们属于进程虚拟地址空间
  5. 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"; // char2 在栈区;*char2 ;*char2 是栈上的字符串
	const char* pChar3 = "abcd"; // pChar3 是栈上的指针;*pChar3 是常量区的常量字符串
	int* ptr1 = (int*)malloc(sizeof(int) * 4); // ptr1 是栈上的指针,*ptr1 就是指针指向的堆上的空间
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}

1)选择题

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

  1. globalVar在哪里?(C) 2. staticGlobalVar在哪里?(C)

  2. staticVar在哪里?(C) 4. localVar在哪里?(A)

  3. num1 在哪里?(A)

  4. char2在哪里?(A) 7. *char2在哪里?(A)

  5. pChar3在哪里?(A) 9. *pChar3在哪里?(D)

  6. ptr1在哪里?(A) 11. *ptr1在哪里?(B)

2)填空题

  1. sizeof(num1) = (40) 2. sizeof(char2) = (5)

  2. strlen(char2) = (4) 4. sizeof(pChar3) = (4)

  3. strlen(pChar3) = (4) 6. sizeof(ptr1) = (4)

三、C语言动态内存管理方式

malloc / calloc / realloc / free 都是库函数:

void Test ()
{
    int* p1 = (int*) malloc(sizeof(int));
    free(p1);
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    // 这里需要free(p2)吗?
    free(p3);
}

calloc 和 malloc 区别:

calloc 会按字节初始化,空间每个字节初始化为 0,相当于 malloc + memset 。calloc 开辟的空间也需要释放。

这里不需要 free(p2):

因为 realloc 对 p2 指向的空间进行了扩容,此刻无论是空间充足还是不足并扩容成功的情况下,之后对于申请内存的释放只要针对 p3 即可。

如果你能正确回答以上两个问题,请阅读:深度剖析动态内存管理

四、C++内存管理方式

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

new 和 delete 是操作符,不是函数。

1、对内置类型

int main()
{
	int* p1 = new int; // 动态申请 1 个 int 类型的空间
	int* p2 = new int[5]; // 申请 5 个 int 类型的空间

	delete p1; // 释放 p1
	delete[] p2; // 释放 p2
    p1 = nullptr;
    p2 = nullptr;

	return 0;
}

显而易见比C语言的更加简洁,而 new 申请的空间,和 malloc 一样的:

image-20230207201734559

总结:malloc/free 和 new/delete 对于内置类型没有本质区别,只有用法上的区别;而 delete 完的指针最好置空,更加安全,不置也没关系,但是要注意不能使用,因为此刻为野指针。

区分:

int* p1 = new int(5); // 申请一个 int 的空间,空间初始化为 5
int* p2 = new int[5]; // 申请 5 个 int 类型的空间

C++98不支持初始化 new 的数组;C++11 可以通过如下方式进行初始化:

int main()
{
	int* p3 = new int[5] {1, 2};
}

申请五个 int ,按照 {} 中顺序依次对空间进行初始化,其他的初始化为 0 :

image-20230207202538139

2、对自定义类型

对于自定义类型来说,malloc 不会调用构造函数,free 不会调用析构函数初始化:

image-20230207220454886

而 new 会调用构造函数,对于数组来说,则会调用数组元素个数的次;同理对于 delete 会调用析构函数,也会析构数组元素个数的次:

image-20230207221843320

(delete[] ,这个 [] 是为了告诉编译器它是一个数组,数组有多少个元素,就要调用对应次数的析构函数,之前 [] 会把对象个数存起来,方便之后调用)

总结

  • new 在堆上申请对象空间(指针指向的) + 调用构造函数初始化对象

  • delete 先调用指针类型的析构函数(清理资源) + delete 释放空间申请的空间给堆

若没有默认构造会报错:

image-20230207222409587

但是可以解决报错:

image-20230207222534271

一定要 malloc / free 和 new / delete 和 new[] / delete[] 匹配使用,否则可能会造成死循环或程序奔溃等情况,总之后果自负

例如 new 和 delete[] 造成死循环 :

image-20230207223016742

new[] 和 delete 报错:

之前说过 new 和 delete 开辟多个空间的方式是把对象个数存起来,以此知道大小。对于 vs ,这块空间会在头部有一块空间,存储个数,这时指针指向的位置是个数的下一个位置:

image-20230221205340876

delete[] 就会往前减去四个字节,取到空间里的值,然后根据值来决定调用多少次析构函数,然后从存放数值的空间的地址开始释放空间。

new[] 和 delete 崩溃的原因:释放空间的指针位置不对。

但是如果把析构函数屏蔽,就不会崩溃:

因为对于自己的析构函数,调用与否也无所谓,这时,不会在头部开这一块空间。

对于初始化,也会进行隐式类型转换:

A* p = new A[4]{1, 2, 3, 4}; // 将 1 进行隐式类型转换为 A 对象
A* p = new A[4]{ A(1), A(2), A(3), A(4) }; // 匿名对象进行调用也可以
// 多参构造函数
A* p = new A[4]{ A(1, 2), A(2, 2), A(3, 2), A(4, 2) }; // 会优化,优化为一次构造
A* p = new A[4]{ {1, 2}, {1, 2}, {1, 2}, {1, 2} }; // 隐式类型转换能成功

五、C++对动态管理的升级

两个方面:

第一个升级的地方自定义类型对象自动申请的时候,初始化和销毁清理的问题,new/delete会调用构造函数和析构函数。

第二个升级则是new失败了以后要求抛异常,这样才符合面向对象语言的出错处理机制。

了解面向过程和对象语言处理错误的方式:

  • 面向对象的语言,处理错误的方式一般是抛异常,C++中也要求错误抛异常 – try catch
  • 面向过程的语言,处理错误的方式是返回错误码 – perror

比如 C 语言在动态内存开辟时,堆空间是有限的,很有可能申请失败:

int main()
{
	char* p1 = (char*)malloc(1024u*1024u*1024u*2u); // u 表示为无符号正数
	if (p1 == nullptr)
	{
		printf("%d\n", errno); // 错误码
		perror("malloc fail"); // perror 报错
		exit(-1);
	}
	else
	{
		printf("%p\n", p1);
	}
}

image-20230207225132698

对于 C++ 来说。则是抛出异常,这时再使用上次的检查就无效了,甚至还会奔溃:

int main()
{
	char* p1 = new char[1024u * 1024u * 1024u * 2u - 1]; // new 的大小不能超过 7fff ffff,所以要 -1 
	if (p1 == nullptr)
	{
		printf("%d\n", errno);
		perror("malloc fail");
		exit(-1);
	}
	else
	{
		printf("%p\n", p1);
	}
}

image-20230207210911478

用 C++ 的方法就是 抛异常 ,但是异常其实很难,所以简单了解一下(之后会讲):

image-20230207211216811

bad allocation 就是坏的申请,就是申请失败。

正常申请:

image-20230207211605049

若不抛异常,则走完 try ,不走 catch ;否则会直接走 catch ;对于异常的捕获只会捕获在 try 内的。

抛异常可以跳过函数:

image-20230207230721841

当在函数中捕获到异常后,函数之后的语句不再执行,直接跳转到 catch 处执行。

这样就不必在可能出错的下面检查,只要在 main 函数中捕获即可。不捕获异常就会弹出未经处理的异常的错误。

抛异常解决的是抛出来的错误,对于一些严重错误:内存错误、断言错误等,会终止程序,不会走异常。

ps:delete/free一般不会失败,如果失败了,都是释放空间存在越界或者释放指针的位置不对

六、operator new/operator delete函数

new 是要先申请空间,再调用构造函数;当一个对象被 new 时,是怎么做到开辟空间的?难道是舍弃了之前的 malloc 开辟空间的方式,另辟蹊径来开空间?我们试着探究。

当调试起来后,看到反汇编,一共调了两个函数 operator new 和 它的构造函数,可 operator new 到底是什么?

image-20230208091704697

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

使用方法,例如开一个栈的空间:

image-20230208092258391

operator new 是不会调用构造函数的(如果会调用,就没有 new 什么事了),里面的仍然是随机值。

而 operator new 又是对 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 :

/*
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 和 operator delete 就是对 malloc 和 free 的封装
  • operator new 和 operator delete 调用 malloc 申请内存,失败后,改为抛异常处理错误,这样才符合 C++ 面向对象语言处理错误的方式

如果没有 operator new ,那么 new Stack 之后,就会 call malloc + call Stack构造,而 malloc 不符合 C++ 处理错误方式(失败返回 0 ,而 new 失败也是返回 0)。

operator new 是给 new 的,我们直接使用 new 即可。

补充

operator new 会抛异常,并且调用 malloc 函数,如果 malloc 失败,则会抛异常;对于 delete 底层,也进行过了封装,先调用析构函数,再用 operator delete 进行释放。

例:

image-20230517185702944

image-20230517185613864

七、new/delete 的实现原理

1、内置类型

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

2、自定义类型

  • new的原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理
    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间
  • new T[N]的原理
    1. 调用operator new[]函数(实际上是 operator new 的封装),在operator new[]中实际调用operator new函数完成N个对象空间的申请,类似于 Stack p1 = new Stack[10] ---> Stack* pst1 = (Stack*)operator new[](sizeof(Stack) * 10)
    2. 在申请的空间上执行N次构造函数
  • delete[]的原理
    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

八、定位new表达式(placement-new)

能否调用构造函数?

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = (A*)malloc(sizeof(A));

	return 0;
}

不能。

在之前构造函数不支持显示调用;而现在则可以使用 定位 new 表达式 来调用构造函数。

定位new表达式是在 已分配的原始内存空间中调用构造函数初始化一个对象 ,例如 malloc 开辟空间后初始化对象。

使用格式:

  • new (place_address) type 或者 new (place_address) type(initializer-list)

  • place_address 必须是一个指针,initializer-list 是类型的初始化列表

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = (A*)malloc(sizeof(A));
	new(p)A; // 不带参初始化,显示调用构造函数
	new(p)A(1); // 带参初始化

	return 0;
}

在这里插入图片描述

模拟 new 行为:

int main()
{
	A* p2 = new A(2); // operator new + 构造函数
	// 等价于 
	A* p3 = (A*)operator new(sizeof(A)); // operator new
	new(p3)A(3); // 调用构造函数

	return 0;
}

析构函数可以显示调用:

int main()
{
	A* p3 = (A*)operator new(sizeof(A)); // operator new
	new(p3)A(2); // 调用构造函数

	p3->~A(); // 调用析构函数
	operator delete(p3); // 释放内存

	return 0;
}

使用场景

定义 new 表达式在平常作用不大,它的真正应用场景是对内存池(池化技术–内存池、线程池、连接池):

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

内存池解读:

image-20230221214439878

平常都是在堆上拿,现在在内存池拿,如果内存池有,则直接拿(比较快),没有内存池就去堆上拿,拿一大块,慢慢用。

定位 new 的应用:链表中创建节点时,使用内存池来进行,下面两步为显示调用构造函数和显示调用析构函数

image-20230517164150108

九、内存泄漏

动态申请的内存,不使用了,没有释放,就可以说成是内存泄漏。

好玩的:

image-20230208105835021

内存泄漏不是一定有危害,就比如:

int main()
{
	char* p = new char[1024u * 1024u * 1024u];
	printf("%p\n", p);

	return 0;
}

每次耗费一个 g 。虽然没有释放,但是当程序执行结束后,内存会被释放,还给OS.

而内存泄漏无非几种现象:

  1. 出现内存泄漏的进程正常结束,进程结束时内存会还给OS,不会有什么损害
  2. 出现内存泄漏的进程非正常结束,比如僵尸进程
  3. 需要长期运行的程序,出现内存泄漏,危害很大,OS会越来越慢,甚至卡死宕机,例如服务器程序:王者匹配机制、美团骑手匹配

2、3 两点会造成很大的危害。尤其是第三点,每天一点点,服务器崩坏一点点,不容易发现。

内存泄漏有两个典型现象:

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	// 2.异常安全问题
	int* p3 = new int[10];
	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
	delete[] p3;
}

若 Func 有异常,则直接跳转到 catch ,这时 delete[] p3 没有执行,就内存泄漏了。

java 也可能会有内存泄漏,虽然有回收机制,但是 java 后台虚拟机会有一定代价,但是比 C++ 更优。

1、内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

    • 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak 。
  • 系统资源泄漏

    • 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2、如何检测内存泄漏

在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。

int main()
{
    int* p = new int[10];
    // 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏
    _CrtDumpMemoryLeaks();
    return 0;
}

// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。

3、如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

4、补充

对于 32 位,申请 2g 的空间是失败的,因为对于 32 位的堆空间一共就 2g ,一下子申请显而不太可能,但是将程序改为 64 位就可以,因为此刻的堆空间就变大了很多:

image-20230208113037831

image-20230208111959538

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

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

相关文章

vue使用echarts根据页面大小 echarts窗口自适应

1. 使用window.onresize var myChart echarts.init(document.getElementById(myChart)); window.onresize () > {myChart.resize() }优点&#xff1a; 可以根据窗口大小实现自适应 缺点&#xff1a; window.onresize是绑定到window上的&#xff0c;切换vue页面时监听依…

WEB APIs day4 (1)

一、日期对象 1.实例化 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…

做私域选个微还是企微,哪个有优势?

做私域&#xff0c;你必须要有一个&#xff0c;引流新客户及留存老客户的地方。 于是&#xff0c;就有很多人讨论或者纠结&#xff1a;做私域&#xff0c;选择个人微信&#xff1f;还是企业微信&#xff1f; 让我们一起来看看个人微信和企业微信在功能和使用上有哪些区别&…

什么是 DevOps?看这一篇就够了!

目录 一、前因 二、记忆 三、他们说…… 3.1、Atlassian 回答“什么是 DevOps&#xff1f;” 3.2、微软回答“什么是 DevOps&#xff1f;” 3.3、AWS 回答“什么是 DevOps&#xff1f;” 四、DevOps 文化 4.1、什么是文化&#xff1f; 4.2、什么是 DevOps 文化&#xf…

谈人工智能和数据治理

一、说明 生成式人工智能已经开始撼动数据治理的世界&#xff0c;并且将继续这样做。 自 ChatGPT 发布以来才 6 个月&#xff0c;但感觉我们已经需要回顾了。在这篇文章中&#xff0c;我将探讨生成式人工智能如何影响数据治理&#xff0c;以及它在不久的将来可能会把我们带到哪…

APP打包教程(使用HBuilder X工具打包uni-app)

App打包&#xff08;使用Hbuilder进行App打包&#xff09; 一、修改接口地址 1.打开uni-app下config/app.js修改接口地址&#xff0c;将下图红框中的地址修改成您的域名 二、配置参数 1.打开 uni-app 根目录下的 manifest.json 文件&#xff0c; 点击《基础配置》&#xff0…

户外运动耳机怎么选?这几款耳机最适合在运动时佩戴!

随着人们开始追求运动和健身带来的乐趣&#xff0c;以及在运动过程中享受音乐的过程&#xff0c;耳机逐渐成为当下的刚需&#xff0c;其中骨传导耳机凭借防水防汗、佩戴稳固不掉落加上开放式聆听受到当下消费者的热烈欢迎&#xff0c;有优点就有缺点&#xff0c;由于骨传导耳机…

opengauss 在一个机器上搭建主备集群

项目上需要高斯主备集群&#xff0c;试了好几个版本。最后搭建出一个在一个机器上的主备。用做测试&#xff0c;记录一下。 下载安装包 从openGauss开源社区下载对应平台的安装包。 a. 通过软件包 | openGauss登录openGauss开源社区&#xff0c;选择3.1.0版本对应平台极简版安…

【网络可用性】

网络可用性 Availability defined in a service-level agreement (SLA) between a network operator (carrier) and a customer. 关于SLA&#xff0c;可参考 思科Service Level Management: Best Practices White Paper 可用性对应的停机时间 转载于 https://blog.csdn.net/a…

Spark(27):Spark任务调度机制

目录 0. 相关文章链接 1. Spark任务调度概述 2. Spark Stage级调度 3. Spark Task级调度 3.1. 调度策略 3.1.1. FIFO调度策略 3.1.2. FAIR调度策略 3.2. 本地化调度 3.3. 失败重试与黑名单机制 0. 相关文章链接 Spark文章汇总 1. Spark任务调度概述 在生产环境下&am…

C#为什么不能成为大学编程入门的首选?

大学编程入门不以C#作为首选的原因有多个因素。虽然C#是一种功能强大的编程语言&#xff0c;但在大学编程入门阶段&#xff0c;通常会选择其他语言作为首选&#xff0c;以下是一些可能的原因&#xff1a; 我这里刚好有嵌入式、单片机、plc的资料需要可以私我或在评论区扣个6 …

投个 3D 冰壶,上班玩一玩 | 物理引擎

本篇文章将介绍如何使用物理引擎和图扑 3D 可视化技术来呈现冰壶运动的模拟。 Oimo.js 物理引擎 Oimo.js 是一个轻量级的物理引擎&#xff0c;它使用 JavaScript 语言编写&#xff0c;并且基于 OimoPhysics 引擎进行了改进和优化。Oimo.js 核心库只有 150K &#xff0c;专门用…

抢滩“返校季”!这些品类拉开爆单之旅!

“返校季”作为仅次于“黑五网一”的购物旺季。在开学的前四到六周&#xff0c;家长与学生就会开始陆续采购返校季所需的物品&#xff0c;从七月下旬一直持续到九月&#xff0c;都将是“返校季”的购物高峰。今年的“返校季”又将呈现什么样的消费趋势&#xff1f;消费者的消费…

Julia变量作用域问题

变量作用域问题 1.软作用域与硬作用域 1.1软作用域 软作用域是指在代码块内部定义的变量&#xff0c;如果外部有同名变量&#xff0c;则内部变量会被优先使用&#xff0c;即“遮蔽”外部的同名变量&#xff0c;而不影响外部变量。 1.2硬作用域 硬作用域是指在代码块内部定…

DataGrip使用随笔

由于公司不让使用NAVIcat&#xff0c;顾用datagrip作为替代软件 1.下载和安装 从官网下载安装包https://download.jetbrains.com.cn/datagrip/datagrip-2023.1.2.exe后&#xff0c;选择安装位置并试用 2.链接数据库 需要先新建个项目存储所有的db连接信息&#xff0c;然后选…

安卓通过adb pull和adb push 手机与电脑之间传输文件

1.可以参考这篇文章 https://www.cnblogs.com/hhddcpp/p/4247923.html2.根据上面的文章&#xff0c;我做了如下修改 //设置/system为可读写&#xff1a; adb remount //复制手机中的文件到电脑中。需要在电脑中新建一个文件夹&#xff0c;我新建的文件夹为ce文件夹 adb pull …

2.9Frame 框架

2.9Frame 框架 这一次的效果将会像下面的图片一样. Frame 部件 Frame 是一个在 Windows 上分离小区域的部件, 它能将 Windows 分成不同的区,然后存放不同的其他部件. 同时一个 Frame 上也能再分成两个 Frame, Frame 可以认为是一种容器. ###定义一个label显示on the windo…

视频问答新增或修改视频问答

通过问答id新增或修改视频问答题目 新增或修改视频问答 图3&#xff1a;视频问答功能&#xff08;观看效果&#xff09; 图4&#xff1a;视频问答功能&#xff08;观看效果&#xff09; 图5&#xff1a;视频问答功能&#xff08;观看效果&#xff09; 单元测试 Testpublic voi…

Linux下九个实用脚本

目录 1.批量创建用户并设置密码脚本 2.查看网卡实时流量脚本 3.nginx访问日志脚本 4.dos防范攻击&#xff08;自动屏蔽攻击脚本&#xff09; 5.监控多台服务器磁盘利用率脚本 6.监控MySQL主从同步异常脚本 7.批量检查网站异常脚本 8.查看服务器资源利用率脚本 9.查找占…

高效出报表的工具有哪些?奥威BI报表工具怎样?

随着企业精细化数据分析的展开&#xff0c;数据分析报表的制作压力也随之增加。对企业而言&#xff0c;拥有一个高效出报表的工具十分重要。高效出报表的工具有哪些&#xff1f;奥威BI报表工具的效率够不够高&#xff1f; 高效出报表的工具有很多&#xff0c;奥威BI报表工具就…