【C语言】—— 动态内存管理

【C语言】——动态内存管理

一、动态内存管理概述

1.1、动态内存的概念

  在了解为什么要有动态内存管理之前,我们得先知道动态内存的定义

  动态内存是指动态的内存空间,意思就是:能动态开辟的内存空间动态就是申请了这块空间后,可动态的修改这块空间的大小,根据需要,动态地释放和分配内存空间
  

1.2、动态内存的必要性

  为什么要有动态内存呢?
  既然有动态内存,那与之相对的就是静态内存
  什么是静态内存呢?其实静态内存我们天天都在用,只是不知道它是静态内存而已
  
下面两种内存开辟方式就是静态内存

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

  

但是静态内存的开辟有两个缺点

  • 空间开辟的大小是固定
  • 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整

  但是,在实际需求中,我们往往只有在程序运行时才能知道所需的空间大小,用数组开辟空间,往往容易造成内存溢出(空间开小),或者内存浪费(空间开大),无法满足实际的需求
  
  因此,C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,并根据需要,自己调整开辟后空间的大小。这样不仅提高了内存的利用率,也极大地增强了程序的灵活性扩展性
  
  

二、 m a l l o c malloc malloc 函数

2.1、函数介绍

  C语言中提供了一个动态内存分配的函数: m a l l o c malloc malloc

在这里插入图片描述

功能:向内存申请一块连续可用的空间(可当成数组),并返回指向这块空间的指针

  • 参数 s i z e size size_ t t t s i z e size size

    • 分配的内存的大小,以字节为单位。即开辟 s i z e size size 字节大小的空间
    • 如果参数为 0 m a l l o c malloc malloc 的行为是标准未定义的,取决于编译器
        
  • 返回值 v o i d void void *

    • 返回指向开辟空间的指针,因为 m a l l o c malloc malloc 函数 事先并不知道使用者开辟空间存放什么类型的数据,因此指针为 v o i d void void* 类型,以便能接受所有类型。
    • 使用者可根据自己的需要,将其强制类型转换成自己所需要的类型,以便能进行解引用操作。
    • 如果开辟失败,则返回 空指针,因此 m a l l o c malloc malloc 的返回值一定要做检查

  

2.2、应用举例

#include<stdlib.h>

int main()
{
	int* p = NULL;
	p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail");
		return 1;
	}
	return 0;
}

上述代码是使用 m a l l o c malloc malloc 函数开辟 10 个整型变量的空间,也即 40 个字节的空间

  • 首先,因为 m a l l o c malloc malloc 函数的返回值是指针,我们需用指针变量 p p p 接收其返回值,创建 p p p 时,并不知道其指向的空间,所以先初始化为 NULL。
  • 接着,使用 m a l l o c malloc malloc 函数开辟空间,因为我们要存放的是整型变量,而 m a l l o c malloc malloc 的返回值类型为 v o i d void void* 我们通过强制类型转换将其返回类型转换为 i n t int int* ,并用 p p p 来接收
  • 因为 m a l l o c malloc malloc 函数有可能开辟失败1,只有当返回值不为空的情况我们才使用它,因此需判断 p p p 指针是否为空。若为空,则用 p e r r o r perror perror 函数2打印出错误信息,并返回 13
  • 若开辟成功,我们就可以愉快地使用这块空间啦

在这里插入图片描述

  需要注意的是: m a l l o c malloc malloc 开辟的空间并不会将其初始化
  
  

三、 c a l l o c calloc calloc 函数

  开辟动态内存空间,C语言还提供了一个函数叫 c a l l o c calloc calloc ,原型如下:

在这里插入图片描述

  • 函数的功能是为 n u m num num 个大小为 s i z e size size 的元素开辟一块空间,并将这块空间初始化为 0
  • 与函数 m a l l o c malloc malloc 的区别只在于 c a l l o c calloc calloc 会返回地址之前把申请的空间的每个字节初始化为全 0

举个例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			printf(" %d", *(p + i));
		}
		printf("\n");
	}
	return 0;
}

  
运行结果:

在这里插入图片描述

  
  

四、 f r e e free free 函数

