C语言进阶---动态内存管理

news2024/9/22 5:36:03

1、为什么存在动态内存分配?

我们已经掌握的内存开辟方式有:

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

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

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

但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才能知道,这个时候就只能试试动态内存开辟了。

2、动态内存函数的介绍

2.1、malloc(申请内存空间)和free(释放/回收内存空间)

1、C语言提供了一个动态内存开辟的函数------malloc:

<stdlib.h>      
void* malloc (size_t size);

这个函数向内存申请一块连续可用,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针(返回这块空间的起始地址)。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数size的单位字节为0,malloc的行为是标准是未定义的,取决于编译器。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)malloc(40);
	int i = 0;
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ",* (p + i));
	}
	return 0;
    //没有free,并不是说内存空间就不回收了,当程序退出的时候,系统会自动回收内存空间。
}

输出:

在这里插入图片描述

使用数组申请的内存空间和使用malloc申请的空间在不同的区域上:

在这里插入图片描述

2、free------释放/回收内存空间。

void free(void* ptr);
  • ptr为NULL,则什么事都不做。

  • ptr必须是动态分配的空间。

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

int main()
{
	int arr[10] = { 0 };
	//动态内存开辟
	int* p = (int*)malloc(INT_MAX);
	int i = 0;
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ",* (p + i));
	}
    //释放内存空间
	free(p);
    p = NULL;
	return 0;
}

【注:】free释放的必须是动态内存的空间,也就是说释放的需要是在堆区中的空间,而不应该去释放栈区中的空间。

如下是错误的:

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

int main()
{
	int a = 0; 
	int* p = &a;         //p是栈区里面的空间,不用free来释放
	free(p);
	p = NULL;
	return 0;
}

2.2、calloc

C语言也提供了一个函数叫calloccalloc函数也用来动态内存分配,原型如下:

void* calloc(size_t num,size_t size);
  • num是代表要开辟多少个元素
  • size代表开辟的每个元素是多少字节。

比如:想要开辟40字节的内存,num=10,size=4即可。

  • 返回值是开辟的那块空间的起始地址。

  • 这个函数还有一个特殊的地方:它在返回之前会把将要开辟的内存空间初始化一下,并初始化为全0。

代码验证:在使用calloc开辟好空间之后,我们来打印,看是不是全部初始化为0。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i=0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
    free(p);
	p = NULL;
	return 0;
}

输出:

在这里插入图片描述

malloc和calloc如何选择呢?

如果想要初始化使用calloc,如果不初始化,两个都可以。

calloc相当于malloc+memset。

2.3、realloc

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

函数原型如下:

void* realloc(void* ptr,size_t size);
  • ptr是要调整的内存地址。
  • size是调整后的内存大小。希望要调整为多大的空间。
  • 返回值为调整之后的内存起始位置。
  • 这个函数在调整原内存空间大小的基础上,还会将原内存中的数据移动到新的空间。
  • realloc在调整内存空间的是存在两种情况:
    • 情况1:原有空间之后有足够大的空间。
    • 情况2:原有空间之后没有足够大的空间。

下面先来说下realloc的两种情况:

比如现在有个使用malloc分配的动态内存,大小为40字节。然后现在想要扩容到80字节。

已存在40个字节了,需要扩容为80字节,所以还需要在原有的内存上在使用realloc追加40个字节。

那主要问题就在于这新追加的40字节的内存位置在那。

1、原有空间之后有足够大的空间:

这种情况是直接追加在原有40字节的后面:这个实现很简单就是直接追加就行了。

在这里插入图片描述

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

这个就是如果在原有的40字节的后面直接在追加40个字节的内存后,由于原有空间之后没有足够大的空间,强行追加40个字节,会占用其它数据的内存地址。所以这样肯定是不行的。那如何解决呢?
答案:realloc会找到一个80字节大小的内存空间,然后先把原有的(使用malloc)动态分配的40字节移动到这个80个字节的前40个字节处,然后还剩40个字节,这个算是扩容后的内存地址。

并且,旧的原40个字节内存,会被realloc自动释放回收。

在这里插入图片描述

代码示例:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i=0; i < 10; i++)
	{
		*(p + i) = i+1;
	}
    //将p处的内存地址,扩容到80字节
	int* ptr = realloc(p, 80);
	if (ptr != NULL)
		p = ptr;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

输出:

在这里插入图片描述

2.4、realloc充当malloc

realloc(NULL,40);    ==========       malloc(40);

3、常见的动态内存错误

3.1、对NULL指针的解引用操作

//不进行NULL的判断,这样是存在安全隐患的。
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(40);
	*p = 20;
	return 0;
}


