【C/C++】内存管理详解

news2024/10/2 12:04:55

目录

  • 内存布局思维导图
  • 1.C/C++内存分布
    • 数据段:
    • 栈:
    • 代码段:
    • 堆:
  • 2.C语言中动态内存管理方式
  • 3.C++内存管理方式
    • 3.1new/delete操作内置类型
    • 3.2new和delete操作自定义类型
  • 4.operator new 与 operator delete函数
  • 5.new和delete的实现原理
    • 5.1内置类型
    • 5.2自定义类型
  • 6.必须匹配的操作符
    • 6.1内置类型
    • 6.2自定义类型
      • new -- free
      • new[]--free/new[]--delete
  • 7.定位new表达式
    • 7.1使用格式:
    • 7.2使用场景:
    • 7.3内存池
  • 8.面试题
    • malloc/free和new/delete的区别
  • 9.总结和博客思维导图

内存布局思维导图

在这里插入图片描述

  • 上图为内存布局的思维导图,有关整篇博客的思维导图放在的最后的总结部分

1.C/C++内存分布

我们对比图片观察不同的数据存放在那个内存空间的那一块(对C语言内存管理不熟悉可查看该篇博客:【C语言】内存管理):

在这里插入图片描述

  • 栈: 又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  • 内存映射段: 是高效的I / O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享的共享内存,做进程通信。
  • 堆: 用于程序运行时动态内存分配,堆是可以上增长的。
  • 数据段: 存储全局数据和静态数据。
  • 代码段: 可执行的代码/只读常量

接着我们对上图的代码分布情况做出如下解释(上图的代码放在解释后,有需要的可以自己复制运行一下)

数据段:

全局变量,static修饰创建的静态变量一定存放在数据段,存放静态数据和全局数据的地方,这个没有异议。

栈:

localNum变量num1数组 正常在栈上开的空间创建,也很好理解。

至于char2、pChar3、ptr1、ptr2和ptr3 ,我们需要明白一点,不论是数组还是指针,不论接收的是常量字符串还是程序员自己开辟的空间的地址,它们终究是在函数内,是在栈上创建的变量,用来存放数据罢了,它们作为变量的地址就在栈上。

代码段:

在C语言中我们就应该知道,使用双引号括起来的数据为常量字符串,这样的字符串为只读常量,不能修改,存放在代码段(常量区),这是规定好的,我们理解记忆即可,但其中有一些比较迷惑人的地方需要解释一下:

  1. 使用数组和指针的形式接收常量字符串结果是不同的。

    使用数组接收: 如上图的char char2[]="abcd";,常量字符串存放在代码段,在赋值时,char2在栈上开辟一个相同大小的数组,通过拷贝将每个字符存放在数组中(包含最后的’\0’终止字符),所以我们可以修改数组char2中的数据。

    在这里插入图片描述

    使用指针接收:

    • C++: 如上图的const char* pChar3 = "abcd";,这个就要好理解了,就是简单的将常量字符串的首地址存入指针变量pChar而已,所以pChar3的地址是在栈上,而指针变量pChar3所存的地址是在代码段中。因为常量字符串不能修改,所以在C++中不允许权限发生改变,必须使用const关键字修饰指针。

      在这里插入图片描述

    • C: 在C语言中,使用指针接收字符串常量则可以不使用const修饰指针,甚至我们可以使用指针来修改常量字符串编译器不会报错,但这样的程序是运行不了的,程序在执行到常量字符串修改哪一行就会停下(如下图),所以我们用C语言遇到这种情况最好还是使用const修饰。

      在这里插入图片描述

堆:

程序员使用malloc、calloc、realloc 开辟的空间必然是放在堆上的,这样的空间,程序员自己申请也需要自己使用free 释放。空间在堆上开辟好后,分别使用指针接收这些空间的首地址,所以接收这些地址的指针变量是在栈上开辟,但它们所存储的地址却是在堆上。

代码如下:

int globalNum = 1;
static int staticGlobalNum = 1;

int main()
{
	static int staticNum = 1;
	int localNum = 1;
	int num1[10] = { 1,2,3,4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int));
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2,sizeof(int) * 4);

	free(ptr1);
	free(ptr3);

	return 0;
}

