高效利用内存资源之动态内存管理详解

目录

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

二、动态内存函数的介绍

2.1malloc

2.2free

2.3calloc

2.4realloc

三、常见的动态内存错误

3.1对NULL指针的解引用操作

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

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

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

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

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

四、经典笔试题

🌴题目一: 

 🌴题目二: 

 🌴题目三: 

  🌴题目四: 

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

六、柔性数组

6.1柔性数组的特点

 6.2柔性数组的使用

6.3柔性数组的优势

结语:


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

截止目前我们已经掌握的开辟空间的方式有以下两种:

int main()
{
	//在栈空间上开辟四个字节,存放一个值
	int a = 10;
	//在栈空间上开辟10个字节的连续空间,存放一组数
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

	return 0;
}

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

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

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组在编译时开辟空间的方式就不能满足了。这个时候C语言给了我们程序员一种权利:能够动态申请和管理内存空间,即动态内存的开辟。有了这种权力,我们就能很好的解决上述所面临的问题了。

二、动态内存函数的介绍

 🌴函数头文件都是:

#include <stdlib.h>

2.1malloc

"malloc"函数用于动态分配指定大小的内存空间,并返回一个指向该内存空间的指针。

🌴函数原型: 

void* malloc (size_t size);
  • 其中,size参数表示需要分配的内存空间的大小,单位为字节。
  • 函数返回一个指向分配的内存空间的指针,如果分配失败则返回 NULL。

 🌴函数用法示例:

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

int main()
{
	//申请一块空间,用来存放10个整型
	int* ptr = (int*)malloc(10 * sizeof(int));
	//如果返回NULL,则代表空间申请失败,并打印失败原因
	if (ptr == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	//因为这个函数返回一个指向分配好的内存块开头的指针,
	//所以ptr这个指针变量相当于这块内存空间的首地址
	for (i = 0; i < 10; i++)
	{
		*(ptr + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ptr[i]);
	}

	return 0;
}

🌴打印结果:

 🌴注意事项: 

  1. 如果开辟成功,则返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  3. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定。
  4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

malloc函数申请的空间有两种释放方式:

  1. free释放 --- 主动回收。
  2. 程序退出后,malloc申请的空间会被操作系统回收 --- 被动回收。

注:正常情况下,谁申请的空间,谁去释放,万一自己不释放,也要交代给别人记得释放。

2.2free

动态分配内存允许程序在运行时分配和释放内存,这对于处理大量数据或者需要灵活管理内存的程序非常重要。但是,如果不及时释放已经分配的内存,就会导致内存泄漏,最终可能导致程序崩溃。在C语言中,我们可以使用free()函数释放已经分配的内存。

 🌴函数原型: 

void free (void* ptr);

 free函数非常简单,它只需要传入一个指向已经分配的内存块的指针,就可以将该内存块释放回操作系统。

  🌴函数用法示例:

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

int main()
{
	//申请一块空间,用来存放10个整型
	int* ptr = (int*)malloc(10 * sizeof(int));
	//如果返回NULL,则代表空间申请失败,并打印失败原因
	if (ptr == NULL)
	{
		perror("malloc");
		return 1;
	}
	*ptr = 10;
	printf("ptr指向的值为:%d\n", *ptr);

	free(ptr);
	ptr = NULL;

	return 0;
}

🍄解析:

  •  在上述代码中,首先使用malloc函数分配一个整型变量的内存空间,并将其赋值给指针变量ptr。然后,将整型变量的值设置为10,并输出该值。最后,使用free函数释放了该内存空间。
  • 需要我们清楚的一点是,malloc的作用是申请一块空间供当前程序使用,而free函数的作用是把申请的这块空间还给操作系统。然而,free虽然将空间还给了操作系统,但指针变量ptr却还记得申请的这块空间的地址,这时候它就变成了一个野指针,这是非常危险的,所以我们要在free释放完空间后将ptr置为空指针NULL。

🌴注意事项: 

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

2.3calloc

"calloc"函数和"malloc"函数相似,都是用来分配内存的。不同之处在于"calloc"函数在分配内存时会将内存中的每个字节初始化为0。

 🌴函数原型:

void* calloc (size_t num, size_t size);
  • "calloc"函数接收两个参数,分别是要分配的元素个数和每个元素的大小。
  • 它会分配总共 num*size 个字节的内存,并将这些内存空间初始化为0。 

   🌴函数用法示例:

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

int main()
{
	int* ptr = (int*)calloc(10, sizeof(int));
	if (ptr == NULL)
	{
		perror("calloc");
		return 1;
	}
	//打印
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(ptr + i));
	}
	//释放
	free(ptr);
	ptr = NULL;

	return 0;
}

