C语言笔记(鹏哥)上课板书+课件汇总(动态内存管理)--数据结构常用

动态内存管理

引言:将内存升起一段空间存放数据有几种手段

  • 创建变量:存放一个值
  • 创建数组:存放多个连续的一组值

以上开辟的内存空间是固定的,创建大了,空间浪费,创建小了,空间不够。并且一旦创建好了就不能调整,不够方便灵活

  • 动态内存管理(分配):程序员可以自己申请和释放空间,灵活多用

一、malloc函数

memory allocate 内存,申请(开辟)
查看函数声明请见:c/c++官网
头文件:stdlib.h
在这里插入图片描述
作用:在内存堆区中申请一段连续的内存空间
实参:size_t类型(unsigned int,long,long long)类型,单位是字节的空间
返回类型:void* 返回开辟内存空间的起始首地址(首指针)

  • 编译器并不知道你要用这段内存空间存储什么数据,所以设置了void*类型的返回指针类型。—>其实就是泛型编程,使用更加广泛,可以存储各种类型数据,使用时只需要强转就好
  • 当你使用的时候,确定自己要存储什么类型的数据,就用什么类型的指针接收,然后将malloc返回类型强转成与当前指针类型一致的即可,一致才能赋值

创建空间成功:返回开辟内存空间的首地址
创建空间失败:返回空指针
因此需要对返回值进行检验,使用perror函数print+error:perror(“malloc”);编译器自动在malloc后面追加错误
参数 size 为0:malloc的⾏为是标准是未定义的,取决于编译器。

malloc综合使用:

#include<stdlib.h>
int main()
{//申请10个字节整形空间,使用sizeof编译器自己计算使用空间int* p = malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1; //错误程序退出为1}
//不报错,可以使用这块空间,跟数组一样去使用,因为也是开辟一段连续的内存空间,只是空间位置不一样int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}for (i = 0; i < 10; i++){printf("%d ",p[i]);}//使用之后应该释放内存空间free(p);p = NULL;return 0;
}

如果开辟空间失败了:将参数设置为INT_MAX整形最大值,编译器环境设置为x86,x64太大了

int main()
{//申请10个字节整形空间,使用sizeof编译器自己计算使用空间int* p = malloc(INT_MAX);if (p == NULL){perror("malloc");return 1; //错误程序退出为1}//不报错,可以使用这块空间,跟数组一样去使用int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}for (i = 0; i < 10; i++){printf("%d ", p[i]);}//使用之后应该释放内存空间free(p);p = NULL;return 0;
}

在这里插入图片描述
malloc函数申请的空间和数组的空间有什么区别呢
1、数组申请的空间不可调整大小,malloc动态内存申请的空间可以调整大小
2、申请空间的位置不同
在这里插入图片描述

二、free函数的使用

释放和回收动态内存申请的空间,经常与malloc,calloc,realloc搭配使用,申请+释放,然后用NULL将指针置空,malloc和free都声明在 stdlib.h 头⽂件中。

void free (void* ptr);

参数是任意类型的指针,也就是存放任意类型数据的空间都可以被释放掉,只要是动态申请来的空间。

  • 释放空间之后,指针变量里面还是会存储之前的地址,所以是很危险的,这块空间已经不属于你了,但是你还记得地址,相当于是野指针,所以需要拿NULL把野指针拴住,也就是将指针置空,后续在使用的时候就找不到不属于你的空间了。
  • free叫做手动释放动态申请的内存,如果使用free在程序结束后这块内存也会自动被操作系统回收,最好是使用free释放你申请的空间,既然申请了就对他负责嘛,就要释放掉哦,
  • 如果程序运行的时间很长,然后你申请的空间用不上了而且还有剩余,但是你不去释放,这些空间就会一直被占用着,就无法被其他地方所使用,造成空间的浪费,到最后才将这些空间一并还给操作系统(好比说我借你一本书,你看完了就还给我嘛,还给我我还可以借给别人,使得值本书被高效利用)
  • 还有一种情况如果你没有free也可能造成空间被占用着,但是你找不到地址的情况,也释放不了,这样的代码多了,就造成内存泄漏了。

