【C进阶】-- 动态内存管理

news2024/11/25 18:27:12

目录

1. 为什么存在动态内存分配❓

2. 动态内存函数的介绍

2.1 malloc和free✅

①申请:1️⃣

②使用:2️⃣

 ③释放:3️⃣

2.2 calloc

🧨与malloc的区别:

2.3 realloc

3.常见的动态内存错误

3.1 对NULL指针的解引用操作

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

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

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

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

解决方法🍚

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

情况1:如果不使用free释放:程序结束之后,也会由操作系统回收🚩

情况2:如果不使用free释放,程序也不结束,内存泄露🏳‍🌈

4. 几个经典的笔试题

4.1 题目1: 对NULL指针追加字符串

 4.2 题目2:数组在栈空间创建的销毁问题🎉

 4.3 题目3:程序正常执行但内存泄露🎯

4.4 题目4:野指针使用问题🎮


1. 为什么存在动态内存分配❓

我们已经掌握的内存开辟方式有:
int val = 20 ; // 在栈空间上开辟四个字节
char arr [ 10 ] = { 0 }; // 在栈空间上开辟 10 个字节的连续空间

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

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

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。

这时候就只能试试动态存开辟了

2. 动态内存函数的介绍

2.1 mallocfree✅

C语言提供了一个动态内存开辟的函数:void* malloc (size_t size);

假设有人在内存中申请20个字节的空间写成这样是肯定没错的:

void* p=  malloc (20);

  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

void*指针需要注意的特点有:

1.可以接收任意类型的地址(也就是无具体类型的指针)

2.不能+-*/,不能解引用操作,非常麻烦

并且如果在这20个byte里面存放1,2,3,4,5五个整型,那就应该用整型指针接收

int* p=(int*) malloc (20);

这里再提一下内存:

关于malloc的使用的整体的过程:①申请②使用③释放

整体呈现的代码:

int main()
{	//申请
	int* p = (int*)malloc(20);
	//使用
    if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	int i = 0;
    //初始化:
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
     //释放 
     free(p);
  //置为空指针   
  p = NULL;
     return 0;

}

①申请:1️⃣

int* p = (int*)malloc(20);

  • 这个函数向内存申请一块连续可用的空间,如果开辟成功,并返回指向这块空间的指针。

if (p == NULL)
    {
        printf("%s\n", strerror(errno));
    }

  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 由于malloc是可能申请失败的,失败的原因有:

  1.申请开辟的内存空间过大

 2.可能内存不足(设计到linux,不便阐述)

虽然申请了空间,但是操作系统并没有真正的给使用者,只是一个虚拟空间的映射,当使用者真正用到的时候,才会使用内存

3.不确定的地方⁉❎

  • malloc申请0个字节的空间?!

如果参数 size 0malloc的行为是标准是未定义的,取决于编译器

②使用:2️⃣

写法一:指针解引用

int i = 0;
    for (i = 0; i < 5; i++)
    {
        *(p + i) = i + 1;
    }
    for (i = 0; i < 5; i++)
    {
        printf("%d ", *(p + i));
    }

在内存窗口地址处输入p即可监视值的变化

写法二:想象成数组

for (i = 0; i < 5; i++)
    {
        p[i] = i + 1;
    }
    for (i = 0; i < 5; i++)
    {
        printf("%d ",p[i]);
    }

两种写法的运行结果都是:

 需要注意的点是:malloc申请的空间不初始化,打印是随机值 

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

int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}

	free(p);

	return 0;

}

 换成16进制打印:

printf("%x ", *(p + i));

 ③释放:3️⃣

C 语言提供了另外一个函数 free ,专门是用来做动态内存的释放和回收的,函数原型如下:
void free ( void* ptr );
                                          "free 函数用来释放动态开辟的内存"💤💦

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

意思是ptr指向的那块空间一定是动态内存开辟到的一块空间,要是ptr随便指向一块空间,让它free一下,那是不行的

如果参数 ptr NULL指针,则函数什么事都不做。

要是ptr本来就是一个空指针,那free之后什么事情都不会发生

free之前,可以看到p的地址

 free之后,地址没有改变!💢

