动态内存管理(C语言)

news2024/9/29 15:20:23

目录

为什么要存在动态内存分配

动态内存函数的介绍

malloc函数

free函数

calloc函数

realloc函数

常见的动态内存错误

对NULL指针解引用错误

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

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

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

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

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

几个经典的笔试题

题目一:

题目二

题目三

题目四

C程序的内存开辟

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

C/C++程序内存分配的几个区域: 看图理解:


 

为什么要存在动态内存分配

我们所熟知的开辟空间的方式,就是声明变量的时候开辟空间

例如: int a=10;(在栈区上开辟四个字节的空间),

char arr[10]={0};(在栈区上开辟连续的10个字节的空间)

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

1、开辟空间的大小是固定的,

2、数组的大小是固定的,它所需要的内存在编译的时候分配!

但我们在正真编写程序的时候对空间的需求不单单是上述的请况,有时候我们需要的空间的大小在运行的时候才能知道,那数组在编译时开辟空间的大小就不能满足我们的需求了!此时我们就需要用到动态内存开辟了,动态内存开辟是在堆区上申请空间!

 那我们该如何动态申请空间呢?在C语言中专门设计了动态内存分配的函数,我们要学会动态内存开辟,就要掌握动态内存开辟函数!

动态内存函数的介绍

malloc函数

C语言提供了动态内存开辟的函数:malloc

 

函数原型:void* malloc (size_t size);

 

函数参数:size_t size

        size_t表示无符号整型,size表示一个无符号整数

        表示要开辟空间的大小单位是字节

 

返回值:void*

        返回任意类型的指针

        返回开辟好空间的地址

 

函数作用:

        这个函数向内存申请一块连续可用的空间,并返回指向这片空间的指针,其中参数size表示要开辟空间的大小单位是字节数,返回类型是void*,表示可以是任意类型的指针,所以malloc函数并不自己决定开辟空间的类型,具体类型在使用的时候由使用者自己来决定。如果空间开辟成功,则返回一个指向开辟好空间的指针。如果开辟失败,则返回一个NULL,因此malloc函数的返回值一定要做检查,看是否开辟成功,在malloc函数中 如果四size的值为0,malloc的行为标准是未定义的,取决于编译器,但其实如果size为0是一种无意义的行为!

 

函数的应用看代码:

#include <stdio.h>
#include <stdlib.h>//动态内存开辟函数头文件
int main()
{
	//在堆区申请100个连续的整型空间,让指针p指向这片空间
	int* p = (int*)malloc(sizeof(int) * 100);

	if (p == NULL)//如果空间开辟不成功
	{
		perror(malloc);//打印错误信息
		return 1;
	}

	//开辟成功开始使用
	int i = 0;
	for (i = 0; i < 100; i++)
	{
		p[i] = i+1;
	}
	//打印
	for (i = 0; i < 100; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
}

 

既然我们主动向操作系统申请了空间,那么我们该如何将这些空间还给操作系统,虽然程序运行结素的时候空间会被自动释放,但是总有一些大型程序,是不会主动停止运行的,或者说它的运行时间会很久,所以当我们用malloc申请空间之后,若没有释放(也就是没有将空间还给操作系统),那么此时可能会存在内存泄漏问题,为了避免内存泄漏,所以我们申请内存之后,要主动将内存还回去(释放),那我们该怎么释放自己申请的内存空间呢?在C语言中给我们提供了一个函数,用来释放空间,该函数就是 free!

 

free函数

C语言提供的free这个函数是专门用来做动态内存的释放和回收的

 

函数原型:void free (void* ptr);

函数参数:void* ptr

        参数ptr是一个任意类型的指针

返回值:void

        无返回值

函数作用:

        free函数是用来释放动态开辟好的内存空间的,如果参数ptr是指向动态开辟内存的指针则将ptr指向的空间还给操作系统,若参数ptr为NULL ,则函数什么都不做,如果ptr指向的空间不是动态开辟的,则free函数的行为是未定义的!free函数也在头文件stdlib.h中声明!

 

函数应用看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//用malloc 函数申请20字节的内存空间
	char* ptr = (char*)malloc(sizeof(char) * 20);
	//判断是否开辟成功
	if (ptr == NULL)
	{
		//打印错误信息
		perror(ptr);
	}
	//使用
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		ptr[i] = 'a' + i;
	}

	//打印
	for (i = 0; i < 20; i++)
	{
		printf("%c ", ptr[i]);
	}

	free(ptr);//释放空间
	ptr = NULL;//将指针置为NULL 避免野指针

	return 0;
}

calloc函数

calloc函数也是C语言提供的动态内存分配的函数

 

