C++从入门到精通——C++动态内存管理

C++动态内存管理

  • 前言
  • 一、C/C++内存分布
    • 分类1
    • 分类2
    • 题目
      • 选择题
      • sizeof 和 strlen 区别
      • 示例
        • sizeof
        • strlen
  • 二、C语言中动态内存管理方式malloc/calloc/realloc/free
    • 示例
    • 例题
      • malloc/calloc/realloc的区别
      • malloc的实现原理
  • 三、C++内存管理方式
    • new/delete操作内置类型
    • new和delete操作自定义类型
  • 四、operator new与operator delete函数
    • operator new与operator delete函数
    • operator new和new区别
    • operator delete和delete区别
    • new注意事项
    • new可以和free配对吗
    • 示例
      • new开辟自定义类型空间,为什么会比开辟内置类型多几个字节
      • 图片展示
    • malloc会出现自定义类型多开几个字节吗


前言

C++动态内存管理涉及使用newdelete操作符来动态分配和释放堆内存。new用于在堆上分配内存并初始化对象,delete用于释放先前分配的内存。此外,C++还提供了智能指针如std::unique_ptrstd::shared_ptr来自动管理内存,以避免内存泄漏和悬挂指针。这些智能指针在超出作用域时会自动删除其所指向的对象。


一、C/C++内存分布

C/C++内存分布不同的人会有不同的分布,这里列举两条常见的,本文主要基于分类2

分类1

在C/C++中,内存可以被分为几个不同的部分:

  1. 栈(Stack):栈是由编译器自动分配和释放的,用于存储局部变量、函数参数、返回地址等。当函数被调用时,其参数和局部变量会被压入栈中,当函数返回时,栈会被恢复到调用函数之前的状态。

  2. 堆(Heap):堆是由程序员手动分配和释放的,用于存储动态分配的内存。在C中,使用malloc()calloc()函数分配内存,在C++中,使用new关键字分配内存。当不再需要分配的内存时,必须手动使用free()(C)或delete(C++)释放内存,否则会导致内存泄漏。

  3. 全局变量和静态变量(数据段):全局变量和静态变量存储在静态存储区,这一区域在程序运行期间一直存在,直到程序结束。全局变量具有全局作用域,可以在程序的任何地方访问,而静态变量具有局部作用域,但其值在函数调用之间保持不变。

  4. 常量区:常量区存储常量数据,如字符串常量。这部分内存在程序运行期间一直存在,直到程序结束。

  5. 代码区:代码区存储程序的指令。这部分内存在程序运行期间一直存在,直到程序结束。

需要注意的是,不同的操作系统和编译器可能有不同的内存分布方式,以上描述是一种常见情况。另外,还有一些其他的内存区域,如动态链接库的加载区、线程栈等,它们也可能存在于程序的内存分布中。

分类2

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);   free(ptr3);
}

在这里插入图片描述

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。

  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。

  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。

  4. 数据段–存储全局数据和静态数据。

  5. 代码段–可执行的代码/只读常量。

题目

 1.选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)   
	globalVar在哪里?____   		staticGlobalVar在哪里?____
	staticVar在哪里?____   		localVar在哪里?____
	num1 在哪里? ____ 			char2在哪里____     
	*char2在哪里?___
	pChar3在哪里?____      		*pChar3在哪里?____
	ptr1在哪里?____        *ptr1在哪里?____
2. 填空题:
	sizeof(num1) = ____;
	sizeof(char2) = ____;      strlen(char2) = ____;
	sizeof(pChar3) = ____;     strlen(pChar3) = ____;
	sizeof(ptr1) = ____;
3. sizeof 和 strlen 区别?

选择题

在这里插入图片描述

sizeof 和 strlen 区别

C语言从入门到实战——数组和指针的强化练习题可看这篇文章强化一下