4.1、函数介绍

  
  上述 m a l l o c malloc malloc 函数、 c a l l o c calloc calloc 函数以及后面讲的 r e a l l o c realloc realloc 函数所申请的空间,并不满足作用域的规则。只有当程序退出时,用他们开辟的动态空间才会归还给操作系统,换言之,程序不退出,就不会主动归还空间。这时,我们就需要 f r e e free free函数 来对其主动释放

   f r e e free free 函数是专门用于动态开辟的内存空间的释放回收,声明如下:

在这里插入图片描述

  
f r e e free free 函数用来释放动态开辟的内存

  • 如果参数 p t r ptr ptr 指向的空间不是动态开辟的,那 f r e e free free 函数的行为是未定义
  • 如果参数是 p t r ptr ptr 是NULL指针,则函数什么事都不做
  • 需要注意的是, f r e e free free 函数不会改变指针所指向的值,释放后它依然指向相同的内存空间。因此我们要手动将释放后的 p t r ptr ptr 置空,避免出现野指针。

m a l l o c malloc malloc c a l l o c calloc calloc 以及 f r e e free free 函数的声明都在 < s t d l i b . h > <stdlib.h> <stdlib.h>
  

4.2、应用举例

  
我们来看个例子:

#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* ptr = NULL;
	ptr = (int*)malloc(10 * sizeof(int));
	if (NULL != ptr)
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);
	ptr = NULL;
	return 0;
}

在这里插入图片描述

  
  那我们来看看下面这种情况行不行呢?

int main()
{
	int* ptr = NULL;
	ptr = (int*)malloc(5 * sizeof(int));
	if (NULL != ptr)
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*ptr = 0;
			ptr++;
		}
	}
	free(ptr);
	ptr = NULL;
	return 0;
}

  当然是不行的,为什么呢?
  因为传递给 f r e e free free 函数的是要释放空间的 起始地址
  上面函数的 p t r ptr ptr 以及不再指向要释放空间的起始地址了,当然是不行的。
  
  

五、 r e a l l o c realloc realloc 函数

5.1、函数介绍

  
  可能有小伙伴问:前面你说动态内存可根据需要,动态调整所开辟空间的大小,但前面介绍 m a l l o c malloc malloc c a l l o c calloc calloc 以及 f r e e free free函数 都只是在将动态空间的开辟和释放,如何调整空间的大小呢?别急,我们接下来要讲的 r e l l o c relloc relloc函数 就是完成调整开辟空间的大小的任务的

   r e a l l o c realloc realloc 函数的出现让动态内存管理更加灵活
  
  有时我们会发现之前申请的空间太小了,有时我们又会觉得申请的空间太大了,那为了合理的使用内存,,我们一定会对内存的大小做出灵活的调整。那 r e a l l o c realloc realloc函数 就可以做到对动态开辟内存大小的调整

先来看看 r e l l o c relloc relloc函数 的声明:

在这里插入图片描述

  • p t r ptr ptr 是要调整的内存地址
  • s i z e size size 是调整之后的大小(可以变大,也可变小)
  • 返回值为调整之后的内存起始位置

r e a l l o c realloc realloc 调整内存大小分为三种情况:

在这里插入图片描述

  1. 原有空间之后有足够大的空间

  如上图: r e l l o c relloc relloc 已经开辟 20 个字节的空间,现在我想扩容到 40 字节,同时原有空间后方空间足够扩展新空间
  
  此时 r e a l l o c realloc realloc 函数直接在后方追加空间,原来空间的数据不发生变化

  
2. 原有空间之后没有足够大的空间

  还是上面那个图,现在我想将他扩容到 400 字节,很明显,已开辟空间后方没有足够的空间,总不能把别人踢开,自己霸占吧
  这时, r e a l l o c realloc realloc 函数就会堆空间 另外找一个 合适大小的空间
  
具体流程如下:

  • r e a l l o c realloc realloc 函数先在堆空间上找一块新的空间,并且满足大小要求
  • 后将旧空间的数据拷贝到新空间中
  • 接着释放旧空间
  • 最后返回新空间的起始地址

  