//对指针进行NULL的判断
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	*p = 20;
	free(p);
	p = NULL;
	return 0;
}

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

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	//当i=10时,就越界访问了。
	for (i = 0; i <= 10; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;
	return 0;
}

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

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

int main()
{
	int a = 10;
    //p是非动态开辟内存,是不能用free释放的。
	int* p = &a;
	free(p);
	p = NULL;
	return 0;
}

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

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = i;
		p++;             //p在++之后,p已经不在是起始位置了,所以下面free释放只是释放了一部分,所以不对。
	}
	free(p);
	p = NULL;
	return 0;
}

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

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//多次释放,会报错
	free(p);
	free(p);
	return 0;
}

//改进:要么free一次,要么添加p = NULL;

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

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

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

忘记释放不再使用的动态开辟空间会造成内存泄漏。

切记:动态开辟的空间一定要释放,并且正确释放。

4、几个经典的笔试题

4.1、题目1:野指针—返回栈区空间地址问题

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

void GetMemory(char* p)
{
    //p是形参,在栈区里面存放
	p = (char*)malloc(100);
}

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

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

问:运行结果?

传值调用,str是实参,p是形参,所以说GetMemory运行后,对str没啥影响,str还是空指针。并且p没有内存释放,导致内存泄漏。

所以说运行结果:

  • 内存泄漏
  • str是NULL,在strcpy时,需要传目标内存地址,而不是NULL,所以会导致内存崩溃。

正确修改:

#define _CRT_SECURE_NO_WARNINGS
#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);
  //这个打印相当于:因为即便printf("hello world");,那传给print函数的也是字符'h'的地址,起始是和直接传str地址是一样的道理。
    printf("hello world");
	free(str);
	str = NULL;
}

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

输出:

在这里插入图片描述

4.2、题目2:野指针—返回栈区空间地址问题

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

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

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

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

输出:

在这里插入图片描述

分析:数组p在GetMemory里面,且p存放的是字符’h’的地址,但是当函数GetMemory运行完毕,p数组就会销毁。然后str = GetMemory(),当GetMemory返回值为p,但是p已销毁。所以str是野指针。str指向的那块地址已经被销毁了,所以结果如上。

总结:以上两题都是返回栈区空间地址问题。让一个函数返回函数体里面的变量的地址时,用个变量接收,这个是非常危险的。

4.3、题目3:

int* f1(void)
{
    int x = 10;
    return (&x);
}


//判断下列代码的问题:野指针问题。
//x在函数f1内部,return &x,说明此函数返回个指针,但是这个函数在运行完毕后,x变量会销毁,所以&x就是野指针。

int* f2(void)
{
    int* ptr;
    *ptr = 10;
    return ptr;
}

//也是野指针问题。
//ptr没有初始化,然后*ptr相当于随便找了地址来解引用,相当于随机访问,野指针问题。

4.4、野指针

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

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

分析错误:使用malloc动态内存分配100字节大小的空间。然后拷贝,str里面存放了首字符’h’的地址。当free(str)后,动态分配的100字节大小的空间就交给操作系统回收了。但是因为没有进行str = NULL这一步操作,所以str的值,也就是存放的首字符’h’的地址并没有变。然后str != NULL为真,然后在进行拷贝,然后现在str已经时野指针了。虽然将"world"传给str,但是str指向的地址,已经不归我们使用了,所以在访问有可能时访问其它的数据的空间,所以此程序不对。

在这里插入图片描述

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

在这里插入图片描述

内核空间是用来运行操作系统的。我们写的代码不可以运行在此处。

数据段又是静态区。

代码段:存放我们写的代码进行编译、链接后为可执行程序的二进制指令。

6、柔性数组

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

1、必须在结构体中。

2、必须是最后一个成员。

3、必须是大小未知的数组。

eg:

typdef struct st_type
{
    int i;
    int a[0];    //柔性数组成员
    int b[];     //这个写法也行
}type_a;

6.1、柔性数组的特点

  • 结构中的柔性数组成员前面必须至少有一个其它成员。
  • sizeof返回的这种结构体大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc()函数进行动态内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

例如:

typdef struct st_type
{
    int i;
    int a[0];    //柔性数组成员
}type_a;
prinf("%d\n,sizeof(type_a)");        //输出的是4。

6.2、柔性数组的使用

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

struct S
{
	int i;
	int arr[];   //打算给此柔性数组10个元素的大小。
};

int main()
{
	//包含柔性数组成员的结构用malloc()函数进行动态内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
	//sizeof(struct S)是结构体大小,40就是柔性数组的大小。
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);
	if (ps == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 80);
	if (ptr != NULL)
	{
		ps = ptr;
	}
    free(ps);
    ps = NULL;
	return 0;
}