函数原型:void* calloc (size_t num, size_t size)

 

函数参数:size_t num, size_t size

        第一个参数num是一个无符号整型

        表示元素个数

        第二个参数size也是无符号整型

        表示要一个元素的大小单位是字节

 

返回值:void*

        返回值是一个任意类型的指针

        返回开辟好的空间的地址

 

函数作用:

       函数是在堆区申请内存的,函数的参数是要开辟空间的元素个数跟每一个元素的大小单位是字节,并把空间的每个字节的内容初始化为0,如果开辟成功则返回这片连续空间的起始地址,如果开辟失败则返回NULL,与malloc函数相比calloc函数会在返回地址之前把申请的每个字节都初始化为0,所以我们有将空间初始化的要求,可以选择calloc函数开辟空间!

 

函数应用看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)calloc(20, sizeof(int));

	//判断是否开辟成功
	if (ptr == NULL)
	{
		perror(calloc);//打印错误信息
		return 1;//开辟失败直接让main函数返回
	}

	//calloc会将每个字节的内容都初始化为0
	printf("calloc自动初始化为0: ");
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		printf("%d ", ptr[i]);
	}
	printf("\n");

	//给ptr指向的空间赋值
	for (i = 0; i < 20; i++)
	{
		ptr[i] = i + 1;
	}
	
	//打印
	for (i = 0; i < 20; i++)
	{
		printf("%d ", ptr[i]);
	}

	free(ptr);//动态开辟的空间都要进行释放,避免内存泄漏
	ptr = NULL;//将指针初始化

	return 0;
}

realloc函数

在有些情况下我们使用动态开辟好内存后,有时候一开始所开辟的空间不够用,或者有时候开辟的空间太大了,为了合理的内存分配,对开辟好的内存大小做灵活调整,C语言给我们提供了一个可以调整开辟好的空间的大小的函数它就是 :realloc函数,realloc函数可以做到对动态开辟内存大小的调整,relloc函数的出现让动态内存管理更加灵活!接下来我们就好好了解一下realloc函数:

函数原型:void* realloc (void* ptr, size_t size);

 

函数参数:void* ptr, size_t size

        第一个参数是任意类型的指针

        表示指向需要调整的空间的指针

        第二个参数是一个无符号整型数

        表示调整后新空间的大小单位是字节        

 

返回值void*

        任意类型的指针

        表示调整好以后新空间的地址

 

函数作用:

        函数的第一个参数是指向需要调整的动态空间的指针,第二个参数是调整之后空间的大小单位是字节,返回的是调整好之后空间的地址,调整失败则返回NULL!

realloc函数在调整空间的时候会有两种情况:

情况1:当需要调整的空间之后有足够大的空间,当有足够大的空间是在原来的开辟的空间后面直接追加,返回的还是原来空间的起始地址!

情况2:当需要调整的空间之后没有足够大的空间,当没有足够大的空间的时候,malloc函数会在堆区重新找一块合适大小的空间来开辟,而在开辟好之后会将原空间里的内容拷贝的新空间中,并将原空间释放掉,返回的是新空间的起始地址!

 

函数应用看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//开辟10个整型空间让ptr指向
	int* ptr = (int*)malloc(sizeof(int) * 10);
	if (ptr == NULL)
	{
		perror(malloc);
		return 1;
	}
	
	//使用空间
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ptr[i] = i + 1;
	}

	//当空间不够用我们进行调整
	int* p = (int*)realloc(ptr, sizeof(int) * 20);
	//使用空间
	for (i = 10; i < 20; i++)
	{
		p[i] = i + 1;
	}
	//打印
	for (i = 0; i < 20; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);//将开辟的空间主动释放掉!
	p = NULL;
	ptr = NULL;

	return 0;
}

relloc函数注意事项:

当调整之后尽量用新的指针来接收这片空间的首地址,尽量避免用指向用旧空间的指针来接收,假设realloc函数调整空间失败的时候我们依旧用旧空间的指针来接收的话,会将指向旧空间的指针置为NULL,导致无法再访问到旧空间,旧空间也没办法人为释放,所以会出现问题,为了规避这种问题,我们尽量用新指针来接收空间的地址,比较保守与安全!

具体看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    //开辟空间
    int* ptr = (int*)malloc(100);
    //判断是否开辟成功
    if (ptr != NULL)
    {
        //业务处理
    }
    else
    {
        exit(EXIT_FAILURE);
    }

    //扩展空间容量
    //代码1,具有风险,万一调整失败会将指向原空间的指针置为NULL
   // ptr = (int*)realloc(ptr, 1000);//调整失败会出问题

    //代码2推荐使用用新指针来接收,即使开辟失败也不影响原空间的访问!
    int* p = NULL;
    p = realloc(ptr, 1000);
    if (p != NULL)
    {
        ptr = p;
    }
    //业务处理
    free(ptr);
    ptr = NULL;
    p = NULL;

    return 0;
}