sizeof是一个运算符,用于获取一个变量或类型的字节大小。它可以用于任何类型的变量,包括基本数据类型和自定义数据类型。sizeof可以在编译时计算,因此不需要实际运行程序。

strlen是一个函数,用于获取一个字符数组的长度,即字符的个数。它只能用于以null字符('\0')结尾的字符串。strlen在运行时计算字符个数,所以需要遍历整个字符数组来计算长度。

所以,sizeof用于获取变量或类型的字节大小,而strlen用于获取以null字符结尾的字符串的字符个数。

示例

sizeof
int a[] = { 1,2,3,4 };//a数组有4个元素,每个元素是int类型的数据

printf("%zd\n", sizeof(a));//16 - sizeof(数组名)的情况,计算的是整个数组的大小,单位是字节 - 16
printf("%zd\n", sizeof(a + 0));//a表示的就是数组首元素的地址,a+0还是首元素的地址 - 4/8
//int*
printf("%zd\n", sizeof(*a));//a表示的就是数组首元素的地址,*a 就是首元素,大小就是4个字节
printf("%zd\n", sizeof(a + 1));//a表示的就是数组首元素的地址,a+1就是第二个元素的地址,这里的计算的是第二个元素的地址的大小-4/8

printf("%zd\n", sizeof(a[1]));//a[1]是数组的第二个元素,大小是4个字节
printf("%zd\n", sizeof(&a));//&a - 取出的是数组的地址,但是数组的地址也是地址,是地址,大小就是4/8个字节

strlen
char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(arr + 0));//arr+0是数组首元素的地址,地址的大小是4/8个字节
printf("%zd\n", sizeof(*arr));//*arr是数组的首元素,这里计算的是首元素的大小 1
printf("%zd\n", sizeof(arr[1]));//1
printf("%zd\n", sizeof(&arr));//&arr - 是数组的地址,数组的地址也是地址,是地址就是4/8个字节
printf("%zd\n", sizeof(&arr + 1));//&arr+1,跳过整个数组,指向了数组的后边,4/8
printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址 4/8

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	//'a'-97
//printf("%d\n", strlen(*arr));//err
//                   //'b'-98
//printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值

二、C语言中动态内存管理方式malloc/calloc/realloc/free

C语言从入门到实战——动态内存管理,可以看这篇文章,详细了解一下,本文不做过多介绍。

示例

void Test()
{
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);
	// 1.malloc/calloc/realloc的区别是什么?   
	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int) * 10);
	// 这里需要free(p2)吗?11    free(p3 );12 }
	free(p3);
}

例题

malloc/calloc/realloc的区别

malloccallocrealloc都是用于动态分配内存空间的函数。

malloc函数用于分配指定大小的内存空间,它只分配内存,并不对内存进行初始化。示例:void* malloc(size_t size)

calloc函数用于分配指定数量和大小的连续内存空间,并将分配的内存空间初始化为0。示例:void* calloc(size_t num, size_t size)

realloc函数用于重新分配已经分配的内存空间的大小,可以扩大或缩小已经分配的内存空间。示例:void* realloc(void* ptr, size_t size)

总结:

  • malloc只分配内存,不进行初始化。
  • calloc分配内存并初始化为0。
  • realloc重新分配内存的大小,可以扩大或缩小原来的内存空间。

malloc的实现原理