以后采用柔性数组的方法可以对结构体数组进行动态内存分配。

除此以上使用柔性数组的方法,其实我们也有第二种方法来对结构体中的数组进行动态内存分配:

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

struct S
{
	int i;
	int* arr;
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		printf("%s", strerror(errno));
		return 1;
	}
    //这里为什么需要对结构体进行malloc呢?将结构体malloc是为将结构体中的成员变量i也放在堆区。
	//因为下面我们要将arr进行malloc,为了将结构体中的每个成员一致,所以先也将结构体malloc,这样以来i就放在了堆区里面了。
	ps->i = 100;
	//给指向arr的地址动态分配40个字节大小的空间。
	ps->arr = (int*)malloc(40);
	int  i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	int* ptr = (int*)realloc(ps->arr, 80);
	if (ptr != NULL)
	{
		ps->arr = ptr;
	}
	free(ps->arr);
	free(ps);
    //这里直接一步到位把ps置为NULL,那ps->arr自然而然的就为NULL了。
	ps = NULL;
	return 0;
}

那以上两种方法如何选择呢?

  • 采用柔性数组的方法,只需要一次malloc,后续不够在使用realloc。
  • 而第二种方法,需要两次malloc,后续不够在使用realloc

注意:使用malloc越多,就越需要free,而且还会产生内存碎片。

总结:

  • 采用柔性数组的好处是:方便内存释放。
  • 第二个的好处是:有利用访问速度。

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

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

相关文章

基于JSP+Servlet+Mysql客户管理系统

基于JSPServletMysql客户管理系统 一、系统介绍二、功能展示1.项目骨架2.登录界面3.个人信息修改4.数据分析5.市场管理6.线索管理7、联系人8、客户管理9、交易管理 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java web项目 项目名称&#xff1a;基…

【弱网】clumsy的filter语法设置

clumsy 官方说明 jagt WinDivert 的语法 https://github.com/basil00/Divert/wiki/WinDivert-Documentation#7-filter-languageDivert大神们的改版 clumsy-regoutbound 发送 inbound

CVPR2023 Openlanev2 挑战赛:第一名方案解读