如果有使用者去使用p的话,那它就是一个野指针了,free这个函数不会帮指针p置空的⭕

  • 要避免野指针问题:free完要主动置为空指针, 这两步不要忘记了:

free(p);//1

p=NULL;//2

2.2 calloc

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

void* calloc (size_t num, size_t size);

#include<stdio.h>
int main()
{
	int* p = (int*)calloc(5, sizeof(int));
	if (p == NULL)
	{
		printf("calloc()-->%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

注意:这段代码是没有初始化任何元素的

执行:

函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0:

 

可以看到里面20个字节全部初始化为0了

🧨与malloc的区别:

👓因为calloc要初始化元素,所以效率是没有malloc高的,根据实际情况去选择吧。👓

2.3 realloc

realloc 函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整
📈函数原型如下:
void* realloc ( void* ptr , size_t size );
  • ptr 是要调整的内存地址
  • size 调整之后新大小
  • 返回值为调整之后的内存起始位置。
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc 在调整内存空间的是存在两种情况:
⇨ 情况 1 :原有空间之后有 足够大的空间

🎃本地扩容:

realloc不会释放空间,free的就是旧的地址(p指向的那块的空间),这块空间地址是连续的,所以会根据地址的顺序连续释放掉

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

🎩异地扩容:

1.realloc会找更大的空间
2.将原来的数据拷贝到新的空间

3.释放旧的空间 --> realloc函数会自己处理,不用我们手动释放(不用手动free)

重新找了一片空间(ptr指向那块空间)进行扩容,那realloc会先将数据拷贝到(ptr指针指向的)新地址处,再将旧(p指向)的空间释放掉


4.返回新空间的地址

realloc返回的是新的地址-->(ptr指针存的地址)

3.常见的动态内存错误

3.1 NULL指针的解引用操作

#include<stdio.h>
int main() {
	int* p = (int*)malloc(20);
	//可能出现对NULL指针的解引用操作
	//所以malloc函数的返回值要判断
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;
   
	return 0;
}

malloc有可能申请失败,返回NULL指针,可能出现对NULL指针的解引用操作,所以malloc函数的返回值要判断

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

#include<stdio.h>
int main() {
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//可能出现对NULL指针的解引用操作
	//所以malloc函数的返回值要判断
	int i = 0;
	//越界访问 
	for (i = 0; i < 10; i++)
	{
		p[i] = i;
	}
	free(p);
	p = NULL;

	return 0;
}

打开内存监视可以发现:

✨发现:

这里越界了,等于甚至超过下标为5它依旧会初始化,但不会发生错误,只有到程序执行到free那一步才会发生错误

个人理解:

这个是因为vs对于越界的检查是不太严格的,可以理解为抽查,可能会检查到,也可能检查不到。

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

arr创建这个变量在栈上,不在堆上

注意:数组也是变量,可以理解为它是一种特殊的变量类型,是由同一类型的元素组成的

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5 };
	int* p = arr;

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

}
free函数用来释放动态开辟的内存
  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的

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

#include<stdio.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p = NULL)
	{
		printf("%s\n", strerror(errno));
		return 0; 
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i + 1;
		p++;
	}
	//释放
	free(p);
	return 0;
}
原因:
申请了40个字节,如果要释放整块空间,必须提供起始位置地址才能释放掉,如果提供中间位置地址,那程序会崩溃的。

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

错误代码:

int main()
{
	int* p = (int*)malloc(20);
	if (p == NULL)
	{
		return 1;
	}
	//使用
	free(p);

	free(p);
	p = NULL;

	return 1;
}

重复释放malloc申请开辟的空间会导致程序崩溃

解决方法🍚

free(p);

p=NULL;//这是解决的方法🍭

free(p);  //置空之后,这里什么事情都不干

p=NULL;

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

写一下前面malloc,calloc,realloc的共同之处:

malloc--calloc--realloc
1️⃣所申请的空间,如果不想使用,需要free释放

❗❗

2️⃣如果不使用free释放:程序结束之后,也会由操作系统回收

注意:函数结束和程序结束不是一回事,整个程序的生命周期到了结束了,它会释放这个内存的。
3️⃣如果不使用free释放,程序也不结束,
内存泄露 

