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

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理

【心法】
【第零章】c语言概述
【第一章】分支与循环语句
【第二章】函数
【第三章】数组
【第四章】操作符
【第五章】指针
【第六章】结构体
【第七章】const与c语言中一些错误代码
【禁忌秘术】
【第一式】数据的存储
【第二式】指针
【第三式】字符函数和字符串函数
【第四式】自定义类型详解(结构体、枚举、联合)
【第五式】动态内存管理


文章目录

  • c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理
  • 前言
  • 一、为什么存在动态内存分配
  • 二、动态内存函数的介绍
    • 1. malloc和free
    • 2. calloc
    • 3. realloc
  • 三、常见的动态内存错误
    • 1. 对NULL指针的解引用操作
    • 2. 对动态开辟空间的越界访问
    • 3. 对非动态开辟内存使用free释放
    • 4. 使用free释放一块动态开辟内存的一部分
    • 5. 对同一块动态内存多次释放
    • 6. 动态开辟内存忘记释放(内存泄漏)
  • 四、使用动态内存修改通讯录
  • 五、几个经典笔试题
    • 1. 题目1
    • 2. 题目2
    • 3. 题目3
    • 4. 题目4
  • 六、C/C++程序的内存开辟
  • 七、柔性数组
    • 1. 柔性数组的特点
    • 2. 柔性数组的使用
    • 3. 柔性数组的优势
  • 总结


前言

大家在使用数组时肯定都会有过这样的疑问,如果一个数组的长度不足了,可以延长这个数组吗?使其能够满足存储的需求。又或者是现在不确定数组应该设为多长,先把它放着,等之后再设置它的长度。
上面的这些需求,数组显然是无法实现的,那么c语言中有什么东西能够做到这一点呢?
动态内存分配!你现在不知道需要多大的空间,没关系,之后你明确了的时候,使用动态内存分配,将你要的空间分配给即可。

本章重点:

  • 为什么存在动态内存分配
  • 动态内存函数的介绍
    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存错误

一、为什么存在动态内存分配

此前我们掌握的内存开辟方式有:

int val = 20; // 在栈空间开辟4个字节的空间
char arr[10] = { 0 }; // 在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:

  • 空间开辟大小的固定的;
  • 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但正如前言中说的,在处理实际问题时,我们对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小只有在程序运行的时候才知道,这时候就只能试试动态内存开辟了。

二、动态内存函数的介绍

1. malloc和free

c语言提供了一个动态内存开辟的函数malloc

void* malloc(size_t size);

在这里插入图片描述

  • 这个函数的功能是分配一块内存空间,并返回这片空间的起始地址;
  • 这片空间的大小由参数size指定,单位为字节;
  • 这片新分配的空间并未初始化,保存的数据未知;
  • 如果参数size为0时,这种行为未被定义,取决于编译器;
  • 如果该函数分配空间成功,则会返回这块空间的地址;这个指针的类型为void *是因为它可以强制转换为任何所需的类型以满足解引用的需求;如果分配空间失败则会返回一个NULL指针;

c语言还提供了另一个函数free

void free(void*)