glibc中malloc实现原理
malloc是一个动态内存分配函数,用于在运行时分配指定大小的内存空间。malloc的实现原理如下:

  1. 物理内存管理:操作系统将物理内存划分为多个块,每个块由连续的内存地址组成。当程序运行时,操作系统为其分配一块虚拟内存空间。

  2. 内存管理单元:malloc使用内存管理单元(Memory Management Unit,MMU)来管理物理内存。MMU将虚拟内存空间映射到物理内存空间。

  3. 内存分配算法:malloc使用不同的内存分配算法来选择合适的内存块进行分配。常见的算法有首次适应算法(First Fit)、最佳适应算法(Best Fit)和最坏适应算法(Worst Fit)等。

  4. 内存块分配:当程序调用malloc函数时,内存管理单元会遍历已分配和未分配的内存块链表,选择一个合适的未分配内存块进行分配。

  5. 内存块标记:分配内存块后,内存管理单元会将该内存块标记为已分配状态,并将其从未分配内存块链表中移除。

  6. 内存块返回:malloc函数返回已分配内存块的起始地址给程序,程序可以使用该地址来访问分配的内存空间。

  7. 内存释放:程序使用完分配的内存后,可以调用free函数来释放内存。free函数将被释放的内存块标记为未分配状态,并将其添加到未分配内存块链表中。

  8. 内存合并:当相邻的内存块都处于未分配状态时,内存管理单元会将它们合并成一个更大的未分配内存块,以减少内存碎片的产生。

以上就是malloc的简单实现原理。实际上,malloc的实现可能会更加复杂,考虑到内存对齐、线程安全、内存池等因素。不同的操作系统和编译器也会对malloc进行调优和优化。

三、C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理。

new/delete操作内置类型

void Test()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;

	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);

	// 动态申请10个int类型的空间
	int* ptr6 = new int[10];

	delete ptr4;
	delete ptr5;
	delete[] ptr6;
}

在这里插入图片描述

注意:申请和释放单个元素的空间,使用newdelete操作符,申请和释放连续的空间,使用new[]delete[],注意:匹配起来使用。

new和delete操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}

	~A()
	{
		cout << "~A():" << this << endl;
	}

private:
	int _a;
};

int main()
{
	// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
		A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;

	// 内置类型是几乎是一样的
	int* p3 = (int*)malloc(sizeof(int)); // C
	int* p4 = new int;
	free(p3);
	delete p4;

	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];
	free(p5);
	delete[] p6;

	return 0;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而mallocfree不会。

四、operator new与operator delete函数

operator new与operator delete函数

newdelete是用户进行动态内存申请和释放的操作符,operator newoperator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}

	return (p);
}

/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;

	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;

	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY

		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);

	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));

	_free_dbg(pUserData, pHead->nBlockUse);

	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY

		return;
}

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

operator new和new区别

在C++中,new操作符和operator new是两个不同的概念。

new是一个表达式,用于在堆上动态分配对象,并返回对象的指针。它会执行以下操作:

  1. 调用operator new分配所需的内存空间。
  2. 调用对象的构造函数,在分配的内存空间中创建对象。
  3. 返回指向分配对象的指针。

operator new是一个函数,用于在堆上分配内存空间,但是它不会调用对象的构造函数。它只执行以下操作:

  1. 分配所需的内存空间。
  2. 返回指向分配内存的指针。

所以,可以说new是一个包含了operator new操作的更高级别的操作符。在使用new时,它会自动调用operator new来分配内存,并调用构造函数进行对象的初始化。而直接使用operator new则只分配内存,不会调用构造函数。

在C++中,我们通常使用new来动态分配对象,而不直接使用operator new,因为它提供了更高的抽象级别,并能确保对象的正确初始化。另外,使用new时,还可以使用delete来释放分配的内存,并调用对象的析构函数进行清理。而直接使用operator new分配的内存,则需要使用operator delete来释放内存,没有自动调用析构函数的功能。

operator delete和delete区别

operator deletedelete是在释放动态分配的内存时使用的两个不同的概念。

delete是一个表达式,用于释放通过new操作符动态分配的对象的内存。它会执行以下操作:

  1. 调用对象的析构函数,进行清理工作。
  2. 调用operator delete释放分配的内存。

operator delete是一个函数,用于释放通过operator new分配的内存,它只执行以下操作:

  1. 释放分配的内存空间。

总结一下,delete是一个包含了调用析构函数和operator delete操作的高级别操作符。它不仅释放内存,还能确保对象的析构函数被正确调用。