2.C语言中动态内存管理方式

C语言动态开辟内存的方式即为malloc、calloc和realloc,最后由free释放开辟的空间,这是学习C必须要掌握的对象,而面试时也会经常问道相关的问题,我们通过下面两道面试题在来看一下

1.malloc/calloc/realloc的区别?

  • malloc向堆区申请空间,参数为申请内存的大小
  • calloc向堆区申请空间,第一个参数为申请空间的每一个字节的初始值,第二个参数为申请空间的大小
  • realloc向堆区申请空间,第一个参数为要调整的地址,第二个参数为调整后新的空间的大小,若第一个参数为空,则realloc和malloc相同。
  • 向堆申请的空间必须使用free释放,防止内存泄漏。

2.malloc的实现原理

  • GLibc堆利用入门-机制介绍

(比较复杂,建议结合操作系统一起学习)

3.C++内存管理方式

C语言管理内存的方法(malloc、calloc、realloc、free)在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,比如说自定义类型,因此C++提出了自己的内存管理方式:通过new和delete操作符进行内存管理。

3.1new/delete操作内置类型

我们以int类型为类

  1. 动态申请一个int类型的空间

    int* ptr1 = new int;//申请空间
    delete ptr1;//释放空间
    

    直接new后跟类型即可向堆申请空间。

  2. 动态申请一个int类型的空间并初始化为8

    int* ptr2 = new int(10);//申请空间
    delete ptr2;//释放空间
    

    在类型后加小括号,在其内填写需要初始的值

    • 若小括号内不添加任何值,默认初始化为0
  3. 动态申请10个int类型的空间

    int* ptr3 = new int[10];//申请空间
    delete[] ptr3;//释放空间
    

    在类型后加中括号,其内填需要申请的同类型空间的个数

  4. 动态申请10个int连续的空间并初始化

    在中括号后加小括号,在其内不添加任何值,默认初始化为0

    int* ptr4_1 = new int[10]();//申请空间
    delete[] ptr4_1;//释放空间
    

    在中括号后加大括号,在其内填写数组的值,不足的部分默认为0

    int* ptr4_2 = new int[10] {1,2,3,4};//申请空间
    delete[] ptr4_2;//释放空间
    

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],需要匹配起来使用,不能乱用。(原因请看 6.必须匹配的操作符

通过下面代码查看,创建空间是否成功

int main()
{
	int* ptr1 = new int;
	int* ptr2_1 = new int(10);
	int* ptr2_2 = new int();
	int* ptr3 = new int[4];
	int* ptr4_1 = new int[4]();
	int* ptr4_2 = new int[4]{ 1,2 };

	printf("ptr1:%p %d\n", ptr1, *ptr1);
	printf("ptr2_1:%p %d\n", ptr2_1,*ptr2_1);
	printf("ptr2_2:%p %d\n", ptr2_2, *ptr2_2);
	for (int i = 0; i < 4; i++)
	{
		printf("ptr3:%p %d\n", &ptr3[i], ptr3[i]);
	}
	for (int i = 0; i < 4; i++)
	{
		printf("ptr4_1:%p %d\n", &ptr4_1[i], ptr4_1[i]);
	}
	for (int i = 0; i < 4; i++)
	{
		printf("ptr4_2:%p %d\n", &ptr4_2[i], ptr4_2[i]);
	}

	delete[] ptr4_1;
	delete[] ptr4_2;
	delete[] ptr3;
	delete ptr2_1;
	delete ptr2_2;
	delete ptr1;

	return 0;
}

在这里插入图片描述

3.2new和delete操作自定义类型

new 和 delete操作自定义类型和操作内置类型相同。

new/delete 和malloc/free对于自定义类型时,不同的是申请空间时:new会调用构造函数,释放空间时:delete会调用析构函数

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

int main()
{
	A* ptr1 = new A(1);
	delete ptr1;

	A* ptr2 = new A;
	delete ptr2;

	A* ptr3 = new A[4];//调用四次构造函数
	delete[] ptr3;//释放四次构造函数

	A* ptr4 = new A[4]{ 1,2,3,4 };//为每次调用的构造函数传值
	delete[] ptr4;

	return 0;
}

在这里插入图片描述

对于malloc和free,什么都不会调用

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

int main()
{
	A* ptr1 = (A*)malloc(sizeof(A));
	if (ptr1 == nullptr)
	{
		perror("malloc fail!");
		exit(-1);
	}
	free(ptr1);

	A* ptr2 = (A*)malloc(sizeof(A)*4);
	if (ptr1 == nullptr)
	{
		perror("malloc fail!");
		exit(-1);
	}
	free(ptr2);

	return 0;
}

优势:

  1. 更简洁
  2. 申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc它们三个不会
  3. new申请空间失败会抛出异常,而malloc需要自己判断是否申请成功(一些严格的编译器必须要判断)

4.operator new 与 operator delete函数

new和delete 是用户进行动态内存申请和释放的操作符operator new 和 operator delete 是系统提供的全局函数

  • new在底层调用operator new 全局函数来申请空间
  • delete在底层通过operator delete 全局函数来释放空间。

我们编写如下代码,通过汇编查看

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

int main()
{
	A* ptr = new A;

	delete ptr;

	return 0;
}

在这里插入图片描述

  • 根据这些我们知道,new在申请空间时是通过operator new 申请,之后调用构造函数初始化。

至于delete我使用的VS2019不方便查看,大家要明白它底层调用的是operator delete ,还有下面的一点。

  • 注意:对于自定义类型的对象,一定是先调用析构函数,清理掉对象的数据,之后再使用operator delete 释放空间,否则空间之间释放,析构函数就没办法调用了。(内置类型没有这个需求,直接释放)

接着我们看一下operator new 和 operator delete 是则么实现的:

operator new:

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

在这里插入图片描述

  • 观察第5行,operator new 实际上是通过malloc 来申请空间
  • malloc 申请空间成功时直接返回;
  • 当申请失败,且用户设置了空间不足应当措施,则尝试执行该措施,继续申请。若没有设置,则抛出异常。

operator delete:

/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

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;
}
  • 观察第4行和18行,operator delete 实际上是通过free 释放空间的