综合使用规则:

//使用之后应该释放动态内存空间
free(p);
p = NULL;

注意事项:

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

回收内存空间:
在这里插入图片描述
如果是数组创建的空间,是在栈区,那么在进入大括号是被创建,出了大括号便把这块空间归还,但是动态内存申请实在堆区,需要你手动释放或者是程序结束后被操作系统自动回收。

三、calloc函数

参数及作用:在堆区中申请num个字节为size的空间,并且将申请到的空间进行初始化全0操作
返回值:和malloc一样
在这里插入图片描述
malloc与calloc的区别

malloc:不会初始化操作,申请到的空间中存放的都是cd,cd,cd…这样的16进制随机值,步骤更少,更快捷
calloc:会初始化,申请空间并且将空间中的值都初始化为0,步骤多一些
请观看他们在内存中申请的空间样子

首先按f10或者f11进入调试,点击窗口,内存,接下来继续按f11逐步调试,直到调试完malloc或calloc这个语句之后,输入p回车,编译器会自动计算p的地址,然后找到地址内存处

malloc:
在这里插入图片描述
calloc:

在这里插入图片描述

四、realloc函数

repeat+allocate,再次开辟(调整动态申请的内存空间) 使得动态空间缩放灵活
在这里插入图片描述
在这里插入图片描述
参数:任意类型的指针ptr,是原来使用malloc,realloc,calloc函数开辟的动态内存空间的起始地址,size是你预期的空间大小,就是你希望将空间调整为多大
返回值:请先看下面realloc开辟空间成功的两种情况,如果情况1则返回原来的地址,如果是情况2,则返回新的地址,如果开辟失败则返回空指针

realloc开辟空间成功的两种情况
1、最开始要调整的那块空间后面还有足够的容量能够调整到你需要的大小,那么直接调整大小,并且返回和之前一样的地址(内存空间的起始地址)
2、最开始要调整的空间后面并没有足够的容量能够调整到你需要的大小,需要的内存空间已经被占用了,realloc函数会直接在内存中找一块符合你要求大小的内存块,并且将新的地址返回给你

在这里插入图片描述

在情况2中realloc函数会进行的操作:
1、在内存中找一块符合你要求大小的内存空间
2、将原始内存中的数据拷贝过来
3、释放掉旧的空间
4、返回新空间的起始地址

注意事项:

1、realloc返回值的接收要使用其他的指针(以ptr为例),指针的类型还是和你要存储的数据保持一致,不可使用原来用malloc/calloc/realloc函数开辟空间的起始地址,因为如果使用原来指针接收,一旦开辟失败,就会返回一个空指针,那么原来的指针变成了一个空指针,不但没有扩容或缩容成功,原来开辟的空间也找不到了,就得不偿失了。
2、传入的指针只能是你需要调整空间的首地址,不可以是你想从哪扩大就直接传入那的地址
3、在使用之前要判断ptr是否为空,如果不为空才可以把ptr的值传给p,否则就直接是扩容或缩容失败,直接上perror函数捕捉错误,终止程序return 1;

综合使用方法:

int main()
{int* p = calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}//开辟成功,想要扩大空间,缩小直接将size改小一点就可以了int* ptr = realloc(p, 12 * sizeof(int));if (ptr != NULL){p = ptr;}else{perror("realloc");return 1;}//使用过程......//释放空间free(p);p = NULL;return 0;
}

使用realloc函数开辟空间:
如果将ptr置为NULL,那么使用方法就和malloc一样了

int main()
{int* p=(int*)realloc(NULL, 10 * sizeof(int));//数组一样去使用...//释放free(p);p = NULL;return 0;
}

前面说的空间开辟成功会有两种情况,我在监视中给大家调试看一看:

  • 监视和上面我教给大家的步骤一样,只是把内存换成了监视窗口

