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

news2024/11/18 10:47:45

【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. 我们开辟内存时,不会紧接着上一块内存开辟,而会留下一点空隙,开辟次数越多,留下的空隙也就也多,这些空隙称为内存碎片。 ↩︎

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

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

相关文章

如何将Hyper-V转VMware?反之亦可

为何要在Hyper-V和VMware之间进行转换呢&#xff1f; 尽管VMware和Microsoft Hyper-V都是当前流行的一类虚拟机监控程序&#xff0c;但它们并不相互兼容。VMware产品使用VMDK格式创建虚拟磁盘&#xff0c;而Hyper-V则使用VHD或VHDX格式创建虚拟磁盘。 有时您可能需要进行这种转…

【Qt-CMake】QT中cmak编译出现CMake Error: The source.. does not match the soused

QT中cmak编译出现CMake Error: The source… does not match the soused 分析 前提是该项目是从另一个路径的项目复制过来的&#xff0c;编写代码时发现无论怎样修改代码&#xff0c;运行后都没有任何变化&#xff0c;以为是qtbug&#xff0c;重构重启都没用&#xff0c;最后…

fatal: fetch-pack: invalid index-pack output

解决方案&#xff1a;git clone --depth1 要克隆的git地址 下载最近一次提交的代码 其他分支的内容都不下载 这样整体下载体量就变小了 执行命令&#xff1a;git clone --depth 1 https://gitlab.scm321.com/ufx/xxxx.git

论文解读--Resolving Target Ambiguities in Automotive Radar Using DDMA Techniques

使用DDMA技术解决汽车雷达中的目标模糊 摘要 多普勒分多址(DDMA)是一种慢时相位编码技术&#xff0c;可以使用传统相控阵硬件实现多输入多输出(MIMO)雷达。然而&#xff0c;众所周知&#xff0c;DDMA会在接收到的多普勒频谱中引起模糊。虽然非对称DDMA已经被提出来缓解目标模糊…

XMind 2023 v23.05.2660软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; XMind 2023 v23.05.2660被视为顶尖思维导图软件&#xff0c;其界面简洁清爽&#xff0c;功能布局直观简单&#xff0c;摒弃繁复不实。尽管体积小巧&a…

PACS医学影像系统全套源码 适应对象:综合医院、军医院中医院、妇幼保健院、专科医院

技术栈 开发语言 : C语言 数据库 : MSSQL 开发工具 : VC 源码类型 : WinForm 适应对象 综合医院 军医院 中医院 妇幼保健院 专科医院 系统框架 云架构、云计算、云存储 平台采用先进的云架构设计&#xff0c;通过云计算、云存储技术让平台低成本、高安全、速度快。 标…

微火全域外卖团购服务,引领商家与合伙人变革行业赛道!

在当今的数字化时代&#xff0c;外卖业务正成为越来越多人的日常生活选择。然而&#xff0c;随着市场的日益饱和和竞争的加剧&#xff0c;传统外卖模式已经难以满足商家和消费者的多元化需求。正是在这样的背景下&#xff0c;全域外卖团购业务应运而生&#xff0c;以其独特的模…

分布式事务技术方案

什么是分布式事务 一次课程发布操作需要向数据库、redis、elasticsearch、MinIO写四份数据&#xff0c;这里存在分布式事务问题。 什么是分布式事务&#xff1f; 首先理解什么是本地事务&#xff1f; 平常我们在程序中通过spring去控制事务是利用数据库本身的事务特性来实现…

第十一讲:指针(3)

第十一讲&#xff1a;指针&#xff08;3&#xff09; 1.字符指针变量1.1存储一个字符1.2存储一个字符串1.3一个有趣的面试题 2.数组指针变量2.1什么是数组指针变量2.2数组指针变量的初始化 3.二维数组传参的本质4.函数指针变量4.1介绍函数指针变量4.2 两段有趣的代码4.2.1代码1…

