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

【C/C++】内存管理详解

目录

  • 内存布局思维导图
  • 1.C/C++内存分布
    • 数据段:
    • 栈:
    • 代码段:
    • 堆:
  • 2.C语言中动态内存管理方式
  • 3.C++内存管理方式
    • 3.1new/delete操作内置类型
    • 3.2new和delete操作自定义类型
  • 4.operator new 与 operator delete函数
  • 5.new和delete的实现原理
    • 5.1内置类型
    • 5.2自定义类型
  • 6.必须匹配的操作符
    • 6.1内置类型
    • 6.2自定义类型
      • new -- free
      • new[]--free/new[]--delete
  • 7.定位new表达式
    • 7.1使用格式:
    • 7.2使用场景:
    • 7.3内存池
  • 8.面试题
    • malloc/free和new/delete的区别
  • 9.总结和博客思维导图

内存布局思维导图

在这里插入图片描述

  • 上图为内存布局的思维导图,有关整篇博客的思维导图放在的最后的总结部分

1.C/C++内存分布

我们对比图片观察不同的数据存放在那个内存空间的那一块(对C语言内存管理不熟悉可查看该篇博客:【C语言】内存管理):

在这里插入图片描述

  • 栈: 又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  • 内存映射段: 是高效的I / O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享的共享内存,做进程通信。
  • 堆: 用于程序运行时动态内存分配,堆是可以上增长的。
  • 数据段: 存储全局数据和静态数据。
  • 代码段: 可执行的代码/只读常量

接着我们对上图的代码分布情况做出如下解释(上图的代码放在解释后,有需要的可以自己复制运行一下)

数据段:

全局变量,static修饰创建的静态变量一定存放在数据段,存放静态数据和全局数据的地方,这个没有异议。

栈:

localNum变量num1数组 正常在栈上开的空间创建,也很好理解。

至于char2、pChar3、ptr1、ptr2和ptr3 ,我们需要明白一点,不论是数组还是指针,不论接收的是常量字符串还是程序员自己开辟的空间的地址,它们终究是在函数内,是在栈上创建的变量,用来存放数据罢了,它们作为变量的地址就在栈上。

代码段:

在C语言中我们就应该知道,使用双引号括起来的数据为常量字符串,这样的字符串为只读常量,不能修改,存放在代码段(常量区),这是规定好的,我们理解记忆即可,但其中有一些比较迷惑人的地方需要解释一下:

  1. 使用数组和指针的形式接收常量字符串结果是不同的。

    使用数组接收: 如上图的char char2[]="abcd";,常量字符串存放在代码段,在赋值时,char2在栈上开辟一个相同大小的数组,通过拷贝将每个字符存放在数组中(包含最后的’\0’终止字符),所以我们可以修改数组char2中的数据。

    在这里插入图片描述

    使用指针接收:

    • C++: 如上图的const char* pChar3 = "abcd";,这个就要好理解了,就是简单的将常量字符串的首地址存入指针变量pChar而已,所以pChar3的地址是在栈上,而指针变量pChar3所存的地址是在代码段中。因为常量字符串不能修改,所以在C++中不允许权限发生改变,必须使用const关键字修饰指针。

      在这里插入图片描述

    • C: 在C语言中,使用指针接收字符串常量则可以不使用const修饰指针,甚至我们可以使用指针来修改常量字符串编译器不会报错,但这样的程序是运行不了的,程序在执行到常量字符串修改哪一行就会停下(如下图),所以我们用C语言遇到这种情况最好还是使用const修饰。

      在这里插入图片描述

堆:

程序员使用malloc、calloc、realloc 开辟的空间必然是放在堆上的,这样的空间,程序员自己申请也需要自己使用free 释放。空间在堆上开辟好后,分别使用指针接收这些空间的首地址,所以接收这些地址的指针变量是在栈上开辟,但它们所存储的地址却是在堆上。

代码如下:

int globalNum = 1;
static int staticGlobalNum = 1;int main()
{static int staticNum = 1;int localNum = 1;int num1[10] = { 1,2,3,4 };char char2[] = "abcd";const char* pChar3 = "abcd";int* ptr1 = (int*)malloc(sizeof(int));int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2,sizeof(int) * 4);free(ptr1);free(ptr3);return 0;
}

2.C语言中动态内存管理方式

C语言动态开辟内存的方式即为malloc、calloc和realloc,最后由free释放开辟的空间,这是学习C必须要掌握的对象,而面试时也会经常问道相关的问题,我们通过下面两道面试题在来看一下

1.malloc/calloc/realloc的区别?

  • malloc向堆区申请空间,参数为申请内存的大小
  • calloc向堆区申请空间,第一个参数为申请空间的每一个字节的初始值,第二个参数为申请空间的大小
  • realloc向堆区申请空间,第一个参数为要调整的地址,第二个参数为调整后新的空间的大小,若第一个参数为空,则realloc和malloc相同。
  • 向堆申请的空间必须使用free释放,防止内存泄漏。

2.malloc的实现原理

  • GLibc堆利用入门-机制介绍

(比较复杂,建议结合操作系统一起学习)

3.C++内存管理方式

C语言管理内存的方法(malloc、calloc、realloc、free)在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,比如说自定义类型,因此C++提出了自己的内存管理方式:通过new和delete操作符进行内存管理。

3.1new/delete操作内置类型

我们以int类型为类

  1. 动态申请一个int类型的空间

    int* ptr1 = new int;//申请空间
    delete ptr1;//释放空间
    

    直接new后跟类型即可向堆申请空间。

  2. 动态申请一个int类型的空间并初始化为8

    int* ptr2 = new int(10);//申请空间
    delete ptr2;//释放空间
    

    在类型后加小括号,在其内填写需要初始的值

    • 若小括号内不添加任何值,默认初始化为0
  3. 动态申请10个int类型的空间

    int* ptr3 = new int[10];//申请空间
    delete[] ptr3;//释放空间
    

    在类型后加中括号,其内填需要申请的同类型空间的个数

  4. 动态申请10个int连续的空间并初始化

    在中括号后加小括号,在其内不添加任何值,默认初始化为0

    int* ptr4_1 = new int[10]();//申请空间
    delete[] ptr4_1;//释放空间
    

    在中括号后加大括号,在其内填写数组的值,不足的部分默认为0

    int* ptr4_2 = new int[10] {1,2,3,4};//申请空间
    delete[] ptr4_2;//释放空间
    

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],需要匹配起来使用,不能乱用。(原因请看 6.必须匹配的操作符

通过下面代码查看,创建空间是否成功

int main()
{int* ptr1 = new int;int* ptr2_1 = new int(10);int* ptr2_2 = new int();int* ptr3 = new int[4];int* ptr4_1 = new int[4]();int* ptr4_2 = new int[4]{ 1,2 };printf("ptr1:%p %d\n", ptr1, *ptr1);printf("ptr2_1:%p %d\n", ptr2_1,*ptr2_1);printf("ptr2_2:%p %d\n", ptr2_2, *ptr2_2);for (int i = 0; i < 4; i++){printf("ptr3:%p %d\n", &ptr3[i], ptr3[i]);}for (int i = 0; i < 4; i++){printf("ptr4_1:%p %d\n", &ptr4_1[i], ptr4_1[i]);}for (int i = 0; i < 4; i++){printf("ptr4_2:%p %d\n", &ptr4_2[i], ptr4_2[i]);}delete[] ptr4_1;delete[] ptr4_2;delete[] ptr3;delete ptr2_1;delete ptr2_2;delete ptr1;return 0;
}

在这里插入图片描述

3.2new和delete操作自定义类型

new 和 delete操作自定义类型和操作内置类型相同。

new/delete 和malloc/free对于自定义类型时,不同的是申请空间时:new会调用构造函数,释放空间时:delete会调用析构函数

class A
{
public:A(int a = 0){_a = a;cout << "A(int a = 0)" << " " << _a << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A* ptr1 = new A(1);delete ptr1;A* ptr2 = new A;delete ptr2;A* ptr3 = new A[4];//调用四次构造函数delete[] ptr3;//释放四次构造函数A* ptr4 = new A[4]{ 1,2,3,4 };//为每次调用的构造函数传值delete[] ptr4;return 0;
}

在这里插入图片描述

对于malloc和free,什么都不会调用

class A
{
public:A(int a = 0){_a = a;cout << "A(int a = 0)" << " " << _a << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A* ptr1 = (A*)malloc(sizeof(A));if (ptr1 == nullptr){perror("malloc fail!");exit(-1);}free(ptr1);A* ptr2 = (A*)malloc(sizeof(A)*4);if (ptr1 == nullptr){perror("malloc fail!");exit(-1);}free(ptr2);return 0;
}

优势:

  1. 更简洁
  2. 申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc它们三个不会
  3. new申请空间失败会抛出异常,而malloc需要自己判断是否申请成功(一些严格的编译器必须要判断)

4.operator new 与 operator delete函数

new和delete 是用户进行动态内存申请和释放的操作符operator new 和 operator delete 是系统提供的全局函数

  • new在底层调用operator new 全局函数来申请空间
  • delete在底层通过operator delete 全局函数来释放空间。

我们编写如下代码,通过汇编查看

class A
{
public:A(int a = 0){_a = a;cout << "A(int a = 0)" << " " << _a << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A* ptr = new A;delete ptr;return 0;
}

在这里插入图片描述

  • 根据这些我们知道,new在申请空间时是通过operator new 申请,之后调用构造函数初始化。

至于delete我使用的VS2019不方便查看,大家要明白它底层调用的是operator delete ,还有下面的一点。

  • 注意:对于自定义类型的对象,一定是先调用析构函数,清理掉对象的数据,之后再使用operator delete 释放空间,否则空间之间释放,析构函数就没办法调用了。(内置类型没有这个需求,直接释放)

接着我们看一下operator new 和 operator delete 是则么实现的:

operator new:

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);
}

在这里插入图片描述

  • 观察第5行,operator new 实际上是通过malloc 来申请空间
  • malloc 申请空间成功时直接返回;
  • 当申请失败,且用户设置了空间不足应当措施,则尝试执行该措施,继续申请。若没有设置,则抛出异常。

operator delete:

/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)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;
}
  • 观察第4行和18行,operator delete 实际上是通过free 释放空间的