1、调整的大小比较小,属于第一种情况,地址不变
在这里插入图片描述
2、调整的大小较大,属于第二种情况,地址改变:
在这里插入图片描述3、空间开辟失败呢:有可能的,内存也是资源,资源就不是无节制的
在这里插入图片描述
4、当你不想使用ptr之后也可以将ptr置空,因为不使用某指针的时候就需要将其置空,防止其成为野指针,也方便后续再次使用这个名字作为指针名,也就是我们之前讲过的,使用指针前都要进行assert断言判断一下指针是否为空的原因,为空才可以继续使用,不为空要先置空在使用。

五、动态内存分配常见的错误

1、对空指针的解引用操作:对于malloc的返回值一定要进行判断,是否为空,实际上这样写编译器也会给你警告

int main()
{int* p = malloc(10 * sizeof(int));//直接使用int i = 0;for (i = 0; i < 10; i++){p[i] = 0;//形成对p的解引用,如果i是0的话,*(p+i)就是空指针的解引用}//....//释放free(p);p = NULL;return 0;
}

1、对动态开辟的空间越界访问

对malloc开辟空间的单位理解不清:申请了10个整形的空间,结果访问40个

int main()
{int* p = malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//*(p+i),p这个指针走一步跳过一个整形4字节,开辟了40个字节的空间,// 并不是每次解引用之后都跳过一个字节走40次int i = 0;for (i = 0; i < 40; i++){p[i] = 0;}//....//释放free(p);p = NULL;return 0;
}

2、对非动态开辟的内存使用free释放

与动态内存相关的错误,关闭窗口要关闭好几次才行,都是这个特点

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;//。。。。使用//糊里糊涂的释放了free(p);p = NULL;return 0;
}

在这里插入图片描述

3、free释放一块动态开辟内存的一部分

起始位置处的指针不要乱动,起始位置的指针记录着开辟空间的起始位置,想找到这块空间就必须依靠这个指针,所以尽量不要动这个指针,如果还想使用指针记录这个位置往后移动的话(例如遍历等等操作)可以创建一个新的指针来移动,循环中p[i]其实p还是在初始位置,其他的元素的地址是p+i所以不用担心指针p位置变了。
事例代码:造成释放内存的一部分

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用for (int i = 0; i < 10; i++){*p = i;p++;}//将指针移动了,指针没有指向空间的起始位置,所以释放的时候释放不掉整个动态内存申请的空间free(p);p = NULL;return 0;
}

4、对同一块内存空间多次释放

每次free之后,都将指针置空是很有必要的

  • 如果后续忘记了,又将其置空一次也没关系,因为已经置空指针了,再次释放就相当于空指针释放,不起任何作用
  • 如果不将其置为空指针,使用两次free就会出现问题。
    请看代码:

1、前期置空了,代码不会出现问题:

int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//...//释放free(p);p = NULL;//...//....//忘记了又去释放了free(p);p = NULL;return 0;
}

2、如果没有置空,又去释放:

在这里插入图片描述

5、内存泄漏

忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
事例代码:

void test()
{int flag = 1;int* p = malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//此处定义一个条件,这个条件发生了提前返回主函数了if (flag){return;}//释放free(p);p = NULL;
}
int main()
{test();//....//...程序一直运行下去,造成内存蚕食(内存泄露)return 0;
}
  • 在代码中我malloc申请了空间,也对其进行了非空判断,也进行了释放
  • 但是在释放之前如果发生某条件,直接返回到主函数,就没有起到释放空间的作用。出了这个函数,p是局部变量出了test会自动被回收,那么在程序后面你就再也找不到p这个地址了,想释放都释放不了这个空间了
  • 这块空间存在,占用着,但是仿佛消失了,你找不到它,这样的代码多了就会造成你的内存在一点一点被蚕食,最终导致程序崩溃------>这叫做内存泄漏
  • 如果在服务器这种系统中,代码7*24小时一直运作下去,程序不结束,发生内存泄露之后一直不能将内存释放还给操作系统,就会崩溃