【Unity】Unity项目转抖音小游戏(二)云数据库和云函数

业务需求&#xff0c;开始接触一下抖音小游戏相关的内容&#xff0c;开发过程中记录一下流程。 抖音云官方文档&#xff1a;https://developer.open-douyin.com/docs/resource/zh-CN/developer/tools/cloud/develop-guide/cloud-function-debug 1.开通抖音云环境 抖音云地址&a…

【Spring源码分析】ResolvableType

【Spring源码分析】ResolvableType 参考 目录 文章目录 【Spring源码分析】ResolvableType一、ParameterizedType 参数化类型&#xff0c;即泛型&#xff1b;例如&#xff1a;List< T>、Map< K,V>等带有参数化的对象;二、GenericArrayType—— 泛型数组 泛型数组…

SOCKET编程(4):SOCKET实战

SOCKET实战 Writen()、Readn()函数 send()函数存在需要发送的字符数len小于函数返回的已发送的字符数(ssize_t)的问题 recv()函数存在需要接收的字符数len小于函数返回的已接收的字符数(ssize_t)的问题 解决上述问题通过Writen()、Readn()函数实现 //buff是数据存储地址&a…

矩阵的压缩存储介绍

引入 概述 特殊矩阵的压缩 对称矩阵 三角矩阵 上三角矩阵&#xff1a;上三角区的元素不同&#xff0c;下三角区的元素相同。 存储不同元素的上三角区(计算前i-1行的所有元素之和(j-i1)[i行的列数]-1[下标由0开始],即以下标为0开始存储的下标)一个相同元素(下三角区) 下三角矩…

Verlog-串口发送-FPGA

Verlog-串口发送-FPGA 引言&#xff1a; ​ 随着电子技术的不断进步&#xff0c;串口通信已成为嵌入式系统和计算机外设中一种广泛使用的异步通信方式。串口通信因其简单性、可靠性以及对硬件资源的低要求&#xff0c;在数据传输领域扮演着重要角色。在FPGA&#xff08;现场可编…

ICode国际青少年编程竞赛- Python-4级训练场-复杂嵌套for循环

ICode国际青少年编程竞赛- Python-4级训练场-复杂嵌套for循环 1、 for i in range(4):Dev.step(i6)for j in range(3):Dev.turnLeft()Dev.step(2)2、 for i in range(4):Dev.step(i3)for j in range(4):Dev.step(2)Dev.turnRight()Dev.step(-i-3)Dev.turnRight()3、 for i …

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习

ICode国际青少年编程竞赛- Python-4级训练场-嵌套for循环练习 1、 for i in range(3):Spaceship.step(4)for j in range(4):Dev.step(2)Dev.turnRight()Spaceship.turnLeft()Spaceship.step(4)Spaceship.turnRight()2、 for i in range(4):Spaceship.step(6)for j in range(3):…

SpringBoot实现图片验证码

引入依赖 <dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version> </dependency>代码实现 package com.qiangesoft.captcha.controller;import com.wf.captcha.*…

【LeetCode刷题记录】简单篇-108-将有序数组转换为二叉搜索树

【题目描述】 给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡 二叉搜索树。 【测试用例】 示例1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,…

面向对象进阶——内部类

1、初始内部类 什么是内部类&#xff1f; 类的五大成员&#xff1a; 属性、方法、构造方法、代码块、内部类 在一个类的里面&#xff0c;再定义一个类。 举例&#xff1a;在A类大的内部定义B类&#xff0c;B类就被称为内部类 public class Outer{ 外部类 public …

计数问题C++

题目&#xff1a; 思路&#xff1a; 1~n之间进行循环遍历&#xff0c;如果i不等于0继续循环&#xff0c;然后求出i的个位数与十位数&#xff0c;如果个位数为要查找的特定数字&#xff0c;计时器就1. 代码&#xff1a; #include<iostream> using namespace std; int n,x…