【c/c++】深入探秘:C++内存管理的机制

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章我们详细讲解c++中的动态内存管理

目录

  • 1.C/C++内存分布
  • 2.C语言中动态内存管理方式:malloc/calloc/realloc/free
  • 3.c++内存管理方式
    • 3.1new/delete对内置类型的操作
      • 3.1.1抛异常
    • 3.2new/delete对自定义类型的操作
  • 4.operator new与operator delete函数
  • 5.new和delete的实现原理
  • 6.简单了解定位new表达式(placement-new)
  • 7.概念辨析
    • 7.1 malloc/free和new/delete的区别
    • 7.2 内存泄漏

1.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在哪里?__1__   staticGlobalVar在哪里?__2__staticVar在哪里?__3__   localVar在哪里?__4__num1 在哪里?__5__char2在哪里?__6__   *char2在哪里?_7__pChar3在哪里?__8__      *pChar3在哪里?__9__ptr1在哪里?_10___        *ptr1在哪里?__11__

我们来依次讨论:

  1. globalVar 是全局变量,不是静态的,所以它存储在数据段(静态区)
  2. staticGlobalVar 也是全局变量,但它是静态的,因此它同样存储在数据段(静态区)
  3. staticVar 是函数内的静态变量,所以它存储在数据段(静态区),因为它的生命周期贯穿程序的整个执行期
  4. localVar 是局部变量,存储在栈上
  5. num1 是局部变量,它是数组,存储在栈上
  6. char2 是局部变量,它是数组首元素的地址,存储在栈上
  7. *char2(即char2数组的内容)存储在栈上,因为char2本身就在栈上
  8. pChar3 是局部指针变量,存储在
  9. *pChar3 指向的内容(即字符串"abcd")存储在代码段(常量区)
  10. ptr1 是局部指针变量,存储在
  11. *ptr1 指向的内容(即通过malloc分配的内存)存储在
  • *char2(局部字符数组)
    当你声明一个局部字符数组并用一个字符串字面量初始化它,如char char2[] = "abcd";时,编译器在栈上为数组分配内存,然后将字符串字面量的内容(包括结尾的\0字符)复制到这块内存中。因此,char2和它的内容(*char2指向的内容)都存储在栈上

  • *pChar3(字符串字面量指针)
    另一方面,当你使用指针指向一个字符串字面量,如const char* pChar3 = "abcd";时,这个字符串字面量存储在程序的只读数据段(或称为代码段、常量区)中。pChar3本身作为一个局部指针变量存储在栈上,但它指向的字符串(“abcd”)实际上存储在常量区。这是因为字符串字面量被视为常量数据,编译器会将它们放在程序的常量区域内,这个区域通常是只读的,以防止程序意外修改它的内容。因此,尽管pChar3是一个指针,存储在栈上,但它指向的字符串内容存储在常量区

总结

  • *char2不在常量区,因为char2是局部字符数组,其内容直接存储在栈上。
  • *pChar3在常量区,因为它指向的是一个字符串字面量,字符串字面量被存储在程序的常量区域,这部分内存是只读的。

当我们讨论变量存储在哪里时,通常涉及到几个关键区域:栈(Stack)、堆(Heap)、数据段(Data Segment,又称静态区)、和代码段(Code Segment,又称常量区)。每种类型的变量根据其特性和声明周期被存储在这些区域中的相应位置

  • 是用于存储局部变量、函数参数等的内存区域。当一个函数被调用时,其局部变量和一些书keeping信息被推入栈中;当函数执行完成,这些信息被从栈上弹出。栈是自动管理的,开发者无需手动分配或释放内存。

  • 是用于动态内存分配的内存区域。不同于栈,开发者需要显式地从堆上分配内存(如使用mallocnew),并在不再需要时释放这些内存(如使用freedelete)。

  • 数据段,又称为静态区,用于存储全局变量、静态变量等。这些变量的生命周期贯穿整个程序执行期,因此它们被存储在一个特定的、持久的内存区域中。

  • 代码段,又称为常量区,用于存储程序的执行代码和常量数据,如字符串字面量。这部分内存是只读的,用来保证程序代码的安全性

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

在C语言中,动态内存管理是通过一组标准库函数完成的,包括malloc, calloc, realloc, 和 free。这些函数允许程序在运行时动态地分配、调整和释放堆内存,这是对于管理变化的数据量和大小特别有用的能力。下面是这些函数的基本用法和它们之间的区别:

malloc

  • 用法void* malloc(size_t size);
  • 功能:分配指定字节数的未初始化内存。它返回一个指向分配的内存的指针。如果分配失败,返回NULL
  • 示例int* ptr = (int*)malloc(sizeof(int) * 4); 这行代码为4个整数分配了内存

calloc

  • 用法void* calloc(size_t num, size_t size);
  • 功能:为指定数量的元素分配内存,每个元素的大小也在参数中指定,并自动初始化所有位为0。如果分配失败,返回NULL
  • 示例int* ptr = (int*)calloc(4, sizeof(int)); 这行代码为4个整数分配了内存,并将它们初始化为0。

realloc

  • 用法void* realloc(void* ptr, size_t size);
  • 功能调整之前调用malloccalloc分配的内存块的大小。如果新的大小大于原始大小,可能会移动内存块到新的位置以提供足够的连续空间。如果realloc的第一个参数是NULL,它的行为就像malloc
  • 示例ptr = (int*)realloc(ptr, sizeof(int) * 8); 这行代码将之前分配的内存大小调整为8个整数的大小。

free

  • 用法void free(void* ptr);
  • 功能:释放之前通过malloc, calloc, 或 realloc分配的内存。一旦内存被释放,那块内存就不能再被访问了。
  • 注意:尝试释放未经分配的内存块或多次释放同一个内存块是不安全的,可能导致未定义行为

注意

  • 在使用这些函数时,确保正确处理内存分配失败的情况,并在内存不再需要时使用free来避免内存泄露。
  • 当使用realloc时,如果分配失败,原始内存不会被释放。因此,建议先将realloc的返回值赋给一个临时指针,以检查是否分配成功,再重新赋值给原始指针,以避免内存泄漏。
  • 始终确保只对通过malloc, calloc, 或 realloc分配的指针使用free,并且每个分配的内存块只被free一次

3.c++内存管理方式

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

3.1new/delete对内置类型的操作

new的基本用法

Type* variable = new Type(arguments);
  • Type:要分配的对象类型
  • variable:指向分配的内存的指针
  • arguments:传递给构造函数的参数(如果需要的话)

示例

int* ptr1 = new int; 

在堆上分配了一个int大小的内存

int* ptr2 = new int[10]; 

加上方括号[ ]表示分配了十个int大小的内存

释放:

对于ptr,我们直接delete

delete ptr1;

释放数组对象的内存ptr2,我们需要加上方括号:

delete [] ptr2;

我们也可以分配内存的同时直接初始化:

int* ptr5 = new int(5);

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

在这里插入图片描述
我们也可以同时开辟多个空间完成初始化:

int* ptr6 = new int[10] {1,2,3,4,5};

在这里插入图片描述
后面的空间默认初始化为零

  • 尽管newdelete提供了对象构造和析构的自动管理,但程序员仍然需要负责确保每个用new分配的内存都被对应的delete释放,以避免内存泄露
  • mallocfree一样,试图delete一个未经new分配的指针,或者对同一个指针执行多次delete,都是未定义行为,并且可能导致程序崩溃
  • 当使用new[]分配数组时,必须使用对应的delete[]来释放内存。使用错误的delete形式也是未定义行为

来看下面的代码:

struct ListNode
{ListNode* _next;int _val;ListNode(int val):_next(nullptr),_val(val){}
};struct ListNode* CreateListNode(int val)
{struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->_next = NULL;newnode->_val = val;return newnode;
}

这是c语言构造一个节点并完成初始化的过程,我们来看c++的实现:

int main()
{ListNode* node1 = new ListNode(1);return 0;
}

这行代码自动为ListNode对象分配了内存,并调用了其构造函数进行初始化。这种方式更简洁,也更安全,因为它保证了对象在使用前被正确初始化,注意这里ListNode是自定义类型,除了开空间还会调用构造函数

在这里插入图片描述
在这里插入图片描述

只要我们写好构造函数,我们发现new的使用是十分方便的

我们来构建一个链表:

ListNode* CreateList(int n)
{ListNode head(-1);  // 哨兵位ListNode* tail = &head;int val;printf("请依次输入%d个节点的值:>", n);for (size_t i = 0; i < n; i++){cin >> val;tail->_next = new ListNode(val);tail = tail->_next;}return head._next;
}

我们输入五个值,1 2 3 4 5
在这里插入图片描述

哨兵节点:ListNode head(-1);这行代码创建了一个局部的哨兵节点,它的值被设为-1(这个值通常是任意的,因为哨兵节点本身不存储任何有意义的数据)。哨兵节点的主要目的是简化在链表头部的插入和删除操作,因为你总是有一个非空的节点作为链表的起始点,从而避免了处理空链表的特殊情况