我们可以将operator new 和 operator delete 看作是对malloc和free 的封装,之所以要这么做是因为C++是面向对象的语言,当申请空间发生错误时不能像C那样去处理问题,直接返回个NULL,需要让用户更直观的看到问题的出现,需要增加异常 这个功能,如果了解一些Java的知识,想必对异常并不陌生,这正是面向对象的特点。

5.new和delete的实现原理

5.1内置类型

申请内置类型的空间,new和malloc,delete和free基本类似, 不同的地方有以下两点:

  1. new/delete申请和释放单个元素的空间,new[]/delete[]申请和释放的是连续的空间,malloc和free不用区分这些
  2. new再申请空间失败时会抛出异常,malloc会返回NULL

5.2自定义类型

  • new的原理

    1. 调用operator new函数 申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理

    1. 在空间上执行析构函数,完成对象中资源的清理工作。
    2. 调用operator delete函数 释放对象的空间
  • new T[N]的原理

    1. 调用operator new[]函数 申请空间

      operator new[] 中实际调用operator new函数完成N个对象的申请。

    2. 在申请的空间上执行N次构造函数

  • delete[]的原理

    1. 在释放的对象空间上执行N次析构函数,完成N个对象资源的清理

    2. 调用operator delete[] 释放空间

      与operator new[]相同,operator delete[]中调用operator delete来释放空间。

6.必须匹配的操作符

我们使用操作符申请空间时,必须匹配使用,malloc/free、new/delete、new[]/delete[]

分别看一下,不匹配会发生什么

6.1内置类型

int main()
{
	int* ptr1 = new int;
	free(ptr1);

	int* ptr2 = new int[10];
	free(ptr2);

	return 0;
}

我们执行上述代码,发现编译器并不会报错,因为new 也是通过malloc 来申请空间,通过free 释放的,对于内置类型,我们直接使用free 释放是没有问题的。

6.2自定义类型