在C++中,我们通常使用delete来释放通过new分配的内存,因为它提供了更高的抽象级别,并能确保对象的正确清理和释放。而直接使用operator delete来释放内存,则需要自己手动调用对象的析构函数进行清理,没有自动调用析构函数的功能。

new注意事项

在C++中,使用关键字new动态分配内存时,如果分配失败,会抛出std::bad_alloc异常。因此,当我们使用new开辟空间时,不需要显式检查接受的指针是否为空。

如果new分配内存失败,它会抛出异常,程序会捕获该异常并做相应的处理。因此,如果new调用返回了一个非空指针,我们可以确定内存分配成功,不必再额外检查指针是否为空。

然而,当我们使用new分配内存时,还是有一些需要注意的地方:

  • 首先,为了确保分配成功,我们可以在分配前使用std::nothrow,它将在分配失败时返回nullptr,而不是抛出异常。
  • 其次,我们在使用动态分配的内存时,要确保在使用完后及时释放内存,以免出现内存泄漏的问题。可以使用delete来释放动态分配的内存。

总的来说,虽然使用new动态分配内存时不需要显式检查接受的指针是否为空,但在使用动态分配内存的过程中,我们仍需要注意其他相关的问题。

new可以和free配对吗

new是可以和free配对的,当然malloc也是可以和delete配对的,主要的问题是,他们进行配对会在某些特定情况下进行报错,所以我不建议交错使用

示例

存在析构函数会直接导致报错,具体原因是释放空间不对

free不行,delete也不行,只有delete[]可以
在这里插入图片描述

new开辟自定义类型空间,为什么会比开辟内置类型多几个字节

在C++中,使用new关键字来动态分配内存时,分配的内存大小取决于所创建的对象的类型。对于内置类型(如intfloat等),分配的内存大小与其字节大小相同。但对于自定义类型,分配的内存大小可能会比其成员变量的总大小大出几个字节。

这是因为C++编译器在内部为自定义类型的对象维护了一些附加的信息,以便进行对象的构造和析构等操作。这些附加信息可能包括虚函数表指针(如果类具有虚函数)、访问控制信息、数据成员的偏移量等。

总结:自定义类型并不一定比内置类型多开几个字节,主要看存不存在析构函数,如果存在析构函数,使用freedelete是会报错,要使用delete[],因为delete[]底层会自动向前取几个字节

图片展示

在这里插入图片描述

malloc会出现自定义类型多开几个字节吗

在C语言中,使用malloc函数来动态分配内存时,分配的内存大小取决于所请求的字节数,与类型无关。因此,无论是自定义类型还是内置类型,使用malloc函数分配的内存大小都是一样的。

malloc函数分配的内存空间是以字节为单位进行分配的。无论是内置类型还是自定义类型,都需要根据其大小来确定所需的字节数,并将其作为参数传递给malloc函数。因此,相同大小的内置类型和自定义类型,在使用malloc分配内存时,分配的空间大小是相同的。

需要注意的是,与C++不同,C语言中的malloc不会为自定义类型分配额外的字节来存储附加信息,如虚函数表指针。在C中,我们需要自己管理内存,确保为自定义类型分配的空间大小足够存储其成员变量的值,并正确地进行内存访问和释放操作。


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

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

相关文章

底层逻辑(1) 是非对错

底层逻辑(1) 是非对错 关于本书 这本书的副标题叫做&#xff1a;看清这个世界的底牌。让我想起电影《教父》中的一句名言&#xff1a;花半秒钟就看透事物本质的人&#xff0c;和花一辈子都看不清事物本质的人&#xff0c;注定是截然不同的命运。 如果你看过梅多丝的《系统之美…

Lagent AgentLego 智能体应用搭建-作业六

本次课程由Lagent&AgentLego 核心贡献者樊奇老师讲解【Lagent & AgentLego 智能体应用搭建】课程。分别是&#xff1a; Agent 理论及 Lagent&AgentLego 开源产品介绍Lagent 调用已有 Arxiv 论文搜索工具实战Lagent 新增自定义工具实战&#xff08;以查询天气的工具…