3. 空间调整失败

   r e a l l o c realloc realloc 可能出现空间调整失败的情况,此时返回的是空指针

  
  
   r e a l l o c realloc realloc 不仅仅能将空间的变大,还能将空间变小,只需要第二个参数的值小于原空间的大小就好了,因为缩小空间比较简单,这里就不再过多介绍,但需要注意的是,缩小空间可能会造成数据丢失,因此需小心使用

  同时 r e a l l o c realloc realloc函数 不仅能调整空间大小,还能完成 m a l l o c malloc malloc函数 的功能:当第一个参数 p t r ptr ptr 传递的是 空指针 时, r e a l l o c realloc realloc 函数就不再是调整空间大小了,你都没空间,我还怎么调。此时 r e a l l o c realloc realloc 函数会 新开辟 s i z e size size 字节大小的空间
  
  

5.2、应用举例

  看了上面三种情况,大家想一想,应该怎样接收 r e a l l o c realloc realloc 调整之后的返回值呢?
  可以直接用原来的指针 p p p 接收吗?

  显然是不行,如果 r e a l l o c realloc realloc 调整成功,那确实没问题,但如果失败了呢?此时返回的是空指针。本来 p p p 还维护着原来的空间,现在直接变空指针,那原来的空间再也找不到了,这就造成了内存泄漏

  正确的方法是创建新的指针 p t r ptr ptr 来接收,当 p t r ptr ptr 不为 NULL,再将 p t r ptr ptr 的值传给 p p p
  
如下:

#include<stdlib.h>

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail");
		return 1;
	}
	//1 2 3 4 5
	for (int i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;
	}

	//希望将空间调整为40个字节
	int* ptr = NULL;
	ptr = (int*)realloc(p, 40);
	if (NULL != ptr)
	{
		p = ptr;
	}
	else
	{
		perror("realloc fail");
	}

	//调整成功,使用40个字节;调整失败,继续使用20个字节

	/**************
	
	业务处理

	**************/

	free(p);
	p = NULL;
	return 0;
}

  
  

六、常见的动态内存错误

6.1、对NULL指针进行解引用

  

#include<stdlib.h>

int main(
{
	int* p = (int*)malloc(INT_MAX);
	*p = 20;//如果p的值是空指针,就会有问题
	free(p);
	p = NULL;
	return 0;
}

  
  动态开辟的空间,应该先对返回值进行判断,确保空间开辟成功
  上述代码所要开辟的空间太大,开辟失败,返回的是空指针,而下面一句代码对空指针进行解引用,是错误的
  

#include<stdlib.h>

int main()
{
	int* p = (int*)malloc(10 * INT_MAX);
	if (NULL == p)
	{
		perror("malloc fail");
		return 1;
	}

	*p = 20;
	free(p);
	p = NULL;
	return 0;
}

在这里插入图片描述

  

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

  

#include<stdlib.h>

int mian()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

  可以看到,当 i i i = 10 时,就是对 m a l l o c malloc malloc 开辟的空间越界访问了。
  动态内存的空间与数组是非常相似的,要注意不能对其越界访问
  

6.3、对非动态开辟的内存使用 f r e e free free 释放

void test()
{
	int a = 10;
	int* p = &a;
	free(p);//ok?
}

  变量 a a a 并不是动态开辟的变量,用 f r e e free free 释放是错误的
  
  

6.4、使用 f r e e free free 释放一块动态开辟内存的一部分

  这种情况即是,传给 f r e e free free 的指针并不是动态开辟内存的起始地址,指针跑后面去了。

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

  注意:动态内存一定是 一同申请,一同释放。无法做到只释放一部分空间
  

6.5、对同一块动态内存多重释放


void test()
{
	int* p = (int*)malloc(100);
	//···
	free(p);
	free(p);//重复释放
}

  这种释放有办法可以避免:释放完后及时 p p p 置为空指针,这样,即使再次释放,传的是空指针, f r e e free free 什么都不会做,不会造成什么影响
  

6.6、动态开辟内存忘记释放(内存泄漏)

#include<stdlib.h>
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

int main()
{
	test();
	while (1);
	return 0;
}

  上述代码,你会发现,一旦出了函数,就再也找不到开辟的那 100 个字节的空间(这代码写的比较极端,一直死循环,程序一直不结束) 。找不到开辟的空间更别提将其释放,空间就一直在那占着,就造成了内存泄漏

  想一想,如果我们一直向内存申请空间,但从来不释放。要知道,内存的总大小是有限的,这样就会把内存耗干,机器就挂了。

  
总结: m a l l o c malloc malloc c a l l o c calloc calloc r e a l l o c realloc realloc 申请的空间,尽量做到:

  • 谁(可能是函数)申请的就谁释放,即 m a l l o c malloc malloc f r e e free free 成对出现
  • 如果不能释放,要告诉使用的人记得释放

  
  

七、动态内存经典笔试题分析

7.1、题一

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

请问运行 T e s t Test Test 函数会有什么样的结果?

运行 T e s t Test Test 函数,程序会崩溃
  
为什么呢?我们来分析一下
  
先来看这段代码想要做什么:

  1. 首先,它定义了一个 G e t M e m o r y GetMemory GetMemory函数,很明显,这个函数是完成动态开辟空间任务的
  2. 接着 T e s t Test Test函数 中创建了指针 s t r str str,将变量传给 G e t M e m o r y GetMemory GetMemory,即希望指针 s t r str str 管理动态开辟的空间
  3. 最后往空间中存入 " h e l l o w o r l d " "hello world" "helloworld",并打印

  
  代码的逻辑没问题,那就是代码本身出问题咯

在这里插入图片描述

  通过调试我们发现, G e t M e m o r y GetMemory GetMemory函数 并没有改变 s t r str str 的值,它依然是个空指针。

  为什么呢?因为 G e t M e m o r y GetMemory GetMemory传值传参,而不是传址传参!传值传参无法改变主调函数中的值,出了函数 s t r str str 依然是空指针,而后面打印 s t r str str 指向的内容,是要对其解引用的,对空指针解引用自然会出问题。
  同时,函数中 m a l l o c malloc malloc实打实开辟了空间的,只有程序结束才销毁,而函数中的变量出了函数作用域就销毁,这样函数中所开辟的 100 个字节空间出了 G e t M e m o r y GetMemory GetMemory函数 后也无法找到,造成内存泄漏=

  可能有小伙伴会问: G e t M e m o r y GetMemory GetMemory函数 的参数类型就是 c h a r char char* 啊,为什么还是传值传参呢?这里我们要指针传址传参的本质:传递的是变量的地址,因为主调函数中要传的值本身就是指针 c h a r char char* 类型,要改变指针变量,就要传递指针变量的指针,即二级指针。这里可不敢看到 G e t M e m o r y GetMemory GetMemory函数 参数中是 c h a r char char* 就认为他是传址传参
  
正确写法应该是这样:

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}