定义如下类,之后的代码会用下面的类来创建对象申请空间(没有用到的地方会说明):

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

new – free

int main()
{
	A* ptr1 = new A;
	free(ptr1);

	return 0;
}

在这里插入图片描述

我们看到,这样的代码执行起来编译器也是不会报错的,只是没有执行析构函数,这种情况就要分类讨论了

  1. 无需要释放的资源 :就像这个A类,它执行不执行析构函数都是无所谓的,因为它对象内没有在额外申请空间,我们只需要释放掉它new出来的即可,这一点free是可以做到的。

  2. 有需要释放的资源 :我们将上面的代码修改一下,在来运行

    class B
    {
    public:
    	B(int a = 4)
    	{
    		_pa = new int[a];
    		cout << "B(int a = 0)" << endl;
    	}
    	~B()
    	{
            delete[] _pa;
    		cout << "~A()" << endl;
    	}
    private:
    	int* _pa;
    };
    
    int main()
    {
    	B* ptr = new B;
    	free(ptr);
    
    	return 0;
    }
    

    在这里插入图片描述

    观察上面的代码,我们free 只是将ptr 指向的空间释放,而对象ptr_pa 指向的空间却没有释放,这就造成内存泄漏 ,这是很严重的事情,而C/C++中,我们自己申请的内存需要我们自己释放,编译器是不会管的,也不会检查出内存泄漏 的,依然会正常运行。

    在这里插入图片描述

    所以delete 的顺序为先调用析构函数,释放对象内的资源,之后operator delete 释放掉对象的空间。

    关于内存泄漏的危害我举个例子:如果一个程序一直在运行,而它存在内存泄漏,那它会一直消耗空间,直到程序无法运行,这是很严重的问题。

    我们使用下面的代码来检测,一个程序可以开辟的最大空间(我使用的是VS2019)

    int main()
    {
    	int size = 0;
    	while (1)
    	{
    		int* ptr = (int*)malloc(sizeof(int)*2*1024);
    		size += 1;//每次开辟1KB的空间
    		if (ptr == nullptr)
    		{
    			break;
    		}
    	}
    	cout << size / 1024 << "MB" << endl;
    	return 0;
    }
    

    在这里插入图片描述

结论: new和malloc系列,底层实现机制有关联交叉。不匹配使用可能出现问题,也可能没有问题,所以大家一定要匹配使用。

拓展:

如果对java有一定了解,应该知道java是没有释放内存这一说的,因为它有一个垃圾回收的机制叫做GC,当我们new一个对象GC就会记录下来,使用过后需要释放该对象时,JVM(java虚拟机) 就会实现这一机制,释放空间。

至于C++为什么不搞这样的机制,因为C/C++是极度追求性能的,GC 这样的机制一定程度上会对性能造成影响,所以C++需要程序员自己释放,以此提高性能。

从学习语言的角度,有GC也并不就是一件好事,虽然用起来方便,但学习Java的每个人都要懂得GC 这个复杂的机制。

new[]–free/new[]–delete

观察下面两段代码及运行结果:

代码1:

int main()
{
	A* ptr = new A[4];
	free(ptr);

	return 0;
}

在这里插入图片描述

编译器报错无法正常运行。

代码2:

int main()
{
	A* ptr = new A[4];
	delete ptr;

	return 0;
}

在这里插入图片描述

编译器报错无法正常运行。

这两段代码报错的原因和析构函数无关,就像上一段讲的,析构函数哪怕不调用,最坏是内存泄漏,编译还是可以执行的,不会突然报错,报错的原因在于new 开辟连续空间的机制

我们先来看正确的写法:

	A* ptr = new A[4];
	delete[] ptr;

使用new申请连续的空间时,我们在类型后的中括号内填入了数字4告诉编译器,需要申请4个A类型的空间,需要调用4次构造函数,而在delete时,却没有告诉编译器到底调用几次析构函数(释放多少空间编译器会记录,我们使用malloc为内置类型开辟连续的空间时,也没有告诉编译器需要free多大的空间就是证明),那编译器是怎么知道要调用几次析构函数的?

在这里插入图片描述