切记:一定要对申请的动态内存进行释放,并且要正确释放,动态内存管理很方便灵活,但是风险也大,所以有些编程语言不允许进行动态内存管理的,给你提供各种垃圾回收的系统,c/c++提供,可见其追求性能到极致哈哈哈

六、一些常见的笔试题:

1、对NULL指针进行解引用程序崩溃 + 内存泄漏

输出结果是:程序崩溃

void GetMemory(char *p){p = (char *)malloc(100);}
void Test(void){char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);}int main(){Test();return 0;}

在内存中开辟的空间是这样的:
在这里插入图片描述

  • 首先进入主函数,调用test函数,栈区创建指针str,存储NULL,调用函数GetMemory,将str进行值传递传递给形式参数p
  • 形式参数指针p是str的一份临时拷贝,所以也在栈区空间上开辟一块空间给p拷贝str的值,malloc在内存的堆区开辟一块100字节空间,地址赋给指针p,p就能指向动态开辟空间了
  • 但是出了GetMemory函数之后这个p就被销毁了,局部变量存储在栈区,在进入大括号时被创建,出大括号时销毁,事实上这个地址就不见了,找不到这块内存空间后续也释放不了,也不能给其他人使用,直到程序运行结束之后才会被回收到操作系统,就造成了内存泄露
  • 然后回到Test函数,strcpy—>string copy函数,将前面的指针解引用后找到那块空间,并将后面的字符串 + \0拷贝到这块空间里面,此时相当于对str进行解引用,空指针解引用,程序会崩溃
    实际上这种操作是想干什么呢?
    想使用p指针在动态内存上开辟空间并且传给栈区上创建的指针str,然后str上便具有这个地址也就能找到这块空间进行strcpy操作了,出函数之后p被销毁了就不会造成野指针p了,所以不用对p置空,然后我们不使用空间之后进行free(str);str = NULL;就可以了

正确操作:最终输出结果都是hello world

1、使用传址调用,形参直接修改实参值,在函数里面释放内存空间

void GetMemory(char** p)//传进来一级指针的地址,用二级指针接收
{*p = (char*)malloc(100);//*p就是str指针
}
void Test(void)
{char* str = NULL;GetMemory(&str);//将str中存储了动态空间的地址strcpy(str, "hello world");//内存中拷贝了hello worldprintf(str);//释放free(str);str = NULL;
}
int main()
{Test();return 0;
}

2、使用传址调用,形参直接修改实参值,在主函数里面释放内存空间

void GetMemory(char** p)//传进来一级指针的地址,用二级指针接收
{*p = (char*)malloc(100);//*p就是str指针
}
char* Test(void)
{char* str = NULL;GetMemory(&str);//将str中存储了动态空间的地址strcpy(str, "hello world");//内存中拷贝了hello worldprintf(str);return str;//想让主函数给你释放动态内存的话,你要给他地址啊
}
int main()
{char * str = Test();free(str);str = NULL;return 0;
}

3、直接将p地址返回,用str接收,不用传递str过去了

char* GetMemory()
{char* p = (char*)malloc(100);return p;//返回动态内存存储的地址
}
char* Test(void)
{char* str = NULL;str = GetMemory();//使用同类型空指针接收strcpy(str, "hello world");//内存中拷贝了hello worldprintf(str);return str;//想让主函数给你释放动态内存的话,你要给他地址啊
}
int main()
{char* str = Test();free(str);str = NULL;return 0;
}

2、注意:printf打印字符串

1、打印字符串的时候可以直接printf(“字符串内容”);
实际上就是将字符串的首地址直接给printf了,就不用使用printf(“%s”,“字符串内容”)了

在这里插入图片描述

3、返回栈空间地址----常见的错误(返回变量是可以的,但是返回地址是不可的)

返回变量:是先将其出处在寄存器中,出括号销毁变量就销毁吧,值还是给了接收变量
在这里插入图片描述
返回地址:
1、输出结果是:随机值

char *GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char *str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0 ;
}

在这里插入图片描述