🍄解析:

 

在上述代码中,首先使用calloc函数分配了10个整型变量的内存空间 ,并将其赋值给指针变量ptr。由于calloc函数会将分配的内存空间初始化为0,因此在使用循环输出ptr指向的内存空间时,会发现所有的值都是0。最后,使用free函数释放了该内存空间。所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

2.4realloc

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整,那 realloc 函数就可以做到对动态开辟内存大小的调整,即realloc函数用于重新分配之前通过malloc、calloc或realloc函数分配的内存块的大小。

  🌴函数原型:

void* realloc (void* ptr, size_t size);
  • ptr:之前分配的内存块的指针。
  • size:新的内存块的大小。
  • 返回值为调整之后的内存起始位置。
  • realloc函数会尝试将之前分配的内存块的大小改变为size字节,并返回指向新内存块的指针。如果无法满足新的大小要求,realloc函数可能会在不同的位置重新分配内存块,并将原内存块的内容复制到新的内存块中。

    🌴函数用法示例:

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

int main()
{
	//分配10个整型的内存块
	int* ptr = (int*)calloc(10, sizeof(int));
	if (ptr == NULL)
	{
		perror("calloc");
		return 1;
	}
	//初始化内存块
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ptr[i] = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ptr[i]);
	}
	//空间不够,希望调整空间为20个整型的空间
	int* p = (int*)realloc(ptr, 20 * sizeof(int));
	if (p != NULL)
	{
		ptr = p;
	}
	//释放
	free(ptr);
	ptr = NULL;

	return 0;
}

 🌴注意事项:  

1.realloc函数在调整内存空间时存在两种情况:

🍄情况1:原有空间之后有足够大的空间:


 

 🍄情况2:原有空间之后没有足够大的空间:

2.如果ptr为NULL,则realloc的行为类似于free,即它将分配一个新的内存块。 

int main()
{
	int* p = (int*)realloc(NULL, 10 * sizeof(int));
	if (p == NULL)
	{
		perror("realloc");
		return 1;
	}
	free(p);
	p = NULL;

	return 0;
}

三、常见的动态内存错误

3.1对NULL指针的解引用操作

int main()
{
	int* p = (int*)malloc(40);
	//不做返回值判断,就可能使用NULL指针解引用,就会报错
	*p = 20;

	return 0;
}

🍂这时候鼠标点上去编译器就会提示你有错误: 

🌴正确写法:

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
	}
	//不做返回值判断,就可能使用NULL指针解引用,就会报错
	*p = 20;

	free(p);
	p = NULL;

	return 0;
}

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

int main()
{
	int* ptr = (int*)calloc(10, sizeof(int));
	if (ptr == NULL)
	{
		perror("calloc");
		return 1;
	}
	//初始化内存块
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		ptr[i] = i;//当i是10的时候越界访问
	}
	for (i = 0; i <= 10; i++)
	{
		printf("%d ", ptr[i]);//当i是10的时候越界访问
	}
	free(ptr);
	ptr = NULL;

	return 0;
}

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

int main()
{
	int a = 10;
	int* p = &a;

	free(p);
	p = NULL;

	return 0;
}

上述代码中的变量a是我们在栈上面开辟的空间,而free只能释放malloc、calloc、realloc开辟的空间,它们都是在堆区上面开辟空间。

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

int main()
{
	int* ptr = (int*)calloc(10, sizeof(int));
	if (ptr == NULL)
	{
		perror("calloc");
		return 1;
	}
	//初始化内存块
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*ptr = i;
		ptr++;
	}
   // 0 1 2 3 4 0 0 0 0 0
	free(ptr);
	ptr = NULL;

	return 0;
}

在上述代码中,我们初始化内存块的时候,刚开始*ptr指向所开辟的这块空间的第一个元素,将它初始化为0,然后ptr++,指向第二个元素,初始化为1,直到循环结束,所开辟的这块空间前5个元素就会被初始化为0 1 2 3 4,而因为它是calloc开辟的空间,所以剩下的5个元素就会被初始化为0 0 0 0 0。而现在当这个循环结束的时候ptr++,它会指向第6个元素,紧接着直接释放内存,这个时候程序就会出现问题。所以我们要释放也是全部释放。
 

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		//...
		return 1;
	}
	free(p);

	//...
	free(p);

	return 0;
}

解决办法是第一次释放完空间后将p置为NULL指针,因为当free函数的参数为NULL指针是它什么事也不做。

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

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);
}

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