在new开辟空间时,编译器会在开辟的空间起始地址处向前在开辟4个字节的空间,用来存放需要调用的析构函数个数(只用来存储调用几次析构,释放空间的大小编译器会使用其他办法记录),若释放空间时,写法为delete[],释放空间会包含这4个字节,也就是释放空间时,指针指向的起始位置为这四个字节的首地址, 也就知道了需要调用几个析构函数,若写法为delete/free,则不会去理会起始位置前的这四个字节,指针指向的位置就是申请的空间的起始位置, 只是按照步骤走下去(delete调用一次析构然后释放空间,free直接释放空间),那4个存储数字的字节被保留下来,释放空间的指针位置不对,因此导致编译器报错。

  • 一般程序崩溃都是指针指向的问题,这个就是指向的初始位置不对,像内存泄漏是不会崩溃的。

这里还要说明一点,我们在定义A类时,我们自己写了析构函数,若我们自己没写,则使用new[]/fee、new[]/delete 是不会报错的

class A
{
public:
	A(int a = 0)
	{
		_a = a;
		cout << "A(int a = 0)" << " " << _a << endl;
	}
	//~A()
	//{
	//	cout << "~A()" << endl;
	//}
private:
	int _a;
};
int main()
{
	A* ptr1 = new A[4];
	free(ptr1);
	A* ptr2 = new A[4];
	delete ptr2;

	return 0;
}

在这里插入图片描述

如上,当我们将析构函数注释后,类内就只有编译器自己的默认析构函数,这时编译器会认为没有调用析构函数的必要, 也就不需要在开辟4个字节来存储需要调用析构函数的次数,编译器正常运行。

注意: 再次强调,因为C++的语法和底层比较复杂,哪怕我们知道了怎么开辟和释放空间编译器不会报错,但终究会造成问题,如自己的使用失误,误导他人等等,我们还是坚持遵守C++的语法,配套使用malloc/free、new/delete、new[]/delete[] ,不交叉使用。

7.定位new表达式

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

7.1使用格式:

new(place_address)typenew(place_address)type(initializer_list)

place_address必须时一个指针,initializer_list是类型的初始化列表

7.2使用场景:

如下,我们使用malloc来申请一块自定义类型的空间,申请后,这块空间是没有办法自动调用构造函数的,只能通过定位new 来调用构造函数初始化,释放时则直接调用析构函数即可。

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

int main()
{
	A* ptr = (A*)malloc(sizeof(A));
	new(ptr)A(1);//定位new,传参
	//new(ptr)A;//定位new,不传参
	ptr->~A();//调用析构函数

	return 0;
}

在这里插入图片描述

想必大家都有疑问,为什么要写的这么复杂,直接new一个对象出来不就好了

	A* ptr = new A;
	delete ptr;

凡是存在必然会有它一定的道理,我们一般使用new是向操作系统的堆来申请空间,但操作系统分配空间效率上会有些慢,所以有些地方如果需要频繁的申请内存就会有一个内存池,用来存储需要分配的空间并且效率要高于操作系统,同时内存池分配出的内存没有初始化,如果是自定义类型的对象, 需要使用new的定义表达式进行初始化。

7.3内存池

接着我们来看一下内存池的工作流程:

  1. 当内存池内有内存时,我们直接向其申请,它就会分配给我们空间

    在这里插入图片描述

  2. 当内存池内没有内存时,我们直接向其申请,它会先和操作系统申请,告诉操作系统自己没有内存了,操作系统就会返回其大量内存空间,使其短时间内不会在缺少内存,防止效率下滑。

    在这里插入图片描述

8.面试题

下面是一道面试题,不建议大家去背,做到理解记忆最好

malloc/free和new/delete的区别

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间的大小并传递,new只需要在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
  4. malloc的返回值为void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型。
  5. malloc申请空间失败时,返回的是NULL,因此使用必须判空,new不需要,但是new需要捕获异常。
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

9.总结和博客思维导图

对于内存管理,C/C++几乎类似,只是C++在C的基础上做出了更适合面向对象的调整

  1. 增加了new/delete
  2. 针对自定义类型更好的调用构造和析构函数

在这里插入图片描述

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

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