在内存中的过程:

  • 进入Test(),栈区创建指针变量str,赋值为空指针
  • 进入GetMemory函数,创建字符数组p存储hello world\0,p数组名存储了数组首元素地址,返回p,然后出函数
  • 本意是想让str接收到这个地址,并且将地址内容打印出来,printf(“字符串数组的首地址”)也是打印出字符串哦。
  • 出了函数之后这段空间就还给操作系统了,即使你记住了地址传给了str也没有用
  • 这个指针str现在就是一个野指针,去访问它没有访问权限的地方如果这块空间已经被其他值占用了那么输出其他值 + 剩余的位数就输出随机值,如果这块空间还没有被利用,那么就可能会非法访问到这个值,但也是非法的
  • 其实就相当于我今天在如家酒店开一个房间,然后我把房间号发给你了,让你第二天过来住,但是第二天早上我把房间退了,你找到这个房间想住进去,结果被告知没有住的权力,或者这个房间已经租给别人了

改正:如果使用动态内存开辟空间就不会这样,只要程序没有结束并且没有手动释放空间就一直可以访问空间

验证一下我刚刚说的:

1、这段代码中虽然这块空间已经不能由a的地址解引用去访问了,但是非法访问之后仍然是10,这是为什么呢,因为这块空间使用权限还给操作系统之后还没有被利用,也就是值还没有被覆盖,空间依然是存在的,只是访问权限会发生变化

在这里插入图片描述

2、如果是这样呢:我将打印hello world放在访问前使用,为什么就会输出hello world11呢,其实11是随机值请看下面栈中分配空间解释的很详细

在这里插入图片描述

  • 栈区中的解释:首先开辟主函数空间,在主函数空间开辟a指针变量记录test地址,在上面开辟test空间,在test里开辟a的空间,返回a的地址,出了函数之后,这块test空间还给操作系统了,然而如果我不去调用printf申请这块空间存放值,这块空间的值就还是10,没有被改变,这就是为什么将地址a解引用之后还会10

  • 然而我调用了printf这样的函数使用了原来开辟的这块空间去存放hello world了,(原来打印值也会被放在栈区),可能这块被申请的空间还有剩余就会输出一些其他值

在这里插入图片描述

鹏哥给找的一点笔试题

这道题就是典型的返回栈空间地址,一旦有接收他的指针就是一个野指针

在这里插入图片描述

  • 这道题ptr并没有初始化是一个野指针,所以对它进行解引用是非法的,压根没有任何东西给你解引用,由于局部变量未初始化是随机值,所以不知道会有什么样的地址给ptr,但这块空间绝对是非法访问
    在这里插入图片描述

4、没有对动态内存空间释放

使用一气呵成,最后却没释放造成内存泄漏

改正:只需要加上我注释掉的两行代码即可

void GetMemory(char **p, int num){*p = (char *)malloc(num);}
void Test(void){char *str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);//free(str);//str = NULL;}int main(){Test();return 0;}

内存中的过程:由于上面已经讲的够详细这种思路了,这里就不细讲了大家自己看图理解
在这里插入图片描述

5、free之后不置空的危害:造成野指针还能继续使用

void Test(void){char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}}
  • 操作步骤是:malloc申请100个字节空间,创建字符指针接收空间首地址(说明未来这些空间是用来存放字符类型的),将str指向的空间拷贝hello\0,释放str,此时str还不是空指针,还记得位置,所以自然不会为空,继续在非法访问空间并拷贝word\0了
  • 运行结果是:world -----说明续写操作非法将原来的空间写入了world\0并且将hello\0覆盖掉了

在这里插入图片描述

总结几点比较好的习惯:荧光笔画的

  • malloc之后判断指针是否为空
  • 所以这里就体现了free之后将指针置为空的好处,防止其变成野指针,而且之前也讲过,如果忘记了已经释放过了的话,释放两次,如果是空指针在释放就没有任何影响,否则会报错
  • 在指针第二讲也讲过,使用指针之前可以先使用断言assert来判断指针是否为空,或者使用if判断,为空说明不再使用指针将其置空了,不再使用,报错程序终止。如果不为空,则说明还可以继续使用。所以不使用指针了一定要记住及时置空哦