void Test(void)
{
	char* str = NULL;
	//传str的地址
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	//释放动态空间
	free(str);
	str = NULL;
}

当然, G e t M e m o r y GetMemory GetMemory函数 我们也可以直接让他返回 p p p,以实现目的

char* GetMemory()
{
	char* p = (char*)malloc(100);
	return p;
}

void Test(void)
{
	char* str = GetMemory();
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}

  
  

7.2、题二

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

int main()
{
	Test();
	return 0;
}

运行结果:

在这里插入图片描述

  为什么会这样呢?
  问题还是出在 G e t M e m o r y GetMemory GetMemory函数

   G e t M e m o r y GetMemory GetMemory函数 中创建的 p p p 数组,在出了函数作用域后就销毁了,因此函数返回 p p p,用 s t r str str 接收,而实际上 s t r str str 接收的地址是指向一块已经归还的空间,此时的 s t r str str野指针。再去访问 s t r str str 所指向的空间是非法访问,打印出的值是随机值。
  
  

7.3、题三

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

  可能有小伙伴会觉得这段代码咋一看好像没什么问题啊

  确实,大家有没有发现代码与我们第一题修改后的代码非常像,但大家仔细想想它还缺少什么?

  这段代码唯一的问题是:没有 f r e e free free,动态申请内存空间后他并没有还回去。

  虽然这里没有 f r e e free free 程序也没有问题,因此程序结束后会自动释放空间,但以后遇到复杂的情况就不好说了,因此我们要 养成主动释放内存空间的习惯
  
  

7.4、题四

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

  这题的问题相信大家都能看得出来:
  
   T e s t Test Test函数 上来先开辟 100 字节动态空间,并创建 s t r str str 变量维护它,再向空间中放 " h e l l o " "hello" "hello"
  
  但紧接着,释放 s t r str str却没将 s t r str str 置空,此时的 s t r str str野指针。将空间释放,即将其还给操作系统,我们是没有使用权限了,但是这块空间本身还在
  
  下面的 i f if if 语句判断为真,往 s t r str str 指向的空间放入 " w o r l d " "world" "world",此时 s t r str str 指向的空间我们已经没有使用权限了,但依然进行修改,为非法内存访问。
  
  