不对称催化(三)- 动态动力学拆分动态动力学不对称转化

一、动力学拆分的基本概念&#xff1a; 动力学拆分的最大理论产率为50%&#xff0c;通过的差异可以将两个对映异构体转化为不同构型的产物&#xff0c;通常情况下使用两个不同反应路径来实现。但是化学家们提供了一个更加实用的方法&#xff0c;通过底物的构型变化实现高于50%的…

【Leetcode】377. 组合总和 Ⅳ

文章目录 题目思路代码复杂度分析时间复杂度空间复杂度 结果总结 题目 题目链接&#x1f517; 给你一个由 不同 整数组成的数组 n u m s nums nums&#xff0c;和一个目标整数 t a r g e t target target 。请你从 n u m s nums nums 中找出并返回总和为 t a r g e t targ…

MySQL随便聊----之MySQL的调控按钮-启动选项和系统变量

-------MySQL是怎么运行的 基本介绍 如果你用过手机&#xff0c;你的手机上一定有一个设置的功能&#xff0c;你可以选择设置手机的来电铃声、设置音量大小、设置解锁密码等等。假如没有这些设置功能&#xff0c;我们的生活将置于尴尬的境地&#xff0c;比如在图书馆里无法把手…

炫云云渲染:免费体验与高性价比的首选,设计师们的渲染利器

使用云渲染是要收费的&#xff0c;如果你是第一次使用&#xff0c;是可以白嫖一波云渲染的&#xff0c;所有的云渲染都会或多或少送一些渲染券&#xff0c;你可以用它们送的渲染券免费渲一波图。但是不能一直白嫖&#xff0c;再次注册账号人家就不会送体验券了&#xff0c;有些…

茴香豆:搭建你的RAG智能助理-作业三

本次课程由书生浦语社区贡献者【北辰】老师讲解【茴香豆&#xff1a;搭建你的 RAG 智能助理】课程。分别是&#xff1a; RAG 基础介绍茴香豆产品简介使用茴香豆搭建RAG知识库实战 课程视频&#xff1a;https://www.bilibili.com/video/BV1QA4m1F7t4/ 课程文档&#xff1a;ht…

为什么近年来机器学习这么火!!

机器学习&#xff08;Machine Learning&#xff09;是一种人工智能&#xff08;AI&#xff09;的分支&#xff0c;它让计算机能够通过数据学习和改进&#xff0c;而无需明确的编程。这意味着机器学习系统可以从经验中学习&#xff0c;逐步提高其性能。它基于统计学和数学算法&a…

OpenHarmony实战开发-按钮 (Button)

Button是按钮组件&#xff0c;通常用于响应用户的点击操作&#xff0c;其类型包括胶囊按钮、圆形按钮、普通按钮。Button做为容器使用时可以通过添加子组件实现包含文字、图片等元素的按钮。具体用法请参考Button。 创建按钮 Button通过调用接口来创建&#xff0c;接口调用有…

Unity入门实践小项目

必备知识点 必备知识点——场景切换和游戏退出 必备知识点——鼠标隐藏锁定相关 必备知识点——随机数和Unity自带委托 必备知识点——模型资源的导入 实践项目 需求分析 UML类图 代码和资源导入 开始场景 场景装饰 拖入模型和添加脚本让场景动起来 开始界面 先用自己写的GUI…

贪吃蛇撞墙功能的实现 和自动行走刷新地图 -- 第三十天

1.撞墙 1.1最初的头和尾指针要置为空&#xff0c;不然是野指针 1.2 在增加和删除节点后&#xff0c;判断是否撞墙&#xff0c;撞墙则初始话蛇 1.3在撞墙后初始化蛇&#xff0c;如果头不为空就撞墙&#xff0c;得定义临时指针指向头&#xff0c;释放头节点 2.自动刷新地图 2.1…