相关文章

ChatGPT is not all you need,一文看尽SOTA生成式AI模型:6大公司9大类别21个模型全回顾(二)

文章目录ChatGPT is not all you need&#xff0c;一文看尽SOTA生成式AI模型&#xff1a;6大公司9大类别21个模型全回顾&#xff08;二&#xff09;Image-to-Text 模型FlamingoVisualGPTText-to-Video 模型PhenakiSoundifyText-to-Audio 模型AudioLMJukeboxWhisperChatGPT is n…

protoc-gen-go的使用和问题

最近 在网上查看关于proto文件编译为golang代码的文章&#xff0c;发现遇到的问题好多都是文件目录不对&#xff0c;参数不对的情况&#xff0c;这里主要解决&#xff0c;使用 不同版本的proto-gen-go 参数不一样和找不到文件问题 安装protoc-gen-go google.golang.org仓库版本…

互联网新时代要来了(二)什么是AIGC?

什么是AIGC&#xff1f; 最近&#xff0c;又火了一个词“**AIGC”**2022年被称为是AIGC元年。那么我们敬请期待&#xff0c;AIGC为我们迎接人工智能的下一个时代。 TIPS:内容来自百度百科、知乎、腾讯、《AIGC白皮书》等网页 什么是AIGC&#xff1f;1.什么是AIGC&#xff1f;…

Vue3篇.01-简介及基本使用,项目创建方式, 模板语法, 事件监听, 修饰符

一.简介1.概念Vue 是一款用于构建用户界面的 JS框架&#xff0c; 基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c; 高效地开发用户界面。渐进式框架&#xff0c; 适应不同需求进行开发。两个核心功能&#xff1a;声明式…

在云原生的趋势下,不掌握Go语言可能不太行

云原生技术已经是不可逆的趋势 云原生技术使组织能够在公共云、私有云和混合云等现代动态环境中构建和运行可扩展的应用程序,其中容器、服务网格、微服务、不可变基础设施和声明式 API 等都是云原生的重要技术内容。 这些新技术的出现使松散耦合的系统具有弹性、可管理和可观…

FPGA基于VDMA实现任意分辨率视频输出显示,高度贴近真实项目,提供工程源码和技术支持

目录1、前言2、任意分辨率视频输出理论基础3、VDMA实现数据缓存4、工程1&#xff1a;Kintex7使用VDMA5、工程2&#xff1a;Zynq7100使用VDMA6、上板调试验证并演示7、福利&#xff1a;工程代码的获取1、前言 之前写过一篇FPGA纯verilog实现任意分辨率视频输出显示&#xff0c;…

hume项目k8s的改造

hume项目k8s的改造 一、修改构建目录结构 1、在根目录下添加build-work文件夹 目录结构如下 [rootk8s-worker-01 build-work]# tree . . ├── Dockerfile ├── hume │ └── start.sh └── Jenkinsfile2、每个文件内容如下 Dockerfile FROM ccr.ccs.tencentyun…

数据结构与算法基础(王卓)(10):案例分析与实现(多项式;稀疏多项式;图书管理系统(略);)

题干&#xff1a; 用线性表和链表的方式&#xff0c;分别实现&#xff08;稀疏&#xff09;多项式的 定义&#xff08;构造框架&#xff09;创建加减乘&#xff08;多项式&#xff09;线性表的创建&#xff1a; &#xff08;略&#xff0c;相较于其他操作难度不&#xff0c;以…

亚马逊真人测评好还是自养号测评好 深度剖析讲解

关于真人测评和自养号哪个好&#xff0c;一直都是老生常谈的问题了。实际操作下来到底哪一个更好呢&#xff1f;今天陈哥给大家详细分析一下。 先说自养号。所谓的自养号&#xff0c;说白了就是通过搭建国外的真实买家环境&#xff0c;然后购买资料自己注册的账号。 很多工作…

Java300集,学完即可就业

学习java首先我们要明白Java是什么&#xff1f;Java是一门面向对象的编程语言&#xff0c;不仅吸收了C语言的各种优点&#xff0c;还摒弃了C里难以理解的多继承、指针等概念&#xff0c;因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表&a…