八、柔性数组

8.1、什么是柔性数组

  
  也许有些小伙伴从来没有听过柔性数组这个概念,但是它确实是存在的
  C99 中,结构体的最后一个元素允许是未知大小的数组,这就叫做:柔性数组成员
  

typedef struct st_type
{
	int i;
	int a[0];
}type_a;

  有些编译器会报错无法编译,可以改成:

typedef struct st_type
{
	int i;
	int a[];
}type_a;

  
  

8.2、柔性数组的特点

  • 结构体中的柔性数组成员前面必须至少一个其他成员
  • s i z e o f sizeof sizeof 返回的这种结构体大小不包括柔性数组的内存
  • 包含柔性数组成员的结构体一般用 m a l l o c malloc malloc函数 进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小

例如:

typedef struct st_type
{
	int i;
	int a[0];
}type_a;
int main()
{
	printf("%d\n", sizeof(type_a));
	return 0;
}

运行结果:

在这里插入图片描述

  
  

8.2、柔性数组的使用

#include<stdio.h>
#include<stdlib.h>

typedef struct st_type
{
	int i;
	int a[0];
}type_a;

int main()
{
	int i = 0;
	type_a* p = (type_a*)malloc(sizeof(type_a) + 10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc fail");
		return 1;
	}
	//业务处理
	p->i = 10;
	for (i = 0; i < 10; i++)
	{
		p->a[i] = i;
	}
	//调整空间
	type_a* ptr = (type_a*)realloc(p, sizeof(type_a) + 20 * sizeof(int));
	if (ptr != NULL)
	{
		p = ptr;
	}
	free(p);
	p = NULL;
	return 0;
}

柔性数组的结构:在这里插入图片描述

  既然这块空间是 m a l l o c malloc malloc 出来的,也就是说他可以通过 r e a l l o c realloc realloc 来调整大小,所以这个数组可变长变短,不就是柔性
  
  

8.3、柔性数组的优势

  
  上述 t y p e type type_ a a a 结构,也可以设计为下面的结构,也能完成同样的效果

#include<stdio.h>
#include<stdlib.h>

typedef struct st_type
{
	int i;
	int* p_a;
}st_type;
int main()
{
	st_type* p = (st_type*)malloc(sizeof(st_type));
	p->i = 100;
	p->p_a = (int*)malloc(p->i * sizeof(int));

	///业务处理
	for (i = 0; i < 100; i++)
	{
		p->p_a[i] = i;
	}

	//释放空间
	free(p->p_a);
	p->p_a = NULL;
	free(p);
	p = NULL;
	return 0;
}

图示:

在这里插入图片描述

  上述两个方法都可以达到类似的效果

  但是使用柔性数组有两个好处
  

  • 方便内存释放
      如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把结构体返回给用户。用户调用 f r e e free free 可以释放结构体,但是用户并不知道结构体内的成员也需要 f r e e free free,所以你不能指望用户来发现这个事
      所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 f r e e free free 就可以把所有的内存释放掉
      
  • 有利于访问速度
      连续的内存有益于提高访问速度,也有益于减少内存碎片4

  
  

九、C/C++中内存区域划分

在这里插入图片描述

C/C++程序内存分配的几个区域

  • 内核空间操作系统核心(内核)运行的地方,在这个区域,操作系统可以直接访问硬件,并执行特权指令。我们用户是无权访问这块空间的
  • 栈区:在执行函数时,函数内局部变量的存储单元都是在栈上创建,函数执行结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令中,效率很高,但是分配的内存容量有限。栈区只要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  • 堆区:堆区一般是用来存储程序运行期间动态分配内存的地方。堆区的内存分配是在程序运行时动态进行的,程序员可以通过调用标准库函数(如 m a l l o c malloc malloc c a l l o c calloc calloc r e a l l o c realloc realloc等)来在堆区中分配内存,并在不需要时手动释放这些内存(使用 f r e e free free函数)。使用堆区需要注意内存泄漏 m e m o r y l e a k memory leak memoryleak)的问题,即程序在不再需要某块内存时没有释放它,导致程序占用的内存越来越多。
  • 数据段:数据段也叫做静态区,主要用来存放全局变量、静态数据、全局变量。程序结束后由系统释放
  • 代码段:存放函数体(类成员函数和全局函数)的二进制代码、只读常量(字符串常量)

  1. 内存开辟失败:动态内存开辟失败的原因一般都是所空间太大,没有足够的空间 ↩︎

  2. p e r r o r perror perror函数:有关该函数的具体介绍请看:《【C语言】——字符串函数的使用与模拟实现(下)》 ↩︎

  3. 返回值为 1: m a i n main main 函数程序正常退出,返回值为 0;异常退出,返回值为 1 ↩︎

  4. 我们开辟内存时,不会紧接着上一块内存开辟,而会留下一点空隙,开辟次数越多,留下的空隙也就也多,这些空隙称为内存碎片。 ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SSL协议