情况1:如果不使用free释放:程序结束之后,也会由操作系统回收🚩

#include<stdio.h>
test()
{
    int* p = (int*)malloc(20);
}
int main()
{
    test();
    return 0;
}

情况2:如果不使用free释放,程序也不结束,内存泄露🏳‍🌈

问:什么时候程序不结束?

答:死循环

#include<stdio.h>
test()
{
    int* p = (int*)malloc(20);
}
int main()
{
    test();
   while (1);
    return 0;
}

main函数中,死循环 程序没有结束 那么那段空间也就一直没有释放

死循环那是因为:
while(1);    相当于while(1){};

条件是1永远是真,while(1)一直运行,那就是死循环了

4. 几个经典的笔试题

4.1 题目1: 对NULL指针追加字符串

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

 注意点:

1.str是值传递,不是地址,可以理解为一个特殊的变量

2.如果在GetMemory函数里面free的话,又违背了这题的初衷,因为它就想返回动态内存开辟的空间,然后让strcpy函数将字符串追加到首地址上去

解决问题:

🌈写法一:通过返回值的方式带回100个字节的地址

char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;//修改位置
}
void Test(void)
{
	char* str = NULL;
	str=GetMemory(str);//修改位置
	strcpy(str, "hello world");
	printf(str);
	free(str);//修改位置
	str = NULL; //修改位置
}
int main()
{
	Test();
	return 0;
}

把申请的100个字节的起始地址返回来,由指针变量str接收

⚡简洁写法:不需要传参

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

💧写法二:通过二级指针的方式

 插叙一条重要的知识点:

#include<stdio.h>
int main()
{
    char* p = "hehe\n";//这里是把"hehe\n"放到p里面去了?不是,把这个表达式(字符串)首字符h的地址,放到p里面去了 
    

    printf("hehe\n");//这里其实是把字符串首字符的地址传给了printf

    return 0;
}

回到原题:

这题的问题出现在哪?str是值传递,传给p的时候p是str的一份临时拷贝,对形参的修改不会影响str,把指针变量的str的地址取出来,写成&str,那形参就该写成char**p,这样的处理方法是不用通过返回值也能把地址带回来

void* GetMemory(char**p)
{
	* p = (char*)malloc(100);//这里*p就是str
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);//关于这里的疑惑可以看上面插叙的知识
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

以上三种写法结果都是:

 4.2 题目2:数组在栈空间创建的销毁问题🎉

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}

执行结果:

返回栈空间地址的问题:

GetMemory函数内部创建的数组是临时的,虽然返回了数组的起始地址给了str,但是数组的内存出了GetMemory函数就被回收了,而str依然保存了数组的起始地址,这时如果使用str,str就是野指针

看到这是不是觉得跟某一题很眼熟🥽

关于左右两段代码,区别是一个在堆区,一个在栈区,左边位于堆区的那个GetMemory函数出了函数,那段空间还保留着,没有销毁,而右边位于栈区的那段空间,出了GetMemory函数就由操作系统回收了

简单提一下函数栈帧的现象:

int* test()
{
	int a = 10;
	return &a;
}


int main()
{
	int* p = test();
	printf("%d\n", *p);

	return 0;
}

 由上面提到的知识我们可知这个p是野指针,但是依然记得这块空间的地址,并且打印该值

可是加上了这一段之后,p的值就改变了

int* test()
{
    int a = 10;
    return &a;
}


int main()
{
    int* p = test();
    printf("hehe\n");//添加的
    printf("%d\n", *p);
    return 0;
}

 4.3 题目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);
}

执行结果:

错误:

内存泄露

该注意的点:

这题是可以正常执行的,内存泄露看的是:空间有没有正常释放,不看程序结束之后的结果 

申请的内存由于某种原因没有被及时释放,导致内存空间被占用但无法再被程序使用或回收,导致内存泄露

4.4 题目4:野指针使用问题🎮

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

执行:

free是将空间还给操作系统了,但是地址是没有销毁的,这个str还是保存的之前的地址,但

是继续这样访问是不对的,因为空间已经不属于str了