我们可以将operator new 和 operator delete 看作是对malloc和free 的封装,之所以要这么做是因为C++是面向对象的语言,当申请空间发生错误时不能像C那样去处理问题,直接返回个NULL,需要让用户更直观的看到问题的出现,需要增加异常 这个功能,如果了解一些Java的知识,想必对异常并不陌生,这正是面向对象的特点。

5.new和delete的实现原理

5.1内置类型

申请内置类型的空间,new和malloc,delete和free基本类似, 不同的地方有以下两点:

  1. new/delete申请和释放单个元素的空间,new[]/delete[]申请和释放的是连续的空间,malloc和free不用区分这些
  2. new再申请空间失败时会抛出异常,malloc会返回NULL

5.2自定义类型

  • 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 new[]相同,operator delete[]中调用operator delete来释放空间。

6.必须匹配的操作符

我们使用操作符申请空间时,必须匹配使用,malloc/free、new/delete、new[]/delete[]

分别看一下,不匹配会发生什么

6.1内置类型

int main()
{int* ptr1 = new int;free(ptr1);int* ptr2 = new int[10];free(ptr2);return 0;
}

我们执行上述代码,发现编译器并不会报错,因为new 也是通过malloc 来申请空间,通过free 释放的,对于内置类型,我们直接使用free 释放是没有问题的。

6.2自定义类型

定义如下类,之后的代码会用下面的类来创建对象申请空间(没有用到的地方会说明):

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

new – free