常见的动态内存错误

对NULL指针解引用错误

对NULL进行解引用操作是非法的!

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

//次操作是非法的!
//int main()
//{
//	int* p = NULL;
//	*p = 20;
//	return 0;
//}

void test(void)
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 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++)
	//{
	//	ptr[i] = i + 1;
	//}

	//正常使用,无越界访问
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ptr[i] = i + 1;
	}
	//打印
	for (i = 0; i <= 10; i++)
	{
		printf("%d ", ptr[i]);
	}

	free(ptr);//释放空间
	ptr = NULL;//指针置为NULL

	return 0;
}

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

动态开辟的空间是在堆区上开辟的,而非动态的空间是在栈区开辟的,free是用来释放动态开辟的空间的,所以不能用free释放栈区的空间,是非法的!

如下代码:

#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	//是不允许的非法的!
	free(p);
	p = NULL;
	return 0;
}

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

用free释放必须从起始位置开始释放这一块空间的全部,不能只释放一半空间或者只释放一部分是不允许的,非法的!

具体看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//开辟空间
	int* p = (int*)calloc(10, sizeof(int));
	
	//判断是否开辟成功
	if (p == NULL)
	{
		perror(calloc);
		return 1;
	}

	p += 2;//改变p的指向
	//不能只释放一部分,得全部释放!
	free(p);
	p = NULL;

	return 0;
}

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

不能对同一块申请的空间进行重复释放,第一次已经释放掉了,再释放这片空间已经不存在了,再次释放就是非法的!

具体看代码:

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

int main()
{
	//开辟空间
	char* ptr = (char*)malloc(20 * sizeof(char));
	//判断是否开辟成功
	if (ptr == NULL)
	{
		perror(ptr);
		return 1;
	}

	//进行释放
	free(ptr);

	//free(ptr);//重复释放是非法的
	return 0;
}

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

当我们开辟好一片空间后,将这块空间忘记释放,会导致内存泄漏,此时风险非常大,假设我们开辟了一块很大的空间,用完之后忘记释放,程序不停止的话,会占用大部分空间,会影响程序的效率!切记动态开辟的内存一定要释放,并且要真确释放,

具体看代码:

#include <stdio.h>
#include <stdlib.h>
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	//忘记释放p
	//函数调用完之后p会被自动销毁
	//此时再也找不到开辟好的空间了
	//也没法将他释放,非常危险
}
int main()
{
	test();
	//while (1);
}

几个经典的笔试题

题目一:

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

void GetMemory(char* p)
{
	//开辟空间
	p = (char*)malloc(100);
	//函数结束的时候p被销毁
	//但空间没有释放存在内存泄漏问题
}

void Test(void)
{
	char* str = NULL;
	GetMemory(str);//函数传过去的是NULL,改变形参不会影响实参!
	//str是NULL指针,不能给NULL指针拷贝内容
	strcpy(str, "hello world");//err
	printf(str);
}

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

解析:

GetMemory函数传过去的是str的内容也就是NULL,用p接收此时的p为NULL,再开辟空间让p指向,最后函数结束的时候p会被销毁,而开辟的空间没有被释放,存在内存泄漏为题,此时str里面还是NULL,给NULL拷贝字符串是不可行的,是错误的!

题目二

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

char* GetMemory(void)
{
	//在栈区开辟一块空间里面存放的是 hello world \0
	char p[] = "hello world";
	return p;//返回数组名,数组名是首元素地址,就是h的地址
	//函数在返回之后函数栈帧会被销毁,p会被销毁
	//销毁之后p指向的内容可能会被修改
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);//打印出来会是烫烫.....
}

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

解析:

Get Memory函数在返回的时候返回的是h的地址,但返回之后函数栈帧会被销毁,在p指向的空间里放的是什么东西我们也不清楚,所以打印出来是一堆乱码!

题目三

#include <stdio.h>
#include <stdlib.h>
void GetMemory(char** p, int num)
{
	//动态开辟空间
	*p = (char*)malloc(num);
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	//str里面存放的是动态开辟空间的地址
	strcpy(str, "hello");//拷贝成功
	printf(str);//打印结果是hello

	//应该加个free比较好一点,释放空间是一个好习惯
}

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

解析:

Get Memory函数传过去的是str指针变量的地址,改变形参会影响实参,*p其实取到的就是str开辟一块动态空间,让str指向,此时在strcpy,一个字符串hello拷贝到str指向的空间中,拷贝是没有任何问题的,打印结果也是hello,但这里有一个错误,动态开辟的空间没有释放,应该加一个free释放str,并将str置为NULL,避免野指针!

题目四

#include <stdio.h>
#include <stdlib.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);//str指向的空间已经被释放了
	//虽然空间被释放了,但str还是指向那片空间的起始地址,是一个野指针

	//更好的改进方法就是在这个位置规避一下野指针,将str置为NULL
	//str=NULL;
	if (str != NULL)
	{
		//强行将world字符串拷贝到str指向的空间中
		strcpy(str, "world");
		printf(str);//world
	}
}

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

解析:

在test函数中str指向的空间已经被释放了,虽然空间被释放了,但str里面存放的还是那片空间的起始地址,str是一个野指针,它不为NULL,再用strcpy强行将world字符串拷贝过去,再用printf打印碰到'\0'结束就能打印出world,但这样操作是不合适的,最合适的方法就是释放完空间后将str置为NULL!

C程序的内存开辟

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

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收 。分配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码

 

C/C++程序内存分配的几个区域: 看图理解:

7637d13b069644cc98a60908a18821f3.png


由图可知:实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。 但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序 结束才销毁 所以生命周期变长。

 

 

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

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

相关文章

客快物流大数据项目(一百零五):启动ElasticSearch

文章目录 启动ElasticSearch 一、启动ES服务端 二、​​​​​​​启动Kibana 启动ElasticSearch

【NI Multisim 14.0虚拟仪器设计——放置虚拟仪器仪表(频率计数器)】

目录 序言 &#x1f3ee;放置虚拟仪器仪表&#x1f3ee; &#x1f9e7;频率计数器&#x1f9e7; &#x1f973;&#x1f973;&#xff08;1&#xff09;“测量”选项组:参数测量区。 &#x1f973;&#x1f973;&#xff08;2&#xff09;“耦合”选项组:用于选择电流耦合方…

CSDN 的故障处理流程,实例分享

CSDN 的研发团队每隔一段时间会和大家分享团队的进展&#xff0c;请看&#xff1a; 2021 年年底的汇报 2022 年上半年的汇报 2022 年下半年的汇报 从上面的报告中大家可以看到&#xff0c;我们在取得进展的同时&#xff0c; 也碰到了很多问题&#xff0c;也有一些困惑。 我写了…

「链表」简析

前言 前言&#xff1a;研究一个数据结构的时候&#xff0c;首先讲的是增删改查。 文章目录前言一、链表简介1. 含义2. 节点组成3. 存储方式1&#xff09;数据在内存中的存储方式2&#xff09;单链表在内存中的存储方式3&#xff09;双链表在内存中的存储方式4&#xff09;循环链…

ZYNQ IP核之MMCM/PLL

锁相环&#xff08;Phase Locked Loop&#xff0c;PLL&#xff09;&#xff0c;一种反馈控制电路&#xff0c;对时钟网络进行系统级的时钟管理和偏移控制&#xff0c;具有时钟倍频、分频、相位偏移和可编程占空比的功能。 Xilinx 7系列器件中的时钟资源包含了时钟管理单元CMT&…

SAPIEN PrimalSQL 2023.1[x64] Crack

SAPIEN PrimalSQL 2023.1 使数据库查询开发和测试变得轻而易举&#xff0c;无论您的数据库类型或供应商如何。 通过单个工具支持多个数据库提供程序。 Access、SQL Server、SQL Server Compact、MySQL、Oracle、ODBC、OLEDB、Sybase 等。 使用Visual Query Builder构建复杂的…

maven基础

一、Maven基础 为什么要学习Maven&#xff1f; Maven作为依赖管理工具&#xff0c;能够管理大规模的jarjarjar包&#xff0c;使用MavenMavenMaven后&#xff0c;依赖对应的JarJarJar包&#xff0c;能够自动下载、方便、快捷切规范。Maven作为构建管理工具&#xff0c;当我们使…

HTTP实用指南

HTTP实用指南 01.初始HTTP 当我们在浏览器地址栏输入一个网址或者关键字&#xff0c;它会给我们跳转到对应的网页&#xff0c;在这一过程中&#xff0c;内部到底是怎么运作的&#xff1f; 总结上述图片过程&#xff0c;用流程图来表示&#xff0c;如下&#xff1a; 处理输入信…

【论文翻译】Semantic Graph Convolutional Networks for 3D Human Pose Regression