K8s 架构简介(一)

一、前言 在开始学习K8s之前&#xff0c;让我们对容器有一个基本的了解 1.1 什么是容器 一个容器镜像是一个可运行的软件包&#xff0c;其中包含了一个完整的可执行程序&#xff0c;包括代码和运行时需要应用、系统库和全部重要设置的默认值。 通过将应用程序本身&#xff…

虹科新闻|虹科与Telco Systems正式建立合作伙伴关系

近日&#xff0c;虹科与美国Telco Systems达成战略合作&#xff0c;虹科正式成为Telco Systems在中国区域的认证授权代理商。未来&#xff0c;虹科将携手Telco Systems&#xff0c;共同为新一代边缘计算和企业网络提供创新通信软件产品。虹科及Telco Systems双方都对彼此的合作…

C语言(逻辑运算符和条件运算符)

目录 一.逻辑运算符 1.原理 2.等级排序 3.求值顺序 二.条件运算符:? 一.逻辑运算符 1.原理 && 与&#xff08;条件都为真&#xff0c;才为真&#xff09; || 或&#xff08;一个条件为真&#xff0c;才为真&#xff09; &#xff01; 非&#xff08;条件为假&…

【R语言(二):Nomogram(诺莫图/列线图)绘制 / R语言逻辑回归分析】

R语言(二)&#xff1a;Nomogram(诺莫图/列线图)绘制 1、基本概念 Nomogram&#xff0c;中文常称为诺莫图或者列线图。简单的说是将Logistic回归或Cox回归的结果进行可视化呈现。它根据所有自变量回归系数的大小来制定评分标准&#xff0c;给每个自变量的每个取值水平一个评分&…

怎样的目标管理能真正实现目标?做到这3点就对了

目标应该是每个人人生中接触最多的一样东西了&#xff0c;大到分分钟几百万上下的项目目标&#xff0c;小到一次考试。目标能不能完成&#xff0c;关键还是看目标管理有没有做好&#xff0c;做到下面这三点&#xff0c;不论是在职场中管团队&#xff0c;还是在生活中管个人&…

十四、平衡二叉树

1、看一个案例&#xff08;说明二叉排序树可能的问题&#xff09; 给你一个数列{1,2,3,4,5,6}&#xff0c;要求创建一棵二叉排序树&#xff08;BST&#xff09;&#xff0c;并分析问题所在。 上面二叉排序树存在问题分析&#xff1a; 左子树全部为空&#xff0c;从形式上看&…

tr命令笔记

tr 是Unix命令行专家工具箱中的一件万能工具。它可用于编写优雅的单行命令。 tr 可以对 来自标准输入的内容进行字符替换、字符删除以及重复字符压缩。 tr 是translate&#xff08;转换&#xff09;的简写&#xff0c; 因为它可以将一组字符转换成另一组字符。 tr 只能…

Windows 11 + WSL(ubuntu 20.04) + CLion(2022.3) 编译OpenJDK12

编译OpenJDK12 目录编译OpenJDK12前言一、下载OpenJDK源码二、编译OpenJDK参考https://openjdk.org/groups/build/doc/building.html1&#xff1a;安装编译所需的组件2&#xff1a;执行编译命令3&#xff1a;验证编译结果三、在Clion中调试OpenJDK源码1&#xff1a;Clion中配置…

R语言GD包地理探测器分析时报错、得不到结果等情况的解决方案

本文介绍在利用R语言的GD包&#xff0c;实现自变量最优离散化方法选取与执行、地理探测器&#xff08;Geodetector&#xff09;操作时&#xff0c;出现各类报错信息、长时间得不到结果等情况的解决方案。 在之前的文章R语言GD包基于栅格图像实现地理探测器与连续参数的自动离散…

深度解读Webpack中的loader原理

一、前言 webpack 是一个现代 JavaScript 应用的静态模块打包器。那么 webpack 是怎样实现不同种类资源模块加载的呢&#xff1f; 没错就是通过 loader。loader 用于对模块的源代码进行转换。loader 可以使你在 import 或加载模块时预处理文件。 我们带着下面几个问题&#…