最后,函数通过return head._next;返回新构建链表的头节点。由于head是一个哨兵节点,它的_next成员实际上指向链表的第一个真实节点(如果有的话)或者是nullptr(如果n为0或用户没有输入任何有效数据)

3.1.1抛异常

我们不用手动检查new是否开辟成功new失败了会抛出异常

void func()
{int n = 1;while (1){int* p = new int[1024 * 1024*100];cout <<n<<"->"<< p << endl;++n;}
}

我们一次申请400M的空间大小
在这里插入图片描述
再看c语言版本

void func()
{int n = 1;while (1){//int* p = new int[1024 * 1024 * 100];int* p = (int*)malloc(1024 * 1024 * 400);cout << n << "->" << p << endl;++n;}
}

在这里插入图片描述
开辟失败,程序无限循环并返回空

c++中的抛异常:

try{func();}catch (const exception& e){cout << e.what() << endl;}

这段代码是C++中的一个示例,展示了如何使用try-catch语句来处理异常。这里的重点是捕获并处理func()函数中可能抛出的异常。如果func()函数执行中出现了问题,它将抛出一个异常,这个异常会被catch块捕获。捕获到的异常类型为const std::exception&,这是C++标准异常类型的一个基类。在catch块中,通过e.what()调用来获取并打印出异常的具体信息

  • try块:在try块中的代码执行时,如果发生了异常(即代码抛出了异常),那么try块中的剩余代码将不会继续执行,而是跳转到相应的catch块中处理异常

  • catch块:此代码段用于捕获类型为const std::exception&的异常。这意味着它能够捕获任何是std::exception实例或其派生类的异常。通过常量引用捕获异常是一种最佳实践,因为这样可以避免异常对象的切片问题,并且可以最小化性能开销

  • const exception& e:这里声明了一个名为e的引用,它引用了被捕获的异常。const限定符表明在catch块中,e是不会被修改的

  • e.what()std::exception及其派生类有一个名为what()的成员函数,它返回一个描述异常的空终止字符序列(C风格字符串)。cout << e.what() << endl;语句将这个消息打印到标准输出中

后续我们还会遇到这个函数,再详细讲解

来看抛异常的结果:
在这里插入图片描述

3.2new/delete对自定义类型的操作

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

new/deletemalloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数

	A* p1 = new A(1);
00007FF798AA260B  mov         ecx,4  
00007FF798AA2610  call        operator new (07FF798AA104Bh)  
00007FF798AA2615  mov         qword ptr [rbp+108h],rax  
00007FF798AA261C  cmp         qword ptr [rbp+108h],0  
00007FF798AA2624  je          main+50h (07FF798AA2640h)  
00007FF798AA2626  mov         edx,1  
00007FF798AA262B  mov         rcx,qword ptr [rbp+108h]  
00007FF798AA2632  call        A::A (07FF798AA1343h)  
00007FF798AA2637  mov         qword ptr [rbp+138h],rax  
00007FF798AA263E  jmp         main+5Bh (07FF798AA264Bh)  
00007FF798AA2640  mov         qword ptr [rbp+138h],0  
00007FF798AA264B  mov         rax,qword ptr [rbp+138h]  
00007FF798AA2652  mov         qword ptr [rbp+0E8h],rax  
00007FF798AA2659  mov         rax,qword ptr [rbp+0E8h]  
00007FF798AA2660  mov         qword ptr [p1],rax

在这里插入图片描述
new过程跳转到构造函数
在这里插入图片描述
delete调用析构函数

打印结果如下:

A():000001DB79796B50
~A():000001DB79796B50

我们发现,汇编代码中有这一步:

00007FF798AA2610  call        operator new (07FF798AA104Bh)

operator new,接下来我们来讲解这一部分

4.operator new与operator delete函数

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

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{void* p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}

operator new该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

 static const std::bad_alloc nomem;

申请失败则会抛异常

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_dbg(pUserData, pHead->nBlockUse);

operator delete: 该函数最终是通过free来释放空间的

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

我们只需要简单了解一下,并不需要深入理解

5.new和delete的实现原理

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

自定义类型:

  • new的原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{A* p1 = new A(1);delete p1;return 0;
}

在这里插入图片描述

  • delete的原理
    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间
class Stack
{
public:Stack(){_a = (int*)malloc(sizeof(int) * 4);_top = 0;_capacity = 4;}~Stack(){free(_a);_top = _capacity = 0;}
private:int* _a;int _top;int _capacity;
};
int main()
{Stack* pst = new Stack;delete pst;return 0;
}

这里进行了双层嵌套:
在这里插入图片描述
我们就很清楚的能看到,现需要调用析构函数再进行释放

  • new T[N]的原理

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

    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{A* p1 = new A;A* p2 = new A[10];delete p1;delete[]p2;return 0;
}

在这里插入图片描述
在这段代码中,p2 是指向由 new A[10] 分配的对象数组的指针。虽然你可能会认为 p2 只需要分配足够存储 10 个 A 类型对象的空间,即 10 * sizeof(A)实际上编译器通常会分配额外的空间来存储有关数组本身的信息,比如数组的大小。这是因为在执行 delete[] p2; 时,系统需要知道要调用多少次析构函数
在这里插入图片描述

让我们具体看一下为什么会这样:

  1. 对象数组的内存分配:当你创建一个对象数组时,例如 new A[10],C++ 需要知道在稍后释放数组时应该调用多少次析构函数。为此,它可能在分配给数组的内存块中存储一些额外的元数据,通常是数组的长度

  2. 析构函数调用:在使用 delete[] p2; 释放内存时,这个额外存储的信息就被用来确保为数组中的每个元素正确调用析构函数

  3. 内存布局:因此,分配给 p2 的内存实际上包含了更多比简单的 10 * sizeof(A) 字节。首先是数组长度的元数据(大小取决于系统和编译器),紧接着是 10 个 A 类型对象的存储空间

  4. 字节大小:如果 sizeof(A) 是 4(假设 int 类型是 4 字节,并且没有类对齐导致的额外空间),那么仅对象部分就占用了 40 字节。加上存储数组大小的额外空间,总大小就会超过 40 字节

在这里插入图片描述

我们再来看内置类型:

int* p1=new int[10];
00007FF7F031206B  mov         ecx,28h  

刚好开辟了四十个字节的空间,因为它不需要调用析构函数

6.简单了解定位new表达式(placement-new)

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

使用格式:
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()
{int* p1 = new int[10];return 0;
}
A* p1 = (A*)malloc(sizeof(A));

p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行

new(p1)A;

显示调用构造函数对一块已经有的空间的初始化

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

7.概念辨析

7.1 malloc/free和new/delete的区别

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

7.2 内存泄漏

  • 什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
  • 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死

分类:

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

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

本节内容到此结束!!求大家三连啊!

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

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

相关文章

【how2j练习题】JS部分阶段练习

练习题 1 <!-- 练习&#xff1a;自定义一个函数&#xff0c;对数组进行排序&#xff0c;要求排序后没有重复数据 --> <script>function p(s){document.write(s);document.write("<br>");}function uniquel(arr){ var hash[];for(var i 0;i<arr.…

Java源值1.5已过时,将在未来所有发行版中删除

1、背景 确认java项目没问题&#xff0c;但是启动的时候&#xff0c;却报错&#xff1a;java: -source 1.5 中不支持 diamond 运算符 2、解决 2.1 2.2 2.3 2.4 2.5

拓数派向量计算引擎PieCloudVector助力东吴证券AIGC应用升级

1.项目背景 随着人工智能技术的不断创新和应用&#xff0c;我们可以看到人工智能在各个领域的应用越来越广泛。深度学习技术在图像识别、语音识别、自然语言处理等领域表现出色。机器学习算法的改进将解决更多实际问题&#xff0c;如增强学习、迁移学习和联合学习等&#xff0…

蓝桥杯 - 受伤的皇后

解题思路&#xff1a; 递归 回溯&#xff08;n皇后问题的变种&#xff09; 在 N 皇后问题的解决方案中&#xff0c;我们是从棋盘的顶部向底部逐行放置皇后的&#xff0c;这意味着在任何给定时间&#xff0c;所有未来的行&#xff08;即当前行之下的所有行&#xff09;都还没…

算法学习——LeetCode力扣动态规划篇9(1035. 不相交的线、53. 最大子数组和、392. 判断子序列、115. 不同的子序列)

算法学习——LeetCode力扣动态规划篇9 1035. 不相交的线 1035. 不相交的线 - 力扣&#xff08;LeetCode&#xff09; 描述 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#x…

matlab使用教程(33)—求解时滞微分方程(1)

1.时滞微分方程(DDE)的分类 时滞微分方程 (DDE) 是当前时间的解与过去时间的解相关的常微分方程。该时滞可以固定不变、与时间相关、与状态相关或与导数相关。要开始积分&#xff0c;通常必须提供历史解&#xff0c;以便求解器可以获取初始积分点之前的时间的解。 1.1常时滞 D…

【MATLAB源码-第176期】基于matlab的16QAM调制解调系统频偏估计及补偿算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在通信系统中&#xff0c;频率偏移是一种常见的问题&#xff0c;它会导致接收到的信号频率与发送信号的频率不完全匹配&#xff0c;进而影响通信质量。在调制技术中&#xff0c;QPSK&#xff08;Quadrature Phase Shift Keyin…

LeetCode每日一题之专题一:双指针 ——复写零

复写零OJ链接&#xff1a;1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 解法&#xff08;原地复写-双指针&#xff09;&#xff1a; 算法思路&#xff1a; 如果「从前向后」进⾏原地复写操作的话&#xff0c;由于 0 的出现会复写两次&#xff0c;导致…

java 数据结构 Map和Set

目录 搜索树 操作-查找 操作-插入 操作-删除&#xff08;难点&#xff09; Map Map 的常用方法 Set 哈希表 哈希函数 哈希冲突 冲突-避免-负载因子调节&#xff08;重点掌握&#xff09; 冲突-解决 冲突-解决-开散列/哈希桶(重点掌握) 实现HashBuck类 put方法 …

C#清空窗体的背景图片

目录 一、涉及到的知识点 1.设置窗体的背景图 2.加载窗体背景图 3.清空窗体的背景图 二、 示例 一、涉及到的知识点 1.设置窗体的背景图 详见本文作者的其他文章&#xff1a;C#手动改变自制窗体的大小-CSDN博客 https://wenchm.blog.csdn.net/article/details/137027140…

AI结合机器人的入门级仿真环境有哪些?

由于使用真实的机器人开发和测试应用程序既昂贵又费时&#xff0c;因此仿真已成为机器人应用程序开发中越来越重要的部分。在部署到机器人之前在仿真中验证应用程序可以通过尽早发现潜在问题来缩短迭代时间。通过模拟&#xff0c;还可以更轻松地测试在现实世界中可能过于危险的…

C# 登录界面代码

背景 MVVM 是一种软件架构模式&#xff0c;用于创建用户界面。它将用户界面&#xff08;View&#xff09;、业务逻辑&#xff08;ViewModel&#xff09;和数据模型&#xff08;Model&#xff09;分离开来&#xff0c;以提高代码的可维护性和可测试性。 MainWindow 类是 View&a…

【星计划★C语言】c语言初相识:探索编程之路

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;星计划★C语言、Linux实践室 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️第一个c语言程序二. ⛳️数据类型2.1 &#x1f514;数据单位2.2 &…

哲♂学家带你深♂入了解动态顺序表

前言&#xff1a; 最近本哲♂学家学习了顺序表&#xff0c;下面我给大家分享一下关于顺序表的知识。 一、什么是顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组 上完成数据的增删查改。 顺序表&#xff…

C++从入门到精通——入门知识

1. C关键字(C98) C总计63个关键字&#xff0c;C语言32个关键字 2. 命名空间 在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、函数和类的名称都将存在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的就是对标识符的名…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(8)复发

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 B站-PS异闻录&#xff1a;萌新系统入门课课程视频 …

大模型论文阅读:ADAPTIVE BUDGET ALLOCATION FOR PARAMETEREFFICIENT FINE-TUNING

大模型论文阅读:ADAPTIVE BUDGET ALLOCATION FOR PARAMETEREFFICIENT FINE-TUNING 论文链接:https://arxiv.org/pdf/2303.10512v1.pdf 当存在大量下游任务时,微调所有预训练模型的参数变得不可行。因此,为了以参数高效的方式学习预训练权重的增量更新,提出了许多微调方法,…

【并发编程】CountDownLatch

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程 ⛺️稳中求进&#xff0c;晒太阳 CountDownLatch 概念 CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。 CountDownLatch 定义了一个计数器&#xff0c;…

【每日一道算法题】移除链表节点

这里写自定义目录标题 【每日一道算法题】移除链表元素思路记录我的代码力扣官方题解递归迭代 【每日一道算法题】移除链表元素 力扣题目链接(opens new window) 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xf…

arm的状态寄存器

目录 一、arm 的 PSRs二、CPSR2.1 CPSR_cxsf 三、SPSR四、APSR 一、arm 的 PSRs arm 中有很多程序状态寄存器&#xff08;Program Status Registers&#xff0c;PSRs&#xff09;用于存储处理器的状态信息&#xff0c;包括 CPSR\SPSR\FPSR\APSR 等&#xff1a; CPSR&#xff…