int main()
{A* ptr1 = new A;free(ptr1);return 0;
}

在这里插入图片描述

我们看到,这样的代码执行起来编译器也是不会报错的,只是没有执行析构函数,这种情况就要分类讨论了

  1. 无需要释放的资源 :就像这个A类,它执行不执行析构函数都是无所谓的,因为它对象内没有在额外申请空间,我们只需要释放掉它new出来的即可,这一点free是可以做到的。

  2. 有需要释放的资源 :我们将上面的代码修改一下,在来运行

    class B
    {
    public:B(int a = 4){_pa = new int[a];cout << "B(int a = 0)" << endl;}~B(){delete[] _pa;cout << "~A()" << endl;}
    private:int* _pa;
    };int main()
    {B* ptr = new B;free(ptr);return 0;
    }
    

    在这里插入图片描述

    观察上面的代码,我们free 只是将ptr 指向的空间释放,而对象ptr_pa 指向的空间却没有释放,这就造成内存泄漏 ,这是很严重的事情,而C/C++中,我们自己申请的内存需要我们自己释放,编译器是不会管的,也不会检查出内存泄漏 的,依然会正常运行。

    在这里插入图片描述

    所以delete 的顺序为先调用析构函数,释放对象内的资源,之后operator delete 释放掉对象的空间。

    关于内存泄漏的危害我举个例子:如果一个程序一直在运行,而它存在内存泄漏,那它会一直消耗空间,直到程序无法运行,这是很严重的问题。

    我们使用下面的代码来检测,一个程序可以开辟的最大空间(我使用的是VS2019)

    int main()
    {int size = 0;while (1){int* ptr = (int*)malloc(sizeof(int)*2*1024);size += 1;//每次开辟1KB的空间if (ptr == nullptr){break;}}cout << size / 1024 << "MB" << endl;return 0;
    }
    

    在这里插入图片描述

结论: new和malloc系列,底层实现机制有关联交叉。不匹配使用可能出现问题,也可能没有问题,所以大家一定要匹配使用。

拓展:

如果对java有一定了解,应该知道java是没有释放内存这一说的,因为它有一个垃圾回收的机制叫做GC,当我们new一个对象GC就会记录下来,使用过后需要释放该对象时,JVM(java虚拟机) 就会实现这一机制,释放空间。

至于C++为什么不搞这样的机制,因为C/C++是极度追求性能的,GC 这样的机制一定程度上会对性能造成影响,所以C++需要程序员自己释放,以此提高性能。

从学习语言的角度,有GC也并不就是一件好事,虽然用起来方便,但学习Java的每个人都要懂得GC 这个复杂的机制。

new[]–free/new[]–delete

观察下面两段代码及运行结果:

代码1:

int main()
{A* ptr = new A[4];free(ptr);return 0;
}

在这里插入图片描述

编译器报错无法正常运行。

代码2:

int main()
{A* ptr = new A[4];delete ptr;return 0;
}

在这里插入图片描述

编译器报错无法正常运行。

这两段代码报错的原因和析构函数无关,就像上一段讲的,析构函数哪怕不调用,最坏是内存泄漏,编译还是可以执行的,不会突然报错,报错的原因在于new 开辟连续空间的机制

我们先来看正确的写法:

	A* ptr = new A[4];delete[] ptr;

使用new申请连续的空间时,我们在类型后的中括号内填入了数字4告诉编译器,需要申请4个A类型的空间,需要调用4次构造函数,而在delete时,却没有告诉编译器到底调用几次析构函数(释放多少空间编译器会记录,我们使用malloc为内置类型开辟连续的空间时,也没有告诉编译器需要free多大的空间就是证明),那编译器是怎么知道要调用几次析构函数的?

在这里插入图片描述

在new开辟空间时,编译器会在开辟的空间起始地址处向前在开辟4个字节的空间,用来存放需要调用的析构函数个数(只用来存储调用几次析构,释放空间的大小编译器会使用其他办法记录),若释放空间时,写法为delete[],释放空间会包含这4个字节,也就是释放空间时,指针指向的起始位置为这四个字节的首地址, 也就知道了需要调用几个析构函数,若写法为delete/free,则不会去理会起始位置前的这四个字节,指针指向的位置就是申请的空间的起始位置, 只是按照步骤走下去(delete调用一次析构然后释放空间,free直接释放空间),那4个存储数字的字节被保留下来,释放空间的指针位置不对,因此导致编译器报错。

  • 一般程序崩溃都是指针指向的问题,这个就是指向的初始位置不对,像内存泄漏是不会崩溃的。

这里还要说明一点,我们在定义A类时,我们自己写了析构函数,若我们自己没写,则使用new[]/fee、new[]/delete 是不会报错的

class A
{
public:A(int a = 0){_a = a;cout << "A(int a = 0)" << " " << _a << endl;}//~A()//{//	cout << "~A()" << endl;//}
private:int _a;
};
int main()
{A* ptr1 = new A[4];free(ptr1);A* ptr2 = new A[4];delete ptr2;return 0;
}

在这里插入图片描述

如上,当我们将析构函数注释后,类内就只有编译器自己的默认析构函数,这时编译器会认为没有调用析构函数的必要, 也就不需要在开辟4个字节来存储需要调用析构函数的次数,编译器正常运行。

注意: 再次强调,因为C++的语法和底层比较复杂,哪怕我们知道了怎么开辟和释放空间编译器不会报错,但终究会造成问题,如自己的使用失误,误导他人等等,我们还是坚持遵守C++的语法,配套使用malloc/free、new/delete、new[]/delete[] ,不交叉使用。

7.定位new表达式

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

7.1使用格式:

new(place_address)typenew(place_address)type(initializer_list)

place_address必须时一个指针,initializer_list是类型的初始化列表

7.2使用场景:

如下,我们使用malloc来申请一块自定义类型的空间,申请后,这块空间是没有办法自动调用构造函数的,只能通过定位new 来调用构造函数初始化,释放时则直接调用析构函数即可。

class A
{
public:A(int a = 0){_a = a;cout << "A(int a = 0)" << " " << _a << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};int main()
{A* ptr = (A*)malloc(sizeof(A));new(ptr)A(1);//定位new,传参//new(ptr)A;//定位new,不传参ptr->~A();//调用析构函数return 0;
}

在这里插入图片描述

想必大家都有疑问,为什么要写的这么复杂,直接new一个对象出来不就好了

	A* ptr = new A;delete ptr;

凡是存在必然会有它一定的道理,我们一般使用new是向操作系统的堆来申请空间,但操作系统分配空间效率上会有些慢,所以有些地方如果需要频繁的申请内存就会有一个内存池,用来存储需要分配的空间并且效率要高于操作系统,同时内存池分配出的内存没有初始化,如果是自定义类型的对象, 需要使用new的定义表达式进行初始化。

7.3内存池

接着我们来看一下内存池的工作流程:

  1. 当内存池内有内存时,我们直接向其申请,它就会分配给我们空间

    在这里插入图片描述

  2. 当内存池内没有内存时,我们直接向其申请,它会先和操作系统申请,告诉操作系统自己没有内存了,操作系统就会返回其大量内存空间,使其短时间内不会在缺少内存,防止效率下滑。

    在这里插入图片描述

8.面试题

下面是一道面试题,不建议大家去背,做到理解记忆最好

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在释放空间前会调用析构函数完成空间中资源的清理。

9.总结和博客思维导图

对于内存管理,C/C++几乎类似,只是C++在C的基础上做出了更适合面向对象的调整

  1. 增加了new/delete
  2. 针对自定义类型更好的调用构造和析构函数

在这里插入图片描述

相关文章:

【C/C++】内存管理详解

目录内存布局思维导图1.C/C内存分布数据段&#xff1a;栈&#xff1a;代码段&#xff1a;堆:2.C语言中动态内存管理方式3.C内存管理方式3.1new/delete操作内置类型3.2new和delete操作自定义类型4.operator new 与 operator delete函数5.new和delete的实现原理5.1内置类型5.2自定…...

Android ProcessLifecycleOwner 观察进程生命周期

文章目录简介使用依赖用法1&#xff0c;结合 LiveData用法2&#xff0c;获取 owner的 lifecycle 实例&#xff0c;并对 lifecycle 添加观察者简介 ProcessLifecycleOwner 直译&#xff0c;就是&#xff0c;进程生命周期所有者。 通过 DOC 注释了解到&#xff1a; Lifecycle.E…...

如何编写一个 npm 插件?

提到写 npm 插件&#xff0c;很多没搞过的可能第一感觉觉得很难&#xff0c;无从下手&#xff0c;其实不然。 我们甚至写个简单的 console.log(hello word)&#xff0c;都是可以当成一个插件发布上去的。 其实无从下手的主要难点还是在于你的具体要做的功能逻辑&#xff0c;这…...

mapstruct- 让VO,DTO,ENTITY转换更加便捷

mapstruct- 让VO&#xff0c;DTO&#xff0c;ENTITY转换更加便捷 1. 简介 MapStruct是一个代码生成器&#xff0c;简化了不同的Java Bean之间映射的处理&#xff0c;所谓映射指的就是从一个实体变化成一个实体。例如我们在实际开发中&#xff0c;DAO层的实体和一些数据传输对…...

IAR警告抑制及还原

工作中需要临时抑制 警告 Pa084&#xff0c;源代码如下&#xff1a; sy_errno_t sy_memset_s(void *dest, sy_rsize_t dmax, int value, sy_rsize_t n) { sy_errno_t err; if (dest NULL) { return SY_ESNULLP; } if (dmax > SY_RSIZE…...

工厂模式(Factory Pattern)

1.什么是工厂模式 定义一个创建对象的接口&#xff0c;让其子类自己决定实例化哪一个工厂类&#xff0c;工厂模式使其创建过程延迟到子类进行。 2.工厂模式的作用 实现创建者和调用者的分离 3.工厂模式的分类 简单工厂模式工厂方法模式抽象工厂模式 4.工厂模式的优缺点 优…...

JavaScript语法学习--《JavaScript编程全解》

《JavaScript编程全解》 JavaScript琐碎基础 0.前言 1.RN: react native是Facebook于2015年4月开源的跨平台移动应用开发框架&#xff0c;是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物&#xff0c;支持iOS和安卓两大平台。 2.ts与js js&#xff1a;是弱…...

linux安装极狐gitlab

1. 官网寻找安装方式 不管我们使用任何软件&#xff0c;最靠谱的方式就是查看官方文档。gitlab提供了相应的安装文档&#xff0c;并且有对应的中文文档。地址如下&#xff1a; https://gitlab.cn/install/ 我在这里以CentOS作为安装示例&#xff0c;大家可根据自己的需要选择…...

软考高级信息系统项目管理(高项)原创论文——人力资源管理

人力资源管理 某市某国有装备制造公司智能安防信息管控平台项目是在公司推进企业信息化进程和实现企业可持续发展的背景下于2016年8月提出来的,我公司积极应标并最终顺利中标,而我有幸被任命为项目经理,担任起该项目的管理工作。该项目投资金额为530万元,其中软件部分为360…...

Java Lambda表达式 匿名内部类 函数式接口(FunctionalInterface)

Java Lambda表达式定义背景示例匿名类实现Lambda表达式实现对比匿名类和Lambda实现Lambda表达式&#xff08;调用&#xff09;说明Lambda表达式的语法Java 1.8 新特性&#xff1a;函数式接口jdk 1.8 自带的函数式接口 &#xff08;举例&#xff09;定义 参考Oracle官网&#x…...

javaEE 初阶 — 流量控制与拥塞控制

文章目录1. 流量控制2. 拥塞控制TCP 工作机制&#xff1a;确认应答机制 超时重传机制 连接管理机制 滑动窗口 1. 流量控制 流量控制是一种干扰发送的窗口大小的机制&#xff0c;滑动窗口&#xff0c;窗口越大&#xff0c;传输的效率就越高&#xff08;一份时间&#xff0c;…...

HTML自主学习 - 2

一、表格 基本语法 <table><tr><td>单元格内容1</td><td>单元格内容2</td><td>单元格内容3</td></tr></table> 1、<table> </table>标签用于定义表格 2、<tr> </tr>标签用于定义表格的…...

【转载】通过HAL库实现MODBUS从机程序编写与调试-----STM32CubeMX操作篇

通过HAL库实现MODBUS从机程序编写与调试-----STM32CubeMX操作篇[【STM32】RS485 Modbus协议 采集传感器数据](https://blog.csdn.net/qq_33033059/article/details/106935583)基于STM32的ModbusRtu通信--ModbusRtu协议(一)基于STM32的ModbusRtu通信--终极Demo设计(二)STM32RS48…...

【C++】string类(上)

文章目录1.为什么要学习string类2.标准库中的string类1.string分类2.string类对象的常见构造1.string3. string类对象的容量操作1.size2.capacity3.reserve4.resize扩容初始化删除数据4. string类对象的修改操作1.push_back2.append3.operator1.为什么要学习string类 c语言的字…...

Java泛型

文章目录一、泛型介绍1. 背景2. 概念3. 好处二、泛型声明泛型类型符号泛型声明方式三、类型擦除1. 什么是类型擦除桥接方法2. 为何需要类型擦除3. 类型信息并未完全擦除四、泛型使用1. 泛型类2. 泛型接口3. 泛型方法五、泛型扩展1. 泛型的上下边界泛型的上边界泛型的下边界2. 泛…...

07 分布式事务Seata使用(2)

1、Seata是什么 Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式&#xff0c;为用户打造一站式的分布式解决方案。AT模式是阿里首推的模式,阿里云上有商用版本的GTS&#x…...

c++练习题5

5.在C语言中&#xff0c;程序运行期间&#xff0c;其值不能被改变的量叫 常量 。 6.符号常量是指用一个符号名代表一个常量。 7&#xff0e;整型常量和浮点型常量也称为 数值常量 &#xff0c;它们有正负之分。 9&#xff0e;在C中&#xff0c;变量是 其值可以改变的量 。 …...

Python 高级编程之正则表达式(八)

文章目录一、概述二、正则表达式语法1&#xff09;字符匹配2&#xff09;字符集合3&#xff09;定位符4&#xff09;分组1、定义分组2、引用分组3、命名分组三、Python 的 re 模块1&#xff09;re.match() 方法2&#xff09;re.search() 方法3&#xff09;re.match() 与 re.sea…...

pynrrd常用操作解析

目录依赖安装官方文档常用操作1. 读部分nrrd.read()nrrd.read_header()nrrd.read_data()2. 写部分nrrd.write()依赖安装 pip install pynrrd官方文档 https://pynrrd.readthedocs.io/en/stable/ 常用操作 1. 读部分 nrrd.read() nrrdpath "your nrrd file path"…...

数据结构:链表基础OJ练习+带头双向循环链表的实现

目录 一.leetcode剑指 Offer II 027. 回文链表 1.问题描述 2.问题分析与求解 (1) 快慢指针法定位链表的中间节点 (2) 将链表后半部分进行反转 附:递归法反转链表 (3) 双指针法判断链表是否回文 二.带头双向循环链表的实现 1.头文件 2.节点内存申请接口和链表初始化接口…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配

目录 一、C 内存的基本概念​ 1.1 内存的物理与逻辑结构​ 1.2 C 程序的内存区域划分​ 二、栈内存分配​ 2.1 栈内存的特点​ 2.2 栈内存分配示例​ 三、堆内存分配​ 3.1 new和delete操作符​ 4.2 内存泄漏与悬空指针问题​ 4.3 new和delete的重载​ 四、智能指针…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...