这道题目实际上想考察的点是:free之后置空,然后空指针想拷贝也拷贝不了了,非法访问也就不可进行了,这就体现了代码的严谨性

柔性数组等我更新完结构体会更好理解

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

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

相关文章

uv - Getting Started 开始使用 [官方文档翻译]

文章目录 uv亮点安装项目脚本工具Python 版本pip 接口了解更多 入门安装 uv安装方法独立安装程序PyPICargoHomebrewWinGetScoopDockerGitHub 发布 升级 uvShell 自动补全卸载 第一次使用 uv特性Python 版本脚本项目工具pip 接口实用工具 获取帮助帮助菜单查看版本故障排除问题在…

HarmonyOS Next~鸿蒙系统安全:构建全方位的防护体系

HarmonyOS Next&#xff5e;鸿蒙系统安全&#xff1a;构建全方位的防护体系 ​ ​ 在数字化飞速发展的当下&#xff0c;操作系统的安全性成为了用户和开发者关注的焦点。华为鸿蒙系统&#xff08;HarmonyOS&#xff09;以其独特的架构和强大的安全性能&#xff0c;在众多操作…

本地安装deepseek大模型,并使用 python 调用

首先进入 ollama 官网 https://ollama.com/点击下载 下载完成后所有都是下一步&#xff0c;就可以 点击搜索 Models &#xff1a; https://ollama.com/search然后点击下载&#xff1a; 选择后复制: ollama run deepseek-r1:32b例如&#xff1a; 让它安装完成后&#xff1…

Linux wifi driver 注册和设备探测流程

基础流程 wifi驱动加载&#xff08;insmod或者modprobe&#xff09; 设备驱动匹配探测&#xff08;我们常见的probe函数&#xff09; 整体流程 驱动加载 → 注册支持设备 → 设备插入 → 匹配驱动 → 初始化硬件 → 创建网络接口 明确两点 两个流程 驱动加载&#xf…

【机器人】复现 GrainGrasp 精细指导的灵巧手抓取

GrainGrasp为每个手指提供细粒度的接触指导&#xff0c;为灵巧手生成精细的抓取策略。 通过单独调整每个手指的接触来实现更稳定的抓取&#xff0c;从而提供了更接近人类能力的抓取指导。 论文地址&#xff1a;GrainGrasp: Dexterous Grasp Generation with Fine-grained Con…

快速部署Samba共享服务器作为k8s后端存储

由于Ceph Squid&#xff08;v19.2.1&#xff09;‌不原生支持直接导出 SMB 服务器‌&#xff0c;需通过手动集成 Samba 或其他第三方工具实现‌ 所以直接部署最简单的 安装软件包 apt install samba编辑配置文件 vim /etc/samba/smb.conf在最末尾添加以下 # cp /etc/samba/sm…

【时时三省】(C语言基础)选择结构和条件判断

山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 选择结构和条件判断 在现实生活中需要进行判断和选择的情况是很多的。如:从北京出发上高速公路,到一个岔路口,有两个出口,一个是去上海方向,另一个是沈阳方向。驾车者到此处必须进行判断,根据自己的目的地…

【MYSQL】索引和事务

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 本期内容讲解 MySQL 中的索引和事务&#xff0c;在学习的过程中&#xff0c;我们需要经常问自己为什么 文章目录 1. 索…

计划管理工具应该具备的能(甘特图)

在当今快节奏的项目管理环境中&#xff0c;高效地规划和跟踪项目进度是至关重要的。甘特图&#xff0c;作为项目管理领域的经典工具&#xff0c;以其直观的时间轴和任务分配方式&#xff0c;深受项目管理者的青睐。 随着数字化时代的到来&#xff0c;甘特图线上编辑器应运而生&…

Redis分布式寻址算法

分布式寻址算法是分布式系统中用于确定数据应该存储在哪个节点的算法。这些算法对于实现高效的数据存取、负载均衡和系统扩展性至关重要。以下是几种常见的分布式寻址算法的解释&#xff1a; 1. Hash 算法 原理&#xff1a;通过哈希函数将数据的键&#xff08;Key&#xff09…