SSL 安全传输协议&#xff08;安全套接层&#xff09; 也叫TLS ---- 传输层安全协议 SSL的工作原理&#xff1a;SSL协议因为是基于TCP协议工作的&#xff0c;通信双方需要先建立TCP会话。因为SSL协议需要进行安全保证&#xff0c;需要协商安全参数&#xff0c;所以也需要建立…

【数据结构】栈和队列专题

前言 上篇博客我们讨论了栈和队列的有关结构&#xff0c;本篇博客我们继续来讨论有关栈和队列习题 这些题算是经典了 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;数据结构 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d…

Oracle 临时表空间的管理

Oracle 临时表空间的管理 临时表空间的处理 1.创建一个新的temporary tablespace; create temporary tablespace tp tempfile ...... size 10m autoextend on; 2.改变数据库的默认临时表空间 alter database default temporary tablespace tp; 3。drop tablespace temp; …

Zoho CRM企业成长的智能引擎,智能化销售自动化

数字化时代&#xff0c;客户体验已成为企业竞争的核心要素。卓豪Zoho CRM&#xff0c;作为全球领先的SaaS云端客户关系管理平台&#xff0c;正引领着一场企业运营模式的变革&#xff0c;助力超过25万家企业跨越180多个国家&#xff0c;实现客户互动与业务增长的无缝对接。让我们…

Verlog-流水灯-FPGA

Verlog-流水灯-FPGA 引言&#xff1a; ​ 随着电子技术的飞速发展&#xff0c;现场可编程门阵列&#xff08;FPGA&#xff09;已成为电子设计自动化&#xff08;EDA&#xff09;领域中不可或缺的组件。FPGA以其高度的灵活性和可定制性&#xff0c;广泛应用于通信、图像处理、工…

【C++】学习笔记——继承_2

文章目录 十二、继承5. 继承与友元6. 继承与静态成员7. 复杂的菱形继承及菱形虚拟继承 未完待续 十二、继承 5. 继承与友元 友元关系不能继承&#xff0c;也就是说父类友元不能访问子类私有和保护成员 。除非子类也设置成友元。 6. 继承与静态成员 父类定义了 static 静态成…

单用户模式破解root密码

目录 一. 破解root密码 1. 查看操作系统版本 2.重启系统&#xff0c;进入grub菜单&#xff0c;选择要使用的内核&#xff0c;按e进入​编辑 3. 找到linux16那一行&#xff0c;把光标移动到最后&#xff0c;添加 init/bin/sh 然后ctrlx保存退出会自动进入系统 4. 进入系统后…

Spring WebFlux:响应式编程

在软件开发领域&#xff0c;随着互联网应用的规模和复杂性不断增加&#xff0c;传统的编程模型逐渐暴露出一些局限性&#xff0c;尤其是在面对高并发、大规模数据流处理等场景时。为了应对这些挑战&#xff0c;响应式编程&#xff08;Reactive Programming&#xff09;应运而生…

强化训练:day9(添加逗号、跳台阶、扑克牌顺子)

文章目录 前言1. 添加逗号1.1 题目描述2.2 解题思路2.3 代码实现 2. 跳台阶2.1 题目描述2.2 解题思路2.3 代码实现 3. 扑克牌顺子3.1 题目描述3.2 解题思路3.3 代码实现 总结 前言 1. 添加逗号   2. 跳台阶   3. 扑克牌顺子 1. 添加逗号 1.1 题目描述 2.2 解题思路 我的写…