四、经典笔试题

🌴题目一: 

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

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

//请问运行Test 函数会有什么样的结果?
int main()
{
	Test();

	return 0;
}

🍂解析:

上述这段代码运行起来后最终会崩溃。首先程序运行起来后先进入main函数,然后调用Test函数,进入Test函数后,创建指针变量str并将它置为NULL指针。紧接着再调用GetMemory函数,将实参str传给形参p,并将str的值也一并传给了形参,然后malloc开辟100个字节的空间并将它的起始地址返回给p,即p指向此时开辟的这100个字节的空间。当它返回被调用的GetMemory函数时,因为它是形参,一旦函数执行完毕,形参的值和状态将不再保留,所以p所占用的内存空间将会被释放,但malloc开辟的这块空间还存在,此时因为p不见了,所以没人能找到这块空间。函数继续往下执行,要将"hello world"拷贝到str指向的空间,但因为str是空指针,这儿必然会对空指针进行解引用操作,所以会出现程序崩溃的情况。而且程序运行结束时,malloc开辟的空间也没有释放,所以还会发生内存泄漏的情况。

🌻改正后的代码: 

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

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

	free(str);
	str = NULL;
}

int main()
{
	Test();

	return 0;
}

因为形参是实参的一份临时拷贝,所以改变形参并不会影响到实参。在上面改进后的代码中,我们将str的地址传给形参,并用二级指针变量来接收,然后解引用p,找到str让他指向malloc函数开辟的空间,就可以将"hello world"拷贝过去了。

 🌴题目二: 

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

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
//请问运行Test 函数会有什么样的结果?
int main()
{
	Test();
	return 0;
}

🍂解析:

在上述代码中,我们还是先进入主函数,然后调用Test函数,进入Test函数内部首先创建str指针变量并将它置为NULL指针,然后调用GetMemory函数并将它的返回值赋给str,进入GetMemory函数内部,创建一个数组,里边存放的是"hello world",这儿我们应该清楚这个p数组它是在栈上边创建的,进入这个函数创建,出这个函数销毁。然后返回p(此时p是数组名,代表的是数组首元素的地址),str接收返回的p,此时,str里边存放的就是p的地址值,但是,虽然将地址值带回来了,创建的那块空间却不属于我了(因为出了函数后,创建的空间就会还给操作系统,它的使用权限就不属于我了)。所以,当我们再去使用str指针的时候,它就会变成野指针,这种问题属于返回栈空间地址的问题。  

要想解决这个问题也很简单,第一种方法可以在局部变量char p[ ]前面加关键字static修饰,这样它就变成了静态局部变量,它会一直存在于内存中,直到程序结束。第二种方法是用malloc函数来开辟空间,只要程序不退出或主动回收,这块空间就一直存在。

 🌴题目三: 

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
//请问运行Test 函数会有什么样的结果?
int main()
{
	Test();
	return 0;
}

 🍂解析:

上面这段代码虽然能打印出结果来,但它在使用完malloc开辟的空间后没有将它释放,所以说存在内存泄漏的问题。

  🌴题目四: 

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

//请问运行Test 函数会有什么样的结果?
int main()
{
	Test();
	return 0;
}

 🍂解析:

上面这段程序将"hello"拷贝到malloc函数开辟的空间后,直接将这块空间释放掉了,但str还是存着这块空间的地址,所以在if语句中判断时,它依然为真,那就进入函数内部,将"world"拷贝到str指向的这块空间的地址,此时的str已经变成了野指针,对野指针进行操作,就是非法访问内存。

其实这段代码考察的点在free释放完空间后有没有将str置为NULL指针。

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


 

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

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

有了这幅图,我们就可以更好的理解static关键字修饰局部变量的例子了。实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁所以生命周期变长。

六、柔性数组

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

🎈例如: 

struct s
{
	char c;
	int i;
	int arr[0];//未知大小的数组--柔性数组成员
};

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

struct s
{
	char c;
	int i;
	int arr[];//未知大小的数组--柔性数组成员
};

6.1柔性数组的特点

1、结构中的柔性数组成员前面必须至少一个其他成员。

2、sizeof 返回的这种结构大小不包括柔性数组的内存。

struct s
{
	char c;//1
	int i;//4
	int arr[0];//未知大小的数组--柔性数组成员
};

int main()
{
	printf("%d\n", sizeof(struct s));

	return 0;
}

 

3、包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

struct s
{
	char c;
	int i;
	int arr[0];//未知大小的数组--柔性数组成员
};