CSS动画

目录 一、核心概念与语法 1. keyframes 关键帧 2. animation 属性 二、动画调速函数&#xff08;animation-timing-function&#xff09; 1. 预设值 2. 贝塞尔曲线 3. 步进函数&#xff08;steps()&#xff09; 三、动画控制与交互 1. 暂停与恢复 2. JavaScript 控制…

2025年河北省第二届职业技能大赛网络安全项目 模块 B样题任务书

2025年河北省第二届职业技能大赛网络安全项目 模块 B样题任务书 河北省第二届职业技能大赛网络安全项目-模块 B-夺旗挑战赛&#xff08;CTF&#xff09;一、目标系统1二、目标系统2三、目标系统3四、目标系统4 需要真题环境-培训可以私信博主&#xff01; 河北省第二届职业技能…

钞票准备好了吗?鸿蒙电脑 5 月见

3月20日&#xff0c;在华为 Pura 先锋盛典及鸿蒙智行新品发布会上&#xff0c;华为常务董事、终端BG董事长、智能汽车解决方案BU董事长余承东表示&#xff0c;华为终端全面进入鸿蒙时代&#xff0c;今年5月将推出鸿蒙电脑。 在3月20日的华为Pura先锋盛典及鸿蒙智行新品发布会上…

Java高频面试之集合-15

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;解决哈希冲突有哪些方法&#xff1f; 1. 开放寻址法&#xff08;Open Addressing&#xff09; 核心思想&#xff1a;当哈…

【机器学习】建模流程

1、数据获取 1.1 来源 数据获取是机器学习建模的第一步&#xff0c;常见的数据来源包括数据库、API、网络爬虫等。 数据库是企业内部常见的数据存储方式&#xff0c;例如&#xff1a;MySQL、Oracle等关系型数据库&#xff0c;以及MongoDB等非关系型数据库&#xff0c;它们能够…

GitHub 上的 Khoj 项目:打造你的专属 AI 第二大脑

在信息爆炸的时代&#xff0c;高效管理和利用个人知识变得愈发重要。GitHub 上的 Khoj 项目为我们提供了一个强大的解决方案&#xff0c;它能成为你的 “AI 第二大脑”&#xff0c;帮你轻松整合、搜索和运用知识。今天&#xff0c;就来详细了解下 Khoj。​ Khoj 是什么&#x…

爬虫(requsets)笔记

一、request_基本使用 pip install requests -i https://pypi.douban.com/simple 一个类型六个属性 r.text 获取网站源码 r.encoding 访问或定制编码方式r.url 获取请求的urlr.content 响应的字节类型r.status_code 响应的状态码r.headers 响应的头信息 import requestsur…

centos7连不上接网络

选择编辑&#xff0c; 选择虚拟机网络编辑 右键虚拟机&#xff0c;点击设置&#xff0c;设置网络,选择nat模式&#xff0c; 配置&#xff1a;/etc/sysconfig/network-scripts/ifcfg-ens33 vim /etc/sysconfig/network-scripts/ifcfg-ens33设置IP地址如图所示&#xff0c;重…

OpenResty(Lua)+Redis实现动态封禁IP

文章目录 架构设计环境准备源码编辑安装OpenResty下载安装准备依赖编译安装配置环境变量&#xff08;可选&#xff09;OpenResty 服务管理命令 安装Redis配置Lua脚本测试准备测试工具测试封禁逻辑 删除版本信息清除编译安装的OpenResty 架构设计 通过 Nginx Redis 的方案&…

Turtle基本操作(前进、后退、旋转)

1. Turtle基本移动概念 在Turtle绘图中,“海龟”(Turtle)相当于一支笔,它在屏幕上移动时,会在经过的路径上留下轨迹。我们可以通过一系列简单的指令控制它的前进、后退和旋转,从而绘制各种形状和图案。 2. 前进与后退 2.1 前进(forward() 或 fd()) Turtle的 forward…