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

👑作者主页:@安 度 因
🏠学习社区: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; // 释放 p1delete[] p2; // 释放 p2p1 = 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 bytesvoid* 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_FINALLYreturn;
}
/*
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 newnew(p3)A(3); // 调用构造函数return 0;
}

析构函数可以显示调用:

int main()
{A* p3 = (A*)operator new(sizeof(A)); // operator newnew(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.mzph.cn/news/4185.shtml

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

相关文章

Html基础知识学习——圣杯布局、margin负值、等高布局(十七)

文章目录 圣杯布局margin负值等高布局 圣杯布局 两边页面固定中间页面宽度随着浏览器大小自适应 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-widt…

mmdetection3.1.0 训练自己的数据集

目录 前言安装mmcv安装mmdetection验证安装数据集转为COCO划分训练集、验证集及测试集安装PaddlePaddle安装PaddleX划分数据集 修改对应文件修改coco.py重新安装修改模型文件 训练测试测试带真值的图像测试不带真值的图像批量测试 错误集锦ValueError: need at least one array…

Kafka

1.定义 Kafka&#xff1a;一个分布式基于发布/订阅模式的消息队列。 发布者发布消息进入队列后&#xff0c;每个订阅者都能在一定时间内获取发布的消息&#xff08;Kafka&#xff1a;消费者通过主动拉取pull队列&#xff09;。 缺点&#xff1a;即使没有消息&#xff0c;消费者…

如何解决VScode远程下载插件不了的问题?如何手动安装插件?

当我们在使用VScode进行远程操作时&#xff0c;在安装我们所需要的一些插件时&#xff0c;可能会出现如下图&#xff0c;一直卡在安装中....明明只有小几十MB&#xff0c;却一连好几个小时都一动不动。像这种情况&#xff0c;就需要我们进行手动安装该插件。 插件网站&#xff…

在云计算环境中,保护Java应用程序可用的有效措施和工具

云计算&#xff08;Cloud&#xff09;技术是近年来计算机科学的一个重要突破。大多数组织已经通过将自己的应用程序移入云平台而获益。不过&#xff0c;如何保证应用程序在第三方服务器上的安全性&#xff0c;是一项艰巨的挑战。 在本文中&#xff0c;我们将重点讨论Java&…

一文带你了解Spring中存入Bean和获取Bean的方式

0. Spring中的五大注解 上图中就是五大类注解对应的层&#xff0c;通过源码可以看到其他四个注解都基于Conponent 1. 存入 Bean Spring既然是一个包含众多工具方法的IoC容器&#xff0c;它是一个控制反转的容器&#xff0c;所以就需要将Bean对象存入到容器中&#xff0c;需要…

使用Jenkins自由风格的软件项目实现接口自动化测试持续集成

这里写目录标题 一、JOB项目配置1、添加描述2、限制项目的运行节点3、源码管理4、构建触发器5、构建步骤6、构建后操作 一、JOB项目配置 1、添加描述 可选选项可填可不填 2、限制项目的运行节点 节点中要有运行环境所需的配置 节点配置教程&#xff1a;https://blog.csdn…

回归预测 | MATLAB实现基于SVM-Adaboost支持向量机结合AdaBoost多输入单输出回归预测

回归预测 | MATLAB实现基于SVM-Adaboost支持向量机结合AdaBoost多输入单输出回归预测 目录 回归预测 | MATLAB实现基于SVM-Adaboost支持向量机结合AdaBoost多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于SVM-Adaboost支持向…

Spring初识(一)

一.Spring 是什么&#xff1f; 首先我们来看看官网的解释 Spring 使每个人都可以更快、更轻松、更安全地进行 Java 编程。Spring 对速度、简单性和生产力的关注使其成为 世界上最受欢迎的 Java框架。 这里我简单的说明一下什么是spring? 我们通常所说的 Spring 指的是 Sprin…

[JavaScript游戏开发] 2D二维地图绘制、人物移动、障碍检测

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 文章目录 系列文章目录前言一、列计划1.1、目标1.2、步骤 二、使用步骤2.1、准备素材(图片)&#xff1a;草坪、人物(熊猫)、障碍(石头)2.2、初…

Python对Excel不同的行分别复制不同的次数

本文介绍基于Python语言&#xff0c;读取Excel表格文件数据&#xff0c;并将其中符合我们特定要求的那一行加以复制指定的次数&#xff0c;而不符合要求的那一行则不复制&#xff1b;并将所得结果保存为新的Excel表格文件的方法。 这里需要说明&#xff0c;在我们之前的文章Pyt…

php连接上mysql数据库该的配置方法

用mysql官方的管理工具workbench&#xff1a; 打开导出界面后&#xff0c;下一步&#xff0c;选择csv格式&#xff0c;导出后excel就能打开了 如果你需要在程序代码中导出&#xff0c;需要找到对应代码的excel处理库。 如php 的 phpExcel( 最新版已更名为 phpoffice/phpspread…

selenium WebDriver 中的几种等待--sleep(),implicitly_wait(),WebDriverWait()

目录 强制等待:sleep() 隐式等待:implicitly_wait() 显示等待:WebDriverWait() 与until()或者until_not()方法结合使用 WebDriverWait与expected_conditions结合使用 显示等待,自定义等待条件 强制等待:sleep() import time sleep(5) #等待5秒 设置固定休眠时间&#x…

14matlab数理统计 多项式的求根和根据根求多项式(matlab程序)

1.简述 分享一下通过多种不同的方法计算多项式的根。 数值根 使用代换法求根 特定区间内的根 符号根 数值根 roots 函数用于计算系数向量表示的单变量多项式的根。 例如&#xff0c;创建一个向量以表示多项式 x2−x−6&#xff0c;然后计算多项式的根。 p [1 -1 -6]; r …

Prometheus节点监控及hadoop集群监控

背景:我司长期苦于CM6.3后收费问题,这次领导痛下决心,决定要自己开发一套大数据管理平台,监控就是其中一部分,本文主要阐述,话不多说,先看效果。 1.监控组件Prometheus 1.1上传Prometheus包 [root@bigdb01 ~]# rsync root@172.16.1.247/data/fan/install/native/09.…

uni.app如何检测小程序版本更新以及上线后如何关闭全局打印

uni.app如何检测小程序版本更新以及上线后如何关闭全局打印 检测小程序版本更新关闭全局打印 检测小程序版本更新 App.vue 入口文件中 添加如下代码 //检测小程序版本是否更新const updateManager wx.getUpdateManager()updateManager.onCheckForUpdate(function(res) {// 请求…

DuiLib中的list控件以及ListContainerElement控件

文章目录 前言1、创建list控件2、创建 ListContainerElement 元素&#xff0c;并添加到 List 控件中,这里的ListContainerElement用xml来表示3、在 ListContainerElement 元素中添加子控件 1、List控件2、ListContainerElement控件 前言 在 Duilib 中&#xff0c;List 控件用于…

【批量将视频转为图像序列】

批量将视频转为图像序列 代码如下&#xff0c;代码中带有解释&#xff1a; # 导入所需要的库 import cv2 import os import numpy as np# 多个视频所在的路径 datasets_path ["/home/y/Code/数据集/1/007f.mp4","/home/y/Code/数据集/1/05f.mp4","/…

UI界面中的图标设计趋势与最佳实践

作为UI设计师&#xff0c;在日常的工作中&#xff0c;避免不了做图标规范。今天跟大家聊一聊&#xff0c;UI设计中的图标设计。 规范的重要性不用多说了&#xff0c;没有规范多个设计师绘制的图标会有很多差异&#xff0c;描边粗细、角度、圆角度等等。今天的文章和大家聊一下…

Stable Diffusion - 编辑生成 (OpenPose Editor) 相同人物姿势的图像

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131818943 OpenPose Editor 是 Stable Diffusion 的扩展插件&#xff0c;可以自定义人物的姿势和表情&#xff0c;以及生成深度、法线和边缘图等信…