简单来说就是:空间是销毁还给操作系统了,地址还记着,依旧使用

图解:

 代码修改:

void Test(void)
{
 char *str = (char *) malloc(100);
 strcpy(str, "hello");
 free(str);

str=NULL;//修改位置
 if(str != NULL)
 {
 strcpy(str, "world");
 printf(str);
    }
}

接下来还有柔性数组等的知识,欢迎大佬补充

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

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

相关文章

这5个PNG免抠素材网站,可商用,赶紧马住了

推荐5个超好用的PNG素材网站&#xff0c;免费下载&#xff0c;还可以商用&#xff0c;建议收藏起来~ 1、菜鸟图库 https://www.sucai999.com/searchlist/66008----all-0-1.html?vNTYxMjky 网站主要分享设计素材为主。像平面海报、免抠元素、背景图片、UI界面模板、图标、电商…

流辰信息微服务平台:数字化转型的优良工具!

在互联网迅猛发展的今天&#xff0c;越来越多的企业倾向于新兴领域带来的便利性和灵活性了&#xff0c;其中&#xff0c;微服务平台就是其中之一了。流辰信息微服务平台是专注于研发系统开发、数据治理、数据分析的平台&#xff0c;致力于为各中大小型企业提供优质的微服务解决…

修炼汇编语言第二章:内存地址空间(概述)

目录 前言 一、主板和接口卡 二、存储器各类芯片 三&#xff1a;内存地址空间 总结 前言 什么是内存地址空间呢&#xff1f;如果地址线为10&#xff0c;那么可以寻址1024个地址空间&#xff0c;这1024个地址空间就构成这个CPU的内存地址空间&#xff0c;下面本文将会介绍…

HTB-DevOops

HTB-DevOops 信息收集5000端口 立足python反序列化攻击XEE读取SSH root 信息收集 5000端口 根据文字所述&#xff0c;下面的图片是feed.py。 目录扫描 /upload如下&#xff1a; 上传测试xml文件。 得到反馈 怀疑是标签不匹配&#xff0c;尝试寻找匹配的标签。前面首页有提…

linux平台移植qt

话不多说直接开干&#xff0c;首先需要下载源码包&#xff0c;进入网址https://download.qt.io/archive/qt/进行下载对应的版本即可&#xff0c;比如我这里下载5.12.12版本的&#xff0c;如下图找到即可。 然后把下载的包放到服务器上进行解压tar xpf qt-everywhere-src-5.12.…

2023PGA塑料行业发展新机遇

什么是PGA塑料? PGA塑料是生物降解塑料中的一种&#xff0c;具有可完全分解的酯结构和降解速度最快的脂肪族聚酯类高分子材料&#xff0c;且无需特定降解条件&#xff0c;同时具有良好的耐高温性、机械强度、降解速率和生物相容性。 从政策面来看&#xff0c;随着中国“限塑…

【问题记录】docker 搭建 minio

一、搭建过程 docker 搜索minio镜像 docker search miniodocker 拉取镜像 docker pull minio/miniodocker 启动 minio docker run -p 9900:9900 --name minio -d --restartalways -e MINIO_ACCESS_KEYminio -e MINIO_SECRET_KEY1qazWSX -v /usr/local/minio/data:/data -v …

【2023软考】信息系统监理师与系统集成项目管理工程师哪个更好考?

肯定是系统集成项目管理工程师更好考。 软考信息系统监理师是一项国家级专业职业资格证书&#xff0c;是我国信息技术行业的重要职业资格之一。软考信息系统监理师主要从事信息系统建设项目的监理和管理工作&#xff0c;包括项目前期准备、项目实施阶段和项目验收阶段的监理和…

Databend 开源周报第 90 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 元数据优化 最…

推荐给工控人的时间管理办法

艾维利时间管理法 Ivy Lee Time Management Act 艾维利时间管理法是一种被广泛采用的时间管理方法&#xff0c;它能够帮助人们更好地利用时间和精力。该方法通过将任务分为三类&#xff0c;即A类、B类和C类&#xff0c;来确定工作的优先级。在这种方法中&#xff0c;A类任务是…