【iccv论文】https://openaccess.thecvf.com/content_CVPR_2019/papers/Zhao_Semantic_Graph_Convolutional_Networks_for_3D_Human_Pose_Regression_CVPR_2019_paper.pdf 【github】https://github.com/garyzhao/SemGCN 摘要 在本文中&#xff0c;我们研究了用于回归的图卷积网…

ANR触发机制分析

ANR是一套监控Android应用程序响应是否及时的机制&#xff0c;可以把发生ANR比作是引爆炸弹&#xff0c;那么整个流程包含三部分组成&#xff1a; 埋定时炸弹&#xff1a;system_server进程启动倒计时&#xff0c;在规定时间内如果目标应用进程没有干完所有的活&#xff0c;则…

QEMU之一调试uboot(vexpress-a9)

u-boot版本&#xff1a;u-boot-2017.05开发板&#xff1a;vexpress-a9&#xff08;没办法&#xff0c;目前看到的都是这个开发板&#xff0c;想QEMU调试tiny210,一直没看到怎么修改qemu&#xff09;编译u-boot&#xff1a;make ARCHarm CROSS_COMPILEarm-linux-gnueabi- vexpre…

Avast 发布免费的 BianLian 勒索软件解密器

安全软件公司 Avast 发布了 BianLian 勒索软件的免费解密器&#xff0c;以帮助恶意软件的受害者在不向黑客支付费用的情况下恢复锁定的文件。 在 2022 年夏天 BianLian 勒索软件的活动增加后大约半年&#xff0c;该威胁组织入侵了多个知名组织&#xff0c;解密器的可用性就出现…

swagger(前言技术)

目录 一、swagger简介 1.前后端分离的特点 2.在没有swagger之前 3.swagger的作用 4.swagger的优点 二、swagger入门 1.新建springboot项目 2.集成swagger 3.开发一个controller用于测试 5.启动服务&#xff0c;验证集成效果 三、swagger常用注解 四、swagger使用综…

2022年PTA行业研究报告

第一章 行业概况 PTA是精对苯二甲酸&#xff08;Pure Terephthalic Acid&#xff09;的英文简称&#xff0c;在常温下是白色粉状晶体, 无毒、易燃&#xff0c;若与空气混合&#xff0c;在一定限度内遇火即燃烧。 PTA是重要的大宗有机原料之一&#xff0c;广泛用于化学纤维、轻…

【数据结构入门】-线性表之顺序表(1)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构】 从今天开始&#xff0c;就正式进入数据结构的大门了&#xff0c;把握机会&#xff0c;好好学习&#xff0c;加油。 本文目录…

Arduino环境下对NodeMCU ESP8266将文件直接传入flash的三种方式

flash存储简答介绍 参考&#xff1a;https://www.elecfans.com/consume/572040.html flash存储器又称闪存&#xff08;快闪存储器&#xff09;&#xff0c;就其本质而言&#xff0c;flash存储器属于EEPROM&#xff08;电擦除可编程只读存储器&#xff09;类型。是一种长寿命的…

Java多线程案例之单例模式

目录 一、饿汉模式 二、懒汉模式 前言&#xff1a;单例模式是校招中最常见的设计模式之一。下面我们来谈谈其中的两个模式&#xff1a;懒汉&#xff0c;饿汉。 何为设计模式&#xff1f; 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多…

【涂鸦蓝牙SDK】基于涂鸦蓝牙SDK数据传输与函数接口解析

基于涂鸦蓝牙SDK数据传输与函数接口解析1.【数据初始化部分】2.【蓝牙状态机控制】3.【数据广播过程】4.【涂鸦平台申请设备以及SDK】5.【涂鸦SDK模组源码思路解析】---- 重要&#xff1a;5.1 数据收发5.【移植涂鸦评估】2023.1.21 本文是基于涂鸦SDK的低功耗蓝牙BLE协议的数据…

Linux创建解压后的应用程序的桌面快捷方式

下面用一个例子演示&#xff0c;其他应用也差不多 下载好的安装文件为.tar.xz格式&#xff0c;通常默认在系统的下载文件夹下&#xff08;按你实际路径&#xff09;。右键点击文件&#xff0c;在下拉框中点击提取到此处&#xff08;意思就是解压&#xff09;。 解压后&#xff…

DocArray 0.21.0版本发布!新增OpenSearch后端存储,支持Redis后端存储的多语言文本搜索!...

github.com/docarray/docarrayDocArray 是一个用于处理、传输和存储多模态数据的 Python 工具包。DocArray 提供便捷的多模态数据处理功能&#xff0c;具备基于 Protobuf 提供高性能的网络传输性能&#xff0c;同时也为多种向量存储方案提供统一的 API 接口。&#x1f4a1; Doc…