4 -26

4-26 1 英语单词100个一篇六级翻译 2 div 4 补题目 3 概率论期中卷子一张&#xff0c;复习复习。 4 备课ing 晚上出去炫饭&#xff0c;串串香&#xff0c;无敌了。 中间一些模拟题是真的恶心&#xff0c;思维题是真的想不到&#xff0c;感觉自己就是一个废物呢。 1.是将一个数…

JUC之线程、线程池

一、start与run方法 start方法开启一个新线程&#xff0c;异步执行。 run方法同步执行&#xff0c;不会产生新的线程。 start方法只能执行一次&#xff0c;run方法可以执行多次。 二、一些方法 sleep() 线程睡眠 两种方式调用&#xff1a; Thread.sleep(1000);TimeUnit.…

Kafka 3.x.x 入门到精通(06)Kafka进阶

Kafka 3.x.x 入门到精通&#xff08;06&#xff09;——对标尚硅谷Kafka教程 3. Kafka进阶3.1 Controller选举3.2 Broker上线下线3.3 数据偏移量定位3.4 Topic删除3.5 日志清理和压缩3.7 页缓存3.8 零拷贝3.9 顺写日志3.10 Linux集群部署3.10.1 集群规划3.10.2 安装虚拟机(略)3…

Ubuntu下载的nginx的位置

位置在/etc/nginx 启动nginx systemctl status nginx上面的命令不合适&#xff0c;就重启nginx sudo service nginx restart 关闭nginx nginx -s stop Ubuntu默认的html地址在该文件夹中的default中&#xff1a; /etc/nginx/sites-available if ($http_host ~* "^(w…

怎么用AI绘画进行人物修复?

用过AI绘画生成人物图片的朋友们是不是都碰到过这样的问题&#xff1a;诡异的造型、崩坏的五官、离谱的手指头、乱七八糟的背景...指望AI一次性生成百分百完美的图貌似有点难啊。 现在AI绘画有了【脸部修复】【手部修复】功能&#xff0c;就能够轻松解决这些的问题了&#xff0…

ESLint 、 e2e test 学习

Lint和Format的区别&#xff1a; Lint只会告诉你代码中的错误或者不符合规范的地方&#xff0c;而Format是用来对格式作调整的 HTML/tpl&#xff1a;HTMLLint CSS/SCSS&#xff1a;Stylelint JS/JSX&#xff1a;Eslint JSLint&#xff1a;古老&#xff0c;不能配置和扩展JSHin…

十几款必备AI写作软件!涵盖国内国外,免费在线一键生成原创文案文章!

今天&#xff0c;就让我带您领略市面上那些备受瞩目的10款AI写作神器&#xff0c;一同探索哪款工具 将成为您创作旅程中的得力助手。首先&#xff0c;让我们揭开这些A1写作工具的神秘面纱 深入了解它们的基本功能。这些工具如同智慧之笔&#xff0c;能够迅速为您生成文章、段 落…

工作记录:vue-grid-layout 修改 margin 导致 item 高度剧烈变化

问题 用 vue-gird-layout 时发现&#xff0c;当改变 margin 值时&#xff0c;item 的尺寸也会跟着变化。 如下图&#xff1a;row height 和每个 item 的 h 都保持不变。修改 margin-y&#xff0c;item 的实际高度也跟着变了&#xff1a; 原因 研究了一番&#xff0c;发现原…

MySql 主从同步-在原来同步基础上增加历史数据库

在MySql已经主从同步的后&#xff0c;由于有新的需求再增加1个历史数据库&#xff0c;要改原来的1个变成现在的2个数据库。在官网并没有找到类似的场景&#xff08;官方同步多个数据是从一开始就设置&#xff0c;不是后续增加的&#xff09;&#xff0c;只能结合以往的经验自己…
最新文章