引言: 如第一篇挑战赛的博客(# CVPR2023挑战赛之-开放车道拓扑信息(OpenLane Topology)的测试【1】)所说,这项任务,实际上是一个复合任务,其评价指标也是语义分割、目标检测加拓扑位置关系三个指标的精准性平均值。本博客就第一名公布的Arxiv方法进行了解读,并对topol…

iview input组件clearable清空属性无法清空双向绑定值的原因解决

最近在使用iview新版本 input组件时&#xff0c;刚开始发现使用v-modal绑定的值&#xff0c;在点击clearable清除input框内容时&#xff0c;再次搜索发现参数还是没有被清掉 于是仔细查看代码的细节&#xff0c;才发现问题所在&#xff1a; 原来我绑定的参数的中文名称&#x…

【C1】数据类型,运算符/循环,数组/指针,结构体,main参数,static/extern,编译预处理,gdb,makefile

文章目录 1.数据类型&#xff1a;编译器&#xff08;compiler&#xff09;与解释器&#xff08;interpreter&#xff09;&#xff0c;中文里的汉字和标点符号是两个字节&#xff0c;不能算一个字符&#xff08;单引号&#xff09;2.运算符/循环&#xff1a;sizeof/size_t3.数组…

SQL中的——左连接(Left join)、右连接(Right join)、内连接(Inner join)

前言 最近有一个开发需求&#xff0c;需要实现一个复杂年度报表&#xff0c;前后端都是博主开发&#xff0c;这里的业务逻辑比较复杂&#xff0c;也很锻炼sql能力&#xff0c;这里博主也将表的内外连接做了一个整理分享给大家 一、概念 首先还是介绍一下这三个的定义 1.Lef…

C#难点语法讲解之abstract---从应用需求开始讲解

一、背景故事 我们正常写的脚本&#xff0c;一般都是非常完整的&#xff0c;脚本完整意味着需求完整。 例如&#xff0c;我是售货员&#xff0c;现在苹果5元&#xff0c;梨子3元&#xff0c;西瓜20元&#xff0c; 一个人&#xff0c;来买了5个苹果&#xff0c;4个梨子&#xf…

springcloud二回头

Spring Cloud 一种微服务&#xff0c;以往的单体项目在一定程度的累积后&#xff0c;一些服务需要进行拆分&#xff0c;独立进行开发&#xff0c;然后再合起来&#xff0c;之前的是装成一个包进行部署 不同的搭配&#xff1a; spring cloud和boot的对应版本&#xff1a; Rest…

JavaScript 内置对象 数据类型

目录 JavaScript 内置对象 1.内置对象 2.查文档 2.1MDN 2.2 如何学习对象中的方法 3.Math对象 3.1Math 概述 3.2随机数方法 random() 4. 日期对象 4.1Date 概述 4.2Date() 方法的使用 4.3 日期格式化 4.4获取毫秒的总的毫秒形式 5.数组对象 5.1数组对象的创建 5…

1. Netty核心功能与线程模型详解

Netty 1. 认识Netty2. 第一个Netty程序 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点纠正&#xff0c;勿喷。 课程内容&#xff1a; 01、Netty核心组件快速了解 02、Hello,Netty! 03、深入理解Channel、EventLoop(Group) 04、深…

MySql的使用

目录 一、安装与启动1.1 Ubuntu下的使用1.2 Mac下的使用 二、MySQL图形化管理工具的使用三、数据完整性3.1 数据类型3.2 约束 四、常用的sql语句4.1 数据库操作4.2 数据库表操作1.查看当前数据库中所有表2.查看建表的数据结构3.创建表4.添加字段5.修改字段6. 删除字段7. 查看表…

nodejs之net模块的使用

vscode的底层ipc通讯都是基于node的net模块进行封装&#xff0c;今天大概讲解下net模块的使用 官方文档地址&#xff1a;https://nodejs.cn/api/net.html net模块的作用 net模块提供了基于流的方式构建tcp或ipc服务器和客户端的能力。 node:net 模块提供异步网络 API&#x…

ChatGPT发展与技术基础

一、ChatGPT发展 【ChatGPT——GPT3.5】 诞生于&#xff1a;2022 年 11 月 类型&#xff1a;对话场景的大语言模型 特点&#xff1a;更贴近人的方式与使用者互动&#xff1b;在理解人类意图、精准回答问题、流畅生成结果方面远超人类预期。 功能&#xff1a;可以回答问题、…

bug--两个表格,数据来自于同一个抽屉表格,现在让两个表格的数据 不能一样--处理checked 和 disabled

步骤一、拿到表格数据 步骤二、处理 checked&#xff0c;要区分是A表 还是B表&#xff0c;这个区分要在 A表、B表 数据展示的组件里&#xff08;根源&#xff09;区分 &#xff1a; 点击A表&#xff0c;抽屉表格中A 已选的状态 是 checked 且 disabled&#xff0c;B 已选的 抽…

HCIA复习一

OSI七层模型 7.应用层&#xff1a; 用于人机交互&#xff0c;将抽象语言转化为编码&#xff1b; 6.表示层&#xff1a; 将编码转换为二进制&#xff1b;&#xff08;加解密&#xff0c;压缩解压缩&#xff09; 5.会话层&#xff1a; 管理通信双方的会话&#xff0c;细分为…

数模笔记6.30

目录 一、基础 建模步骤&#xff1a; 论文格式&#xff1a; 写作过程&#xff1a; 赛题类型&#xff1a; 二、优化类模型 2.1简单的优化模型 2.2数学规划模型 三、Lingo 一、基础 建模步骤&#xff1a; 找目标 找条件&#xff08;变量、常量、自己的假设&#xff09…

Tomcat的优化多实例部署

一.tomcat核心组件模块 1.web容器&#xff1a;接受.响应请求 2.展示动态页面 2..JSP容器&#xff1a;翻译java---》servlet 3.serverlet容器&#xff1a;serverlet代码格式是用于JSP容器处理 简述&#xff1a; web容器 &#xff1a;1.接受、响应请求 2.展示动态页面 JSP容…

python之 flask 框架

创建安装虚拟环境 两种方法 第二种 # 先打开cmd 中断 # 查看virtual是否安装过 pip show virtualenv # 安装 pip install virtualenvwrapper-win # workon 查看虚拟环境 vorkon # 切换虚拟环境 # workon 虚拟环境 # mkvirtualenv 创建新的虚拟环境 mkvirtualenv falsk2…

MySQL 字符集与比较规则

字符集与比较规则 一. 字符集相关操作1. 查看字符集1.1 查看数据库当前字符集配置1.2 查看某数据库/数据表字符集 2. 修改字符集2.1 全局修改字符集2.2 修改已有库表字符集 3. 字符集级别二. 比较规则1. 后缀表示含义2. 查看指定数据集比较规则3. 查看/修改数据库/表比较规则 一…

计算机视觉:窥探数字世界的眼睛

目录 简介&#xff1a; 一. 计算机视觉的起源与发展 二. 计算机视觉的应用领域 三. 计算机视觉的挑战与未来发展 结论&#xff1a; 简介&#xff1a; 计算机视觉&#xff08;Computer Vision&#xff09;是人工智能&#xff08;AI&#xff09;领域中的一个重要分支&#…