ros键盘控制程序teleop_twist_keyboard 键值含义及用法

在机器人仿真中&#xff0c; 经常会用到键盘控制程序teleop_twist_keyboard 对机器人进行控制。但是对各个键值是何种含义&#xff0c; 如何操作并没有任何资料介绍,初次使用时会不知所措。 通过实践&#xff0c; 发现各个键值的作用如下&#xff1a; u-- 向左前方前进 i-- 直…

java-spring 09 下.populateBean (方法成员变量的注入@Autowird,@Resource)

1.在populateBean 方法中的一部分&#xff1a;用于Autowird&#xff0c;Resource注入 // 后处理器已经初始化boolean hasInstAwareBpps hasInstantiationAwareBeanPostProcessors();// 需要依赖检查boolean needsDepCheck (mbd.getDependencyCheck() ! AbstractBeanDefinitio…

现在闪侠惠递寄快递有福利了,千万不要因没把握住而后悔呀!

闪侠惠递平台寄快递现在真的是太便宜了&#xff0c;优惠价格把握不住&#xff0c;后悔都来不及&#xff01;大家可以在闪侠惠递上面寄快递&#xff0c;价格真的非常优惠呢&#xff0c;比咱们平常寄快递的价格都优惠呢&#xff0c;真的&#xff0c;小编都亲自替大家尝试过了呢。…

联软安渡 UniNXG 安全数据交换系统 任意文件读取漏洞复现

0x01 产品简介 联软安渡UniNXG安全数据交换系统,是联软科技自研的业内融合网闸、网盘和DLP的一体机产品,它同时支持多网交换,查杀毒、审计审批、敏感内容识别等功能,是解决用户网络隔离、网间及网内数据传输、交换、共享/分享、存储的理想安全设备,具有开创性意义。 UniN…

千层烤馍,五彩斑斓的甘肃特色美食

甘肃玫瑰千层烤馍是一道具有浓郁地方特色的传统面点&#xff0c;以其独特的口感和精美的外观而闻名。食家巷的千层烤馍更是其中的佼佼者&#xff0c;它采用了优质的原料和传统的制作工艺&#xff0c;让你品尝到最正宗的甘肃味道。 食家巷千层烤馍的制作过程非常讲究&#xff0c…

CVHub | CVPR 2024 | 英伟达发布新一代视觉基础模型: AM-RADIO = CLIP + DINOv2 + SAM

本文来源公众号“CVHub”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;CVPR 2024 | 英伟达发布新一代视觉基础模型: AM-RADIO CLIP DINOv2 SAM 标题&#xff1a;《AM-RADIO: Agglomerative Vision Foundation Model Reduce Al…

GPT-4o,AI实时视频通话丝滑如人类,Plus功能免费可用

不开玩笑&#xff0c;电影《她》真的来了。 OpenAI最新旗舰大模型GPT-4o&#xff0c;不仅免费可用&#xff0c;能力更是横跨听、看、说&#xff0c;丝滑流畅毫无延迟&#xff0c;就像在打一个视频电话。 现场直播的效果更是炸裂&#xff1a; 它能感受到你的呼吸节奏&#xf…

爆款预警!2024年必火的五大软件应用,你准备好了吗?

2024年必火的五大软件应用可以从多个角度进行预测。首先&#xff0c;人工智能&#xff08;AI&#xff09;的应用将继续扩大其在软件开发和用户体验改善中的作用。AI技术被用于改善用户体验&#xff0c;如聊天机器人&#xff0c;创建数据驱动的战略和决策&#xff0c;预测趋势以…

学习神经网络基础架构

今日学习了解了常见的几种神经网络基础架构。 1.卷积神经网络 卷积神经网络CNN是一种人工神经网络&#xff0c;旨在处理和分析具有网格状拓扑结构的数据&#xff0c;如图像和视频。将 CNN 想象成一个多层过滤器&#xff0c;可处理图像以提取有意义的特征并进行推理预测。 想…

第十五节:贪心算法(下)

一 、 贪心算法的解题套路实战一&#xff08;最多的会议宣讲场次&#xff09; 1.1 描述 一些项目要占用一个会议室宣讲&#xff0c;会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间 你来安排宣讲的日程&#xff0c;要求会议室进行的宣讲的场次最多。…