【技术选型】Mysql和ES数据同步方案汇总

文章目录 背景一、Mysql和ES各自的特点为什么选用Mysql为什么选用 ES 二、数据同步方案1、同步双写2、异步双写&#xff08;MQ方式&#xff09;3、基于Mysql表定时扫描同步4、基于Binlog实时同步5、业界目前较为流行的方案&#xff1a;使用canal监听binlog同步数据到es 三、数据…

Java学习笔记-01

目录 jdk安装及环境配置 java前置知识 编写一个HelloWorld 常量 数据类型 变量 类型转换 算数运算符 一元运算符( --) 关系运算符 逻辑运算符(与或非&#xff0c;逻辑异或) 三元(三目)运算符 Scanner类的简单使用 jdk安装及环境配置 看到的一篇文章&#xff0c;…

Vue 过渡与动画的使用

transition 标签的作用&#xff1a;在插入 更新或 移除 DOM 元素时 可以给元素添加动画效果. transition 标签配合的 class 类有&#xff1a; - v-enter:进入的起点。 - v-enter-active:进入时的效果。 - v-enter-to:进入的终点。 - v-leave:离开的起点。 - v-leave-active:离…

USB-C为什么可以取代传统接口?消费类电子产品如何改用TYPE-C接口?

现如今&#xff0c;越来越多的设备选择使用Type-C接口来取代之前传统的接口。例如&#xff1a;手机去掉了之前的Micro USB接口&#xff0c;而选择Type-C&#xff1b;还有好多笔记本也取消了很多传统的传输接口&#xff0c;而采用Type-C来替代它们。那为何Type-C可以取代那么多的…

chatGPT电脑端怎么安装-chatgpt国内怎么用

chatgpt怎么用 ChatGPT是一个大型语言模型&#xff0c;可以用于自然语言生成和理解任务&#xff0c;比如对话系统、文本生成、翻译、摘要等。您可以使用以下步骤来使用ChatGPT: 选择一个平台&#xff1a;ChatGPT可以在不同的平台上运行&#xff0c;比如Python、JavaScript、Jav…

【Java基础练习题】多线程IO流操作实现文件的复制(举一反三)

前言&#xff1a;时不我待&#xff0c;忽而已春&#xff0c;初夏将至。然惊觉自身Java基础仍薄弱不堪&#xff0c;虽“雄关万道真如铁”&#xff0c;只得“万里关山从头越”。把基础打扎实才是根本&#xff0c;对于日后的工作而言也是极为重要。通过不断的学习和理解加上手动实…

使用vue2搭建项目的流程

论坛项目 服务器地址: http://172.16.11.18:9090 http://xawn.f3322.net:10004/ swagger地址: http://172.16.11.18:9090/doc.html http://xawn.f3322.net:10004/doc.html 前端h5地址: http://172.16.11.18:9099/h5/#/ http://xawn.f3322.net:10005/h5/# 前端管理系统…

回文自动机(PAM)入门路线 + P3649 【模板】[APIO2014] 回文串(PAM)

个人比较推荐的回文自动机学习路径&#xff1a; 回文自动机学习博客&#xff1a; 回文树&#xff08;讲的最严谨&#xff0c;oiwiki上的&#xff09; 回文自动机&#xff08;Palindrome Automanton PAM&#xff09;&#xff08;讲的最通俗易懂&#xff0c;知乎上的&#xff09…

Docker快速部署springboot项目

有很多开发者在项目部署过程中都会遇到一些繁琐的问题&#xff0c;比如打包、上传、部署等。而使用Docker可以非常方便地解决这些问题。在本文中&#xff0c;将详细讲解如何使用IDEA中的docker打包插件&#xff0c;将代码打包并直接发布到服务器上。这样&#xff0c;我们就可以…

CSS中的 clip 属性

参考&#xff1a;https://baijiahao.baidu.com/s?id1757136902803734131&wfrspider&forpc 作用&#xff1a; clip 属性用来设置元素的形状&#xff0c;用于剪裁绝对定位的元素。当一幅图像的尺寸大于包含它的元素时&#xff0c;clip 属性允许规定一个元素的可见尺寸…