int main()
{
	struct s* pc = (struct s*)malloc(sizeof(struct s) + 20);
	if (pc == NULL)
	{
		perror("malloc");
		return 1;
	}

	//释放
	free(pc);
	pc = NULL;

	return 0;
}

 6.2柔性数组的使用

//代码1
struct s
{
	char c;
	int i;
	int arr[];
};

int main()
{
	struct s* pc = (struct s*)malloc(sizeof(struct s) + 20);
	if (pc == NULL)
	{
		peror("malloc");
		return 1;
	}
	pc->c = 'w';
	pc->i = 100;
	for (int i = 0; i < 5; i++)
	{
		pc->arr[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", pc->arr[i]);
	}
	//空间不够了,增容
	struct s* ptr = (struct s*)realloc(pc, sizeof(struct s) + 40);
	if (ptr != NULL)
	{
		pc = ptr;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	//增容成功后,继续使用
	// ......
	
	//释放
	free(pc);
	pc = NULL;

	return 0;
}

6.3柔性数组的优势

🌵上述的代码也可写成下面这样: 

//代码2
struct s
{
	char c;
	int i;
	int* data;
};

int main()
{
	struct s* pc = (struct s*)malloc(sizeof(struct s) + 20);
	if (pc == NULL)
	{
		peror("malloc1");
		return 1;
	}
	pc->c = 'w';
	pc->i = 100;
	pc->data = (int*)malloc(20);
	if (pc->data == NULL)
	{
		perror("malloc2");
		return 1;
	}
	for (int i = 0; i < 5; i++)
	{
		pc->data[i] = i;
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", pc->data[i]);
	}
	//空间不够了,增容
	int* ptr = (int*)realloc(pc->data, 40);
	if (ptr != NULL)
	{
		pc->data = ptr;
		return 1;
	}
	//释放
	free(pc->data);
	pc->data = NULL;

	free(pc);
	pc = NULL;

	return 0;
}
上述代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

🍂第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

🍂第二个好处是:有利于访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

结语:

动态内存分配是一项强大而灵活的功能,它允许程序再运行时动态的分配和释放内存。然而,与之相伴而来的是一些潜在的问题和风险。在使用动态内存时,我们需要特别注意内存泄漏、野指针、内存碎片等问题,以及合理的管理内存的生命周期。 

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

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

相关文章

普冉(PUYA)单片机开发笔记(7): ADC-轮询式多路采样

概述 应用中经常会有使用单片机进行模数转换的需求。PY32F003 具有 1 个 12 位的模拟数字转换器&#xff08;ADC&#xff09;&#xff0c;今天我们一起来使用一下这个 ADC。 数据手册中对 ADC 简介如下。 SAR ADC&#xff1a;逐次逼近式 ADC&#xff0c;原理参见“参考链接&a…

Weblogic-wls-wsat-unserialize_CVE-2017-10271

文章目录 Weblogic < 10.3.6 wls-wsat XMLDecoder 反序列化漏洞1. 漏洞描述2. 漏洞复现2.1 环境启动2.2 漏洞扫描2.3 漏洞验证 3. 修复建议 Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞 1. 漏洞描述 说明内容漏洞编号CVE-2017-10271漏洞名称Weblogic <…

手机搭建kali

kali是著名的黑客专用系统&#xff0c;一般都是直接装在物理机或者虚拟机上&#xff0c;我们可以尝试把kali安装在手机上&#xff0c;把手机打造成一个便携式渗透神器。 我们需要下载以下3款软件&#xff1a; (1).Termux(终端模拟器) (2).AnLinux(里边有各种安装liunx的命令…

[架构之路-261]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 网络数据交换格式

一、网络数据交换格式 1.1 什么是网络数据交换格式 网络数据交换格式指的是在计算机网络中传输和存储数据时所采用的特定格式。 它定义了数据的组织方式、结构和编码规则&#xff0c;以便不同系统和应用程序之间能够准确地解析和处理数据。 网络数据交换格式的主要目的是&a…

内存映射机制

什么是内存映射 Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来&#xff0c;以初始化这个虚拟区域的内如&#xff0c;这个过程称为内存映射。 代码示例&#xff1a; /******************************************************************** > File Name: mmap…

java--StringBuilder、StringBuffer、StringJoiner

1.StringBuilder ①StringBuilder代表可变字符串对象&#xff0c;相当于是一个容器&#xff0c;它里面装的字符串是可以改变的&#xff0c;就是用来操作字符串的。 ②好处&#xff1a;StringBuilder比String更适合做字符串的修改操作&#xff0c;效率会比更高&#xff0c;代码…

Vue prop 子组件 给 父组件 使用sync传值 双向数据绑定

父传子 Vue prop组件间通信&#xff08;父传子&#xff09; 父组件 <User :name"userName" />data() {return {userName: 生产队的驴}}子组件 <span>用户名&#xff1a;{{name}}</span><button click"alter">点击给父组件传值&…

IDEA启动应用时报错:错误: 找不到或无法加载主类 @C:\Users\xxx\AppData\Local\Temp\idea_arg_filexxx

IDEA启动应用时报错&#xff0c;详细错误消息如下&#xff1a; C:\devel\jdk1.8.0_201\bin\java.exe -agentlib:jdwptransportdt_socket,address127.0.0.1:65267,suspendy,servern -XX:TieredStopAtLevel1 -noverify -Dspring.output.ansi.enabledalways -Dcom.sun.management…

【3DsMax】制作简单的骨骼动画

效果 步骤 首先准备4个板子模型展开放置好 添加一个4段的骨骼 选中其中的一块板子添加蒙皮命令 在蒙皮的参数面板中&#xff0c;设置每块板子对应哪块骨骼 设置好后你可以发现此时就已经可以通过骨骼来控制模型了 接下来就可以制作动画 点击左下角“时间配置”按钮 设置一下动…

Vue3: 给表格多个字段添加排序功能

问题 在Vue3项目中&#xff0c;使用element-plus的表格组件绘制表格后&#xff0c;需要令表格的多个字段可以进行选择排序&#xff08;选择升序或者降序&#xff09;但是排序功能好像有时候会出错&#xff0c;需要排序的字段多了之后&#xff0c;排序功能有时候会不起作用 解…

C++初阶(十四)list

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、 list的介绍二、list的模拟实现1、list的节点2、list 的迭代器3、list4、打印5、完整代码…

数据表记录的操作

一、数据添加 1、打开SSMS&#xff0c;附加数据库&#xff08;数据库文件在自己的文件夹下面&#xff09;&#xff0c;并进行下面的设置&#xff1a; &#xff08;1&#xff09;设置“部门信息”表中的“编号”为主键&#xff08;SSMS&#xff09; 首先建立好所需的数据库库…

Grounding DINO、TAG2TEXT、RAM、RAM++论文解读

提示&#xff1a;Grounding DINO、TAG2TEXT、RAM、RAM论文解读 文章目录 前言一、Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection1、摘要2、背景3、部分文献翻译4、贡献5、模型结构解读a.模型整体结构b.特征增强结构c.解码结构 6、实…

python画动漫形象(魔法少女小圆晓美焰,super beautiful)

1.源代码 import turtle as te import time WriteStep 15 # 贝塞尔函数的取样次数 Speed 5 Width 600 # 界面宽度 Height 500 # 界面高度 Xh 0 # 记录前一个贝塞尔函数的手柄 Yh 0 def Bezier(p1, p2, t): # 一阶贝塞尔函数 return p1 * (1 - t) p2 * t def Bezier_2(x1…

智能优化算法应用:基于蜻蜓算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蜻蜓算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蜻蜓算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜻蜓算法4.实验参数设定5.算法结果6.参考文献7.MA…

计算机毕业设计 SpringBoot的二手物品交易平台 二手商城系统 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

C++初阶-vector的介绍及使用

vector的介绍及使用 一、vector的介绍1.1 vector的概念 二、vector的使用2.1 vector的定义2.2 vector iterator的使用2.3 vector空间增长问题2.4 vector的增删改查2.5 vector的整体代码实现2.5.1 vector的常用内置函数使用2.5.2 vector的访问方式及测试函数 三、vector迭代器失…

windows系统安装RocketMQ_dashboard

1.下载源码 按照官网说明下载源码 官网 官网文档 2.源码安装 2.1.① 编译rocketmq-dashboard 注释掉报错的maven插件frontend-maven-plugin、maven-antrun-plugin mvn clean package -Dmaven.test.skiptrue2.2.② 运行rocketmq-dashboard java -jar target/rocketmq-…

API测试基础之http协议

http简介&#xff1a; http&#xff08;超文本传输协议&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP&#xff08;传输控制协议&#xff09;之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出…

分布式分布式事务分布式锁分布式ID

目录 分布式分布式系统设计理念目标设计思路中心化去中心化 基本概念分布式与集群NginxRPC消息中间件&#xff08;MQ&#xff09;NoSQL&#xff08;非关系型数据库&#xff09; 分布式事务1 事务2 本地事务3 分布式事务4 本地事务VS分布式事务5 分布式事务场景6 CAP原理7 CAP组…
最新文章