在这里插入图片描述

  • 它的功能是回收一片动态分配的内存空间;
  • 它可以回收malloccalloc或者是realloc函数分配的空间,使得这片空间又能被再次分配;
  • 如果`ptr``指向的空间不是动态分配的内存空间,这种行为是未定义的;
  • 如果ptr是一个NULL指针,这个函数什么都不会做(不是错误);
  • 注意,这个函数并不会改变ptr这个指针本身的内容,也就是说指针ptr仍指向这片空间(变成了野指针);

举个例子:

#include <stdio.h>
#include <stdlib.h> // 动态分配函数必须包含库int main()
{// 代码1int num = 0;scanf("%d", &num);// 要在程序执行过程中,从用户处获得输入,设置一个用户指定大小的数组int arr[num] = { 0 }; // error,数组的定义[]中的值应该是一个常量// 代码2int* ptr = NULL;ptr = (int*)malloc(num * sizeof(int)); // malloc函数的单位为字节if (ptr == NULL) // 内存分配失败{perror("malloc"); // 输出错误信息,方便调试return;}// 内存分配成功后执行逻辑int i = 0;for (i = 0; i < num; i++){*(ptr + i) = 0;}// 内存使用完毕// 回收空间free(ptr);ptr = NULL; // free函数并不会改变ptr本身的值,需要程序员手动将指针置空return 0;
}

在这里插入图片描述

注意
在最后的ptr = NULL这条语句是非常有必要的,因为没有这条语句,ptr此时已经变成了野指针,这是十分危险的;

2. calloc

除了能使用malloc函数进行内存分配之外,c语言还提供了calloc函数来进行内存分配;

void* calloc(size_t num, size_t size);

大家可能会疑惑,我们已经有malloc函数,为什么还要来一个calloc呢?
请接着往下看:
在这里插入图片描述

  • 这个函数的功能是分配一片空间,并对这片空间进行初始化(初始化为0);
  • calloc将会为num个元素分配一片内存空间,每个元素占size个字节,并在分配空间之后,以字节为单位将这片空间的值初始化为0;
  • 这个函数的有效结果就是分配一片初值为0的,有num * size这么多字节的空间;
  • 如果size为0,它的返回值依赖于特定的库实现(不一定是NULL指针),但这个指针一定不能被解引用;
  • 两个参数,num表示元素的个数,size表示一个元素占的字节数;
  • 如果分配成功,函数会返回一个指向这片空间的指针,指针的类型为void *,它可以根据需求,由使用者自由的强制转换成需要的类型,并进行解引用操作;
  • 如果分配失败,将会返回一个NULL指针;

从上面的解读中,我们就能知道,malloc函数仅仅只是分配了这么一片空间,但其中的值是没有被初始化的,calloc函数能够在分配空间的同时,将这片空间的每个字节都初始化为0;

#include <stdio.h>
#include <stdlib.h>int main()
{int *ptr1 = (int*)malloc(10 * sizeof(int));if (ptr1 == NULL){perror("malloc");return;}int *ptr2 = (int*)calloc(10, sizeof(int));if (ptr2 == NULL){perror("calloc");}free(ptr1);ptr1 == NULL;free(ptr2);ptr2 = NULL;return 0;
}

在这里插入图片描述
所以当我们对申请的空间的内容有初始化的要求时,就可以使用calloc函数很方便的完成了;

3. realloc

有了上面的两个内存分配函数,我们能够在程序执行中根据需求为变量分配内存空间;
但是仔细想想,当我们在分配完空间之后,这片空间仍不足以满足需求呢?这时应该怎么办?
是再使用malloccalloc函数重新分配一片空间,并将原来的空间回收吗?
这样是否有点麻烦,有没有其他方法?
有的,兄弟有的。
使用realloc函数;

void* realloc(void* ptr, size_t size);

在这里插入图片描述

  • realloc函数可以重新分配动态分配的空间,改变ptr指向空间的大小;
  • 这个函数可能会分配一片新的内存空间(返回值指向的空间);
  • 这片空间的内容会保留到新大小和旧大小中较小的那个,即使这片空间是一片新分配的空间;如果新大小更大,新分配空间中的内容是未定义的;
  • 如果参数ptrNULL指针,这个函数的行为和malloc相同,分配一片大小为size字节的空间,并返回一个指向它的指针;
  • 在C90标准中,如果参数size为0,调用该函数的行为就像调用了free一样,它会释放掉ptr指向的空间,并返回一个NULL指针;在C99/C11标准中,则取决于特定的库实现,该行为未定义;
  • 返回值有两种情况:
    1. 返回的指针和ptr相同;
    2. 返回的指针和ptr不同,是一个新的地址;
  • 返回指针的类型为void*,原因和前面的相同;
  • C90标准中,有两种情况会返回NULL指针:参数size为0;空间分配失败;
  • C99/C11标准中,只有空间分配失败会返回NULL指针。参数size为0的这种行为未定义;

在这里插入图片描述

看下面代码:

#include <stdio.h>
#include <stdlib.h>int main()
{int* ptr = (int*)malloc(100);if (ptr == NULL){perror("malloc");return;}// 业务处理// 扩充容量// 代码1ptr = (int*)realloc(ptr, 1000); // 代码2int* p = (int*)realloc(ptr, 1000);if (p == NULL){perror("realloc");return;}ptr = p;// 业务处理free(ptr);ptr = NULL;return 0;
}

上面代码中代码1和代码2正确吗,应该用哪个?

根据上面的函数解读,我们知道realloc函数是可能出现分配失败的情况的。
在代码1中,如果realloc失败,它会返回NULL指针,此时直接将ptr设为了NULL指针,这就导致了内存泄漏,之后再也找不到ptr指向的空间了;
所以代码1是不可行的,应该使用代码2,在调用realloc之后,先判断它的返回值,看看空间是否分配成功,成功之后,才将分配的地址赋值给ptr;

三、常见的动态内存错误

1. 对NULL指针的解引用操作

#include <limits.h>void test()
{int* p = (int *)malloc(INT_MAX / 4);*p = 20;free(p);p = NULL;
}

这段代码正确吗?
错误,因为malloc函数可能返回NULL指针,当空间分配失败时,ptr指针为NULL指针,对NULL指针解引用是错误的;

2. 对动态开辟空间的越界访问

#include <stdio.h>void test()
{int i = 0;int *p = (int *)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return;}for (i = 0; i < 11; i++){*(p + i) = 1;}free(p);p = NULL;
}

这段代码也是有问题的,
malloc函数只为p分配了10个int类型的空间,循环会访问到第十一个int类型的元素,此时产生了越界;

3. 对非动态开辟内存使用free释放

#include <stdlib.h>void test()
{int arr[10] = { 0 };int *p = arr;free(p);
}

这段代码就是使用了free函数释放不是动态分配的内存空间,此时也会出错;

4. 使用free释放一块动态开辟内存的一部分

#include <stdio.h>
#include <stdlib.h>void test()
{int *p = (int *)malloc(10 * sizeof(int));if (p == NULL){	perror("malloc");return ;}int i = 0;for (i = 0; i < 5; i++){*(p++) = i;}free(p);p = NULL;
}

这段代码同样是有问题的,在循环中,指针p指向的位置已经发生了变化,此时指针p并没有指向动态分配的内存空间的起始位置,此时使用free函数来释放动态分配的空间,仍有一部分没有释放,所以这个代码是错误的;

5. 对同一块动态内存多次释放

#include <stdio.h>
#include <stdlib.h>void test()
{int *p = (int *)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return ;}// 业务实现free(p);// 业务实现free(p);
}

这种代码也是错误的,它对同一片动态内存空间释放了两次;
虽然,上面的代码不太可能会出现(毕竟没有谁会在同一个函数中对一片动态分配的内存空间释放多次),但这样的错误的可能会出现的;
动态分配的空间,只有两种情况会释放,程序结束、手动free;有可能出现,你在这个函数中将这个空间释放了一次,在另一个函数中又对它释放了一次,这时就出现了多次释放的错误;

有方法可以规避这种错误:

free(p);
p = NULL;

上面的这两行代码就能规避多次释放的问题,还记得free函数的特点吗?
当free函数的参数为NULL指针时,调用这个函数什么也不会发生。
在每次释放指针指向的动态内存空间之后,紧接着将这个指针置为NULL指针,能够有效的规避这种错误的出现;

6. 动态开辟内存忘记释放(内存泄漏)

#include <stdio.h>
#include <stdlib.h>void test()
{int *p = (int *)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return ;}// 业务处理// 忘记释放动态内存空间// 出现内存泄漏
}int main()
{test();// 业务处理return 0;
}

上述代码中,主函数调用了test函数,动态分配了一个内存空间给指针p,在test函数退出时,并没有释放这片空间,这片空间就一直留存在堆区中,且指针p是test函数的局部变量,在test弹出之后,这个变量就销毁了,这片动态分配的空间也就没有人能够找到了,所以这片空间在之后的程序中是无法再被使用的,只有在整个程序结束时,这片空间才会还给OS,这就产生了内存泄漏问题。
可能有人会这没什么,反正程序结束,这片空间会归还给OS的,我们不用管;
但是试想一下,这个程序如果是一个服务器程序呢?它需要长期的执行,每次调用test函数都会泄漏一部分的内存空间,硬件资源总是有限的,那么这个程序在运行一段时间之后就一定会因为内存资源不足而挂掉,如果2天这个程序就把内存消耗光了,就得2天重启一次程序,这会带来很大的损失;

忘记释放不再使用的动态开辟的空间会造成内存泄漏
切记
动态开辟的空间一定要释放,并且要正确的释放

四、使用动态内存修改通讯录

在之前的通讯录结构体类型中,不管什么情况,通讯录都能保存1000个人的信息,如果这个人他只有100个朋友的信息需要保存,他使用这个通讯录会浪费剩下的900个空间,这会造成内存资源很大的浪费;如果他需要保存的信息超过1000个,那么这个通讯录就无法满足他的需求;
这时我们就可以使用动态内存来替换掉之前使用的静态分配的空间,在程序执行过程中,根据用户的需要增加通讯录的空间,即满足了用户要保存人的信息的需求,又减少了系统资源的开销;

// 静态版本
// 定义结构体、标识符常量
#define MAXNUM 1000 // 通讯录中能保存的人数typedef struct
{int age; // 年龄char name[20]; // 名字char sex[7]; // 性别char phone_number[20]; // 电话号码char address[30]; // 地址
} Peoinfo; // 保存人的信息typedef struct
{Peoinfo data[MAXNUM]; // 通讯录可以保存1000个人的信息int count; // 当前通讯录中的人数
} Contact;

修改后的代码,有改动的代码

// 动态内存版本
#define INIT_COUNT 3 // 通讯录初始能保存3个人的信息
#define INCREASE_COUNT 2 // 每次扩容增加数量typedef struct
{int age; // 年龄char name[20]; // 名字char sex[7]; // 性别char phone_number[20]; // 电话号码char address[30]; // 地址
} Peoinfo; // 保存人的信息// 使用指针指向保存人的信息的空间
typedef struct
{Peoinfo* data; // 指向保存人信息的空间int count; // 当前通讯录中的人数int num; // 当前通讯录中能保存的最大人数
} Contact;
void initContact(Contact* contact)
{assert(contact);// 动态分配 INIT_COUNT * sizeof(Peoinfo) 个字节的空间contact->data = (Contact*)calloc(INIT_COUNT, sizeof(Peoinfo));contact->count = 0;contact->num = INIT_COUNT;
}static void increase_Capacity(Contact* contact)
{// 为通讯录的保存人数扩容Contact* ptr = (Contact*)realloc(contact->data, ((contact->num) + INCREASE_COUNT) * sizeof(Peoinfo));// 空间分配失败if (ptr == NULL){perror("checkCapacity, realloc");return;}contact->data = ptr; // 将重新分配的空间的地址赋值给contactcontact->num += INCREASE_COUNT;
}void Add(Contact* contact)
{assert(contact);if (isFull(contact)){increase_Capacity(contact);printf("已扩容\n");}getchar();// 没有做溢出检查printf("输入名字:>");gets((contact->data[contact->count]).name);printf("输入年龄:>");scanf("%d", &((contact->data[contact->count]).age));getchar();printf("输入性别:>");gets((contact->data[contact->count]).sex);//getchar();printf("输入电话:>");gets((contact->data[contact->count]).phone_number);//getchar();printf("输入地址:>");gets((contact->data[contact->count]).address);(contact->count)++;
}

别忘了要手动释放动态开辟的内存空间

case exit:free(contact.data);contact.data = NULL;printf("退出通讯录\n");break;

五、几个经典笔试题

1. 题目1

#include <stdio.h>
#include <string.h>void GetMemory(char* p)
{p = (char*)malloc(100);
}
void Test()
{char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}int main()
{// 调用Test函数会有什么样的结果?Test();return 0;
}

分析:
根据代码意思,这段代码的目的是在Test函数中定义了一个char *的变量,初始化为NULL指针,调用GetMemorystr动态分配一片空间,之后将“hello world”这个字符串复制到这片动态开辟的空间中;
但事实真的如此吗?
来看看运行结果:
在这里插入图片描述
为什么会这样呢?
我们不是将一个指针作为参数吗?这不是传址调用吗?
真的吗?真的是传址调用吗?
注意,虽然GetMemory的参数是指针,且传入的实参也是指针,但是GetMemory是对这个指针本身进行修改,并不是修改这个指针指向的内容;形参pstr的一份临时拷贝,这个函数是一个传值调用,函数内部的操作是不会对实参本身产生任何影响的;
注意不要看到函数的参数是指针就把它当成传址调用,应该结合这个指针在函数中执行什么样的操作来判断

2. 题目2

#include <stdio.h>char* GetMemory()
{char p[] = "hello world";return p;
}void Test()
{char *str = NULL;str = GetMemory();printf(str);
}int main()
{Test();return 0;
}

分析:
上面代码的目的应该是,Test函数通过调用GetMemory函数来获得"hello world"这个字符串的地址,并在Test中输出它;
但结果真的如此吗?
并不是对吧,注意GetMemory函数中的字符数组p是一个局部变量,且没有static修饰,并不是静态变量,所以这个变量的生命周期只在GetMemory函数中,离开函数这个变量就销毁了,所以当Test函数使用这个地址时,其实它已经变成了一个野指针,这片空间指向的内容是什么完全确认;

运行结果:
在这里插入图片描述

可以看到结果和分析的相同,这个地址指向内容变成了随机值;

3. 题目3

#include <stdio.h>void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}void Test()
{char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}int main()
{Test();return 0;
}

分析:
GetMemory函数中动态开辟了一片空间,并通过指针操作,将Test函数传给GetMemory的参数str,此时是一个传址调用,此时的代码是符合设计的;
运行结果:
在这里插入图片描述

4. 题目4

#include <stdio.h>
#include <stdlib.h>void Test()
{char *str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}

分析:
这就是一个典型的野指针问题,str接收了动态开辟的空间,在调用了free函数之后,str指向的空间已经被系统回收,此时这片空间已经不属于str,但是并没有手动将str指向NULL,所以if语句块中的语句会被执行,会输出"world"(虽然这片空间已经不属于str,但strcpy这个函数不会管你这些,你给出指令,它就会执行对应的操作);

运行结果:
在这里插入图片描述
在这里插入图片描述

六、C/C++程序的内存开辟

我们曾经简单的介绍了C\C++中程序的内存区域划分,分为栈区、堆区、静态区三个区域,现在我们再对这部分进行详细说明;
在这里插入图片描述
C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等;
  2. 堆区:一般由程序员分配释放,若程序员不释放,程序结束可能由OS回收。分配方式类似于链表;
  3. 数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放;
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码;

从这幅图中更能理解为什么用static修饰的局部变量生命周期会变长,因为静态变量存放在数据段,数据段的特点是在上面创建的变量会一直存在,直到程序结束才销毁,所以生命周期变长。

七、柔性数组

在C99中,结构体的最后一个元素允许是未知大小的数组,这就叫做【柔性数组】成员。
例如:

typedef struct st_type
{int i;int a[0]; // 柔性数组成员
} type_a;

有时上面代码编译器会报错,可以改成:

typedef struct st_type
{int i;int a[]; // 柔性数组成员
} type_a;

1. 柔性数组的特点

  • 结构中的柔性数组成员前面必须至少有一个其他成员;
  • sizeof返回这种结构大小不包括柔性数组的内存;
  • 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小;

例如:

#include <stdio.h>typedef struct st_type
{int i;int a[]; // 柔性数组成员
} type_a;int main()
{printf("%d\n", sizeof(type_a));return 0;
}

运行结果:
在这里插入图片描述

2. 柔性数组的使用

// 代码1
#include <stdio.h>
#include <stdlib.h>typedef struct st_type
{int i;int a[]; // 柔性数组成员
} type_a;int main()
{int i = 0;type_a *p = (type_a*)malloc(sizeof(type_a) + sizeof(int) * 100);if (p == NULL){perror("malloc");return;}p->i = 100;for (i = 0; i < p->i; i++){p->a[i] = i;}free(p);p = NULL;return 0;
}

上面的代码在堆区开辟了一片空间用于保存这个结构体的信息,这个结构体中有一个长度为100的int数组;
柔性数组a有100个int元素的连续空间;

3. 柔性数组的优势

上面的代码还有另一种写法:

// 代码2
#include <stdio.h>
#include <stdlib.h>typedef struct
{int i;int *p_a;
} type_a;int main()
{type_a *p = (type_a*)malloc(sizeof(type_a));if(p == NULL){perror("malloc p");return ;}p->i = 100;p->p_a = (int*)malloc(p->i * sizeof(int));if(p->p_a == NULL){perror("malloc P_a");return ;}int i = 0;for (i = 0; i < p->i; i++){*(p->p_a + i) = i;}// 一定要按照顺序,否则会出错free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

代码1和代码2才能实现同样的功能,但是代码1有两个好处:

  1. 方便内存释放:

    可以看到,在代码2中进行了两次内存释放操作,因为结构体内部的成员也进行了动态内存开辟;
    当这个函数是我们封装好提供给他人使用的函数,别人是不知道,我们也不希望别人知道这个函数是怎样实现的,所以用户是不知道这个结构体内部的成员在使用结束后也需要释放内存,这就会产生问题;
    使用代码1的写法,直接释放结构体指针指向的内存空间,就能够完成动态开辟的内存空间的释放;

  2. 有利于访问速度

    代码1中整个结构体是在一片连续的堆区空间中,代码2则是数组和结构体的内存空间并不连续;连续的内存有益于提高访问速度,也有益于减少内存碎片;

所以上面修改后的通讯录还可以使用柔性数组来实现;

typedef struct
{int age; // 年龄char name[20]; // 名字char sex[7]; // 性别char phone_number[20]; // 电话号码char address[30]; // 地址
} Peoinfo; // 保存人的信息typedef struct
{int count; // 当前通讯录中的人数int num; // 当前通讯录中能保存的最大人数Peoinfo data[0]; // 指向保存人信息的空间
} Contact;

在空间不足时使用realloc函数来扩容即可;


总结

本文介绍了动态内存的开辟方法,并结合这个技术修改了之前的通讯录程序,这个技术使得程序员可以在程序执行过程中动态的调整变量占据的内存空间大小,极大的扩展了程序的功能;并在之后详细解读了动态内存开辟的6种错误;在最后介绍了柔性数组;

相关文章:

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理 【心法】 【第零章】c语言概述 【第一章】分支与循环语句 【第二章】函数 【第三章】数组 【第四章】操作符 【第五章】指针 【第六章】结构体 【第七章】const与c语言中一些错误代码 【禁忌秘术】 【第一式…...

理解进程和线程的概念

在操作系统中&#xff0c;进程和线程都是执行的基本单位&#xff0c;但它们在性质和管理方面有所不同 进程 定义: 进程是一个正在运行的程序的实例&#xff0c;是操作系统资源分配的基本单位。特点: 独立性&#xff1a;每个进程有其独立的内存空间、数据栈和其他辅助数据。重…...

【项目管理】第2章 信息技术发展 --知识点整理

Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 对应:第1章-第5章 (二)知识笔记 二、信息技术的发展 1. 信息技术及其发展 1)计算机软硬件 计算机硬件由电子机械、光电元件等组成的物理装置,提供物质基础给计算机软件运行。软件包括程…...

docker部署rabbitmq

拉取镜像 docker pull rabbitmq:managementmanagement 标签表示包含管理插件&#xff0c;可以通过 Web 界面管理 RabbitMQ 启动容器 docker run --restartalways -d --name rabbitmq \-p 9004:5672 -p 9005:15672 \-e RABBITMQ_DEFAULT_USERrabbitmq \-e RABBITMQ_DEFAULT_P…...

手搓多模态-03 顶层和嵌入层的搭建

声明&#xff1a;本代码非原创&#xff0c;是博主跟着国外大佬的视频教程编写的&#xff0c;本博客主要为记录学习成果所用。 我们首先开始编写视觉模型这一部分&#xff0c;这一部分的主要功能是接收一个batch的图像&#xff0c;并将其转化为上下文相关的嵌入向量&#xff0c;…...

计算机系统---UEFI(统一可扩展固件接口)

一、UEFI定义与历史背景 UEFI&#xff08;Unified Extensible Firmware Interface&#xff09; 是替代传统BIOS的新一代固件接口标准&#xff0c;由UEFI论坛&#xff08;前身是Intel的EFI论坛&#xff09;制定&#xff0c;旨在解决BIOS在现代硬件和操作系统下的局限性。 设计…...

2025年渗透测试面试题总结-某四字大厂面试复盘扩展 二面 (题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 某四字大厂面试复盘 二面 一、文件上传绕过WAF的15种深度技巧 二、SSRF利用与WAF绕过的8种高阶手法 …...

基于BP神经网络的杂草智能识别系统(杂草识别、Python项目)

基于BP神经网络的杂草智能识别系统 项目介绍 本项目是一个基于PyQt5和BP神经网络的杂草智能识别系统。系统通过图像处理和神经网络技术&#xff0c; 能够识别8种不同的杂草类别。用户可以通过上传图片&#xff0c;系统会自动识别图片中的杂草类别&#xff0c;并显示识别结果和…...

Java的Selenium的特殊元素操作与定位之时间日期控件

分为两种情况: 控件没有限制手动输入&#xff0c;则直接调用sendKeys方法写入时间数据 //时间日期控件处理 chromeDriver.get ("https://www,fliggy,com/?ttidsem.000000736&hlreferidbaidu.082076&route sourceseo"); chromeDriver.findElement (By.xpat…...

深入理解矩阵乘积的导数:以线性回归损失函数为例

深入理解矩阵乘积的导数&#xff1a;以线性回归损失函数为例 在机器学习和数据分析领域&#xff0c;矩阵微积分扮演着至关重要的角色。特别是当我们涉及到优化问题&#xff0c;如最小化损失函数时&#xff0c;对矩阵表达式求导变得必不可少。本文将通过一个具体的例子——线性…...

基于大数据的美团外卖数据可视化分析系统

【大数据】基于大数据的美团外卖数据可视化分析系统 &#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统通过对海量外卖数据的深度挖掘与分析&#xff0c;能够为美团外卖平台提供运营决策支…...

TypeScript 类型系统详解

基础类型​ TypeScript 支持丰富的基础数据类型&#xff0c;涵盖number、string、boolean、null、undefined、symbol以及bigint。这些类型为构建可靠的代码提供了基石。​ 数值类型&#xff08;number&#xff09;&#xff1a;在 TypeScript 里&#xff0c;所有数字均为浮点数…...

【学Rust写CAD】33 近似 Alpha 混合函数(argb.rs补充方法)

源码 #[inline]pub fn over(self, dst: Argb) -> Argb {let a 256 - self.alpha32();let rb (dst.rb() * a) >> 8;let ag dst.ag() * a;Argb(self.0 (rb & Argb::MASK) | (ag & !Argb::MASK))}源码分析 这段代码实现了一个近似 Alpha 混合&#xff08;“…...

4.3-4.6学习总结 Java:Set系列集合+双列集合+Map

Set系列集合&#xff1a; 元素是唯一的。 HashSet&#xff1a; 哈希值&#xff1a; 如果没有重写hashcode方法和equals方法&#xff0c;那么哈希值是根据地址值计算的。 LinkedHashSet&#xff1a; TreeSet底层为红黑树。 红黑树&#xff1a;两个红色节点不能相连。 双列集合&…...

深信服护网蓝初面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...

2011-2019年各省地方财政商业服务业等事务支出数据

2011-2019年各省地方财政商业服务业等事务支出数据 1、时间&#xff1a;2007-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、地方财政商业服务业等事务支出 4、范围&#xff1a;31省 5、指标说明&#xff1a;地方财政在商…...

InfoSec Prep: OSCP靶场渗透

InfoSec Prep: OSCP InfoSec Prep: OSCP ~ VulnHubInfoSec Prep: OSCP, made by FalconSpy. Download & walkthrough links are available.https://www.vulnhub.com/entry/infosec-prep-oscp,508/ 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做…...

【Kubernetes】Kubernetes 如何管理存储?PV 和 PVC 是如何工作的?

在 Kubernetes 中&#xff0c;存储管理是通过 Persistent Volume (PV) 和 Persistent Volume Claim (PVC) 来实现的。 它们为容器化应用提供持久化存储&#xff0c;使得应用在重启或重新调度时仍然能够访问数据。 1. 存储管理概述 Kubernetes 的存储管理是基于 持久化卷 (Pe…...

CasaOS小主机本地安装1Panel运维面板结合内网穿透移动端远程运维

文章目录 前言1. 添加镜像源2. 部署1Panel3. 本地访问测试4. 安装内网穿透工具5. 配置公网地址6. 配置固定公网地址 前言 小伙伴们&#xff0c;是不是一想到要远程管理服务器就头大如斗&#xff1f;特别是没有公网IP或路由器设置复杂时&#xff0c;简直让人抓狂&#xff01;但…...

BGP路由协议之属性2

Orgin 起源 公认必遵属性 起源名称标记描述IGPi如果路由是由始发的 BGP 路由器使用 network 命令注入到 BGP 的&#xff0c;那么该 BGP 路由的 origin 属性为 IGPEGPe如果路由是通过 EGP 学习到的&#xff0c;那么该 BGP 路由的 Origin 属性为 EGPIncomplete?如果路由是通过…...

HttpClient(笔记)

介绍&#xff1a; 发送请求的步骤&#xff1a; 发送get类型的请求&#xff1a; 发送post类型的请求&#xff1a;...

【简历全景认知】简历的历史演变与当代定位:从羊皮卷到算法博弈的艺术

引言:时间维度下的求职凭证。 当古埃及工匠在莎草纸上记录自己的技能时,他们不会想到这份"技能清单"会在四千年后演变成LinkedIn上的数字档案。简历作为个人职业身份的载体,其演变史就是一部微观的人力资源配置史。从手写推荐信到ATS系统解析的PDF,每一次形式变…...

ERP系统五大生产模式概述

制造业中,选择合适的生产模式是企业高效运营的关键。 以下是ERP系统支持的五大核心生产模式及其特点总结: 1. MTS(按库存生产) - 定义:先生产后销售,基于需求预测提前备货。 - 适用场景:需求稳定、标准化程度高的产品(如日用品、家电)。 - 优点:交货快、生产…...

003 vue生命周期和钩子函数

文章目录 1.Vue实例有一个完整的生命周期&#xff0c;也就是说从开始创建、初始化数据、编译模板、挂载DOM、渲染-更新-渲染、卸载等一系列过程&#xff0c;称之为Vue实例的生命周期 2.钩子函数&#xff08;监听函数&#xff09;&#xff1a;Vue实例在完整的生命周期过程中&…...

学透Spring Boot — 016. 魔术师 - Spring MVC Conversion Service 类型转换

本文是我的《学透Spring Boot》专栏的第16篇文章&#xff0c;了解更多请移步我的专栏&#xff1a; Postnull的专栏《学透Spring Boot》 目录 遇到问题 日期格式的转换 实现需求 创建转换器 注册转换器 编写Controller 访问测试 存在的问题 解决问题 源码分析 总结 …...

Ubuntu 22 Linux上部署DeepSeek R1保姆式操作详解(Xinference方式)

一、安装步骤 1.基础环境安装 安装显卡驱动、cuda&#xff0c;根据自己硬件情况查找相应编号&#xff0c;本篇不介绍这部分内容&#xff0c;只给出参考指令&#xff0c;详情请读者自行查阅互联网其它参考资料。 sudo apt install nvidia-utils-565-server sudo apt install…...

C语言查漏补缺:占位符篇

占位符篇 1. 整数类型2. 字符类型3. 浮点数类型4. 指针类型5. 字符串6. 修饰符 1. 整数类型 %d / %i&#xff1a;用于 int&#xff08;有符号十进制整数&#xff09;。int num -42; printf("%d", num); // 输出: -42%u&#xff1a;用于 unsigned int&#xff08;无…...

springcloud现常用的组件都有哪些,如何使用

Spring Cloud是一个用于构建分布式系统的开源框架&#xff0c;它提供了多个常用的组件来简化分布式系统的开发和部署。以下是Spring Cloud中常用的一些组件&#xff1a; Spring Cloud Config&#xff1a;用于集中式管理配置&#xff0c;并提供配置的动态刷新功能。Spring Clou…...

Vue3响应式引擎解密:从依赖追踪到性能调优的深度之旅

一、响应式内核架构演进 1.1 响应式范式升级 1.2 新旧架构性能对比 操作类型Vue2(ms)Vue3(ms)提升幅度深对象初始化8502203.86x数组操作响应120186.67x嵌套属性访问340655.23x批量更新响应210326.56x 二、依赖追踪机制剖析 2.1 依赖收集实战案例 // 数据模型const state r…...

主流程发起,去除子流程的时长计算问题

问题&#xff1a; 有三个流程&#xff0c;流程1.2.3&#xff0c;流程2.3是流程1的子流程&#xff0c;若在流程1的过程中发起流程2.3&#xff0c;计算流程1的时长要排除流程2.3的时间&#xff0c;流程2.3之间可能是包含、交集、无交集三种。 实现 代码&#xff1a; import jav…...

Java 8 到 Java 21 系列之 新日期时间API:精确的时间管理(Java 8)

Java 8 到 Java 21 系列之 新日期时间API&#xff1a;精确的时间管理&#xff08;Java 8&#xff09; 系列目录 Java8 到 Java21 系列之 Lambda 表达式&#xff1a;函数式编程的开端&#xff08;Java 8&#xff09;Java 8 到 Java 21 系列之 Stream API&#xff1a;数据处理的…...

【LeetCode】在每个树行中找最大值(DFS 深度优先搜索)

&#x1f4cc;题目链接&#xff1a;LeetCode 515. 在每个树行中找最大值 题目描述 给定一棵二叉树的根节点 root&#xff0c;请找出该二叉树中每一层的最大值。 示例 示例 1 输入: root [1,3,2,5,3,null,9] 输出: [1,3,9]示例 2 输入: root [1,2,3] 输出: [1,3]思路分析 …...

深度学习中模型量化那些事

在深度学习中模型量化可以分为3块知识点&#xff0c;数据类型、常规模型量化与大模型量化。本文主要是对这3块知识点进行浅要的介绍。其中数据类型是模型量化的基本点。常规模型量化是指对普通小模型的量化实现&#xff0c;通常止步于int8的量化&#xff0c;绝大部分推理引擎都…...

手写JSX实现虚拟DOM

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…...

Redis的Spring客户端的使用

Redis 的 Spring 客户端使用 前面使用 Jedis 时, 是借助 Jedis 对象中的各种方法来对 Redis 进行操作. 而在 Spring 框架中, 则是通过 StringRedisTemplate 来操作 Redis. 最开始提供的类是 RedisTemplate, StringRedisTemplate 是 RedisTemplate 的子类, 专门用于处理文本数据…...

7.4 SVD 的几何背景

一、SVD 的几何解释 SVD 将矩阵分解成三个矩阵的乘积&#xff1a; ( 正交矩阵 ) ( 对角矩阵 ) ( 正交矩阵 ) (\pmb{正交矩阵})\times(\pmb{对角矩阵})(\pmb{正交矩阵}) (正交矩阵)(对角矩阵)(正交矩阵)&#xff0c;用几何语言表述其几何背景&#xff1a; ( 旋转 ) ( 伸缩 )…...

WEB安全--内网渗透--LMNTLM基础

一、前言 LM Hash和NTLM Hash是Windows系统中的两种加密算法&#xff0c;不过LM Hash加密算法存在缺陷&#xff0c;在Windows Vista 和 Windows Server 2008开始&#xff0c;默认情况下只存储NTLM Hash&#xff0c;LM Hash将不再存在。所以我们会着重分析NTLM Hash。 在我们内…...

BN 层做预测的时候, 方差均值怎么算

✅ 一、Batch Normalization&#xff08;BN&#xff09;回顾 BN 层在训练和推理阶段的行为是不一样的&#xff0c;核心区别就在于&#xff1a; 训练时用 mini-batch 里的均值方差&#xff0c;预测时用全局的“滑动平均”均值方差。 &#x1f9ea; 二、训练阶段&#xff08;Trai…...

【AI热点】meta新发布llama4深度洞察(快速认知)

以下是一份针对新发布的 Llama 4 模型的深度洞察报告。报告将从模型家族整体概览、技术创新与架构特点、功能与性能表现、多模态与超长上下文、与主流竞品比较、应用场景与未来展望六大部分进行分析和总结。 一、Llama 4 家族整体概览 家族成员 Llama 4 Scout 总参数量约 10…...

大厂机考——各算法与数据结构详解

目录及其索引 哈希双指针滑动窗口子串普通数组矩阵链表二叉树图论回溯二分查找栈堆贪心算法动态规划多维动态规划学科领域与联系总结​​ 哈希 ​​学科领域​​&#xff1a;计算机科学、密码学、数据结构 ​​定义​​&#xff1a;通过哈希函数将任意长度的输入映射为固定长度…...

前端面试的ACM模式笔试输入模式

在前端面试的ACM模式笔试中&#xff0c;输入参数的读取是核心技能之一&#xff0c;以下是常见场景的代码实现及注意事项&#xff1a; 一、JavaScript的两种输入模式 1. V8模式&#xff08;浏览器环境/部分OJ平台&#xff09; • 核心方法&#xff1a;通过 read_line() 或 rea…...

AIP-215 API特定proto

编号215原文链接AIP-215: API-specific protos状态批准创建日期2018-10-01更新日期2018-10-01 API通常使用API特定proto定义&#xff0c;偶尔依赖通用组件。保持API相互隔离可以避免版本问题和客户端库打包问题。 指南 所有特定于某个API的protos 必须 位于带有主版本号的包…...

计算机毕业设计指南

哈喽各位大四的小伙伴们&#xff0c;以下是一份详细的计算机专业毕业设计指南&#xff0c;涵盖选题、流程、技术选型、开发建议和常见问题解决方案&#xff0c;帮助你高效完成毕业设计&#xff0c;如有其他问题&#xff0c;欢迎点击文章末尾名片进行咨询&#xff0c;可免费赠送…...

内网渗透-Linux提权之suid提权

Linux提权之suid提权 suid简介 在Linux系统中的文件&#xff0c;通常有rwx也就是读、写、执行三种权限&#xff0c;但其实还有第四种权限&#xff0c;也就是suid权限。在执行拥有suid权限的文件的过程中&#xff0c;会获得文件属主的权限。例如&#xff0c;当cat命令具有suid…...

FreeCAD傻瓜教程-钣金工作台SheetMetal的安装和简单使用

起因&#xff1a; 因为需要在平面上固定一段比较短的铝型材&#xff0c;角码太占用横向空间&#xff0c;所以想做两个Z字固定片&#xff0c;将型材从两端进行螺丝固定。在绘图的时候想到&#xff0c;板材折弯后的长度。开孔位置等都会有所变化&#xff0c;如何确定相关的尺寸&a…...

语法: ptr=malloc(size)

MALLOC( ) 语法: ptrmalloc(size) 参数: size是一个整数,表示被分配的字节个数; 返回值: 如果允许的话,返回值是一个指向被分配存储器的指针;否则的话, 返回值是一个非指针; 功能: 该函数用来分配一定大小的空间给一个对象,其大小为size,但该空间的值为不确定值; 有…...

(五)安卓开发中的滚动布局(ScrollView / HorizontalScrollView)使用详解

在安卓开发中&#xff0c;滚动布局是一种非常重要的布局方式&#xff0c;它允许用户在屏幕上滚动查看超出屏幕范围的内容。本文将详细讲解滚动布局的基本概念、主要属性、代码示例以及具体的使用场景&#xff0c;帮助开发者深入理解并灵活运用。 基本概念 滚动布局本质上是一个…...

Matlab:三维绘图

目录 1.三维曲线绘图命令&#xff1a;plot3 实例——绘制空间直线 实例——绘制三角曲线 2.三维曲线绘图命令&#xff1a;explot3 3.三维网格命令&#xff1a;mesh 实例——绘制网格面 实例——绘制山峰曲面 实例——绘制函数曲线 1.三维曲线绘图命令&#xff1a;plot3 …...

Java中String、Array、List的相互转换工具类

Java中的数组与集合类的使用,系列文章: 《Java数组》 《Java集合类》 《Java中String、Array、List的相互转换工具类》 《Java8使用Stream流实现List列表的查询、统计、排序、分组》 《Java实现List集合的排序:Comparable接口、Comparator接口、stream().sorted()方法的使用…...

【HFP】蓝牙HFP应用层核心技术研究

免提配置文件(Hands-Free Profile, HFP)作为实现设备间音频通信的关键协议,广泛应用于车载系统、蓝牙耳机等场景。本文将基于最新技术规范,深入剖析HFP应用层的功能要求、协议映射及编解码器支持,为蓝牙开发工程师提供详尽的技术指南。 一、HFP应用层功能全景图 HFP定义…...