C C++ 的内存管理(C++)

news2024/11/24 0:43:55

目录

C / C++ 的内存分布

C / C++ 程序内存区域划分:​

C语言内存管理

C中动态内存管理方式:

C++内存管理

C++内存管理的方式:

new / delete 操作内置类型

new 和 delete 操作自定义类型

new 和 delete 与 malloc 和 free 的区别:

operator new 与 operator delete 函数

operator new 和 operator delete 函数

new和delete的实现原理

内置类型

自定义类型

定位new表达式(placement-new)

常见面试题

malloc/free 和 new/delete的区别?

内存泄露

什么是内存泄露,内存泄露的危害?

内存泄露分类

堆内存泄露

系统资源泄露

如何检测内存泄露

如何避免内存泄露


C / C++ 的内存分布

C / C++ 程序内存区域划分:


说明:

1、栈,又被叫做堆栈,里面存放 函数栈帧,非静态局部变量/函数参数/返回值……等。栈是向下增长的(所谓向下增长,就是从高地址往低地址处存数据)

2、内存映射区是高效的IO映射方式,用于装载一个动态的内存库,用户可以使用系统接口,创建共享内存,做进程间的通信!

3、堆,用于程序运行时动态内存分配,堆是向上增长的!

4、数据段,存储全局数据和静态数据

5、代码段,可执行的代码 或 只读变量


C语言内存管理

C中动态内存管理方式:

C中用动态开辟空间函数来管理动态内存(详解可看主页C语言动态内存管理文章)

函数分别是:malloc 、calloc、realloc、free 

下面看看它的应用实例(用C实现一个栈)


代码:


//C语言实现栈
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>

typedef int DateType;

typedef struct Stack
{
	DateType* _a;
	int size;
	int capacity;
}ST;

//初始化栈
void InitStack(ST* ps)
{
	//开空间
	ps->_a = (DateType*)malloc(sizeof(DateType) * 4);
	if (ps->_a == NULL)
	{
		perror("malloc tail:");
		return;
	}
	ps->size = 0;
	ps->capacity = 4;
}

//扩容
void ChilckCapacity(ST* ps)
{
	if (ps->size == ps->capacity)
	{
		//进行增容
		DateType* tmp = (DateType*)realloc(ps->_a, sizeof(DateType) * ps->capacity * 2);
		if (NULL == tmp)
		{
			perror("realloc tail ");
			return;
		}
		ps->_a = tmp;
		ps->capacity *= 2;
	}
}

//入栈
void PushStack(ST* ps,DateType x)
{
	//扩容
	ChilckCapacity(ps);

	//添加数据
	ps->_a[ps->size++] = x;
}

//判断栈是否为空
bool EmptyStatic(ST* ps)
{
	return ps->size == 0;
}

//出栈
void PopStack(ST* ps)
{
	//判断栈是否出空了
	if (EmptyStatic(ps))
	{
		printf("栈为空了,不可再出了\n");
		return;
	}

	--ps->size;
}

//返回栈顶元素
DateType TopStack(ST* ps)
{
	//栈为空就提示
	assert(!EmptyStatic(ps));
	return ps->_a[ps->size - 1];
}

//栈元素个数
int SizeStack(ST* ps)
{
	return ps->size;
}

//栈的销毁
void DestoryStack(ST* ps)
{
	free(ps->_a);
	ps->_a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

int main()
{
	ST st;

	//初始化栈
	InitStack(&st);

	//入栈
	PushStack(&st, 1);
	PushStack(&st, 2);
	PushStack(&st, 3);
	PushStack(&st, 4);

	//出栈
	PopStack(&st);

	//返回栈顶元素
	printf("%d\n", TopStack(&st));

	//入栈
	PushStack(&st, 5);

	//返回栈顶元素
	printf("%d\n", TopStack(&st));

	//栈里面元素的个数
	printf("Stack Size = %d\n", SizeStack(&st));

	//入栈
	PopStack(&st);
	PopStack(&st);
	PopStack(&st);
	PopStack(&st);
	PopStack(&st);
	//printf("%d\n", TopStack(&st));

	//销毁栈
	DestoryStack(&st);

	return 0;
}

上述代码是C语言对动态内存的管理方式(利用malloc,calloc,realloc 函数管理)


C++内存管理

C++内存管理的方式:

因为C++兼容C语言,C语言中的内存管理的方式在C++中是可以继续使用的。但在有些场景下C语言管理内存的方式是无能为力的,而且使用起来非常麻烦,所以C++提出了自己的一套内存管理的方式!

C++通过 new 和 delete 操作符进行动态内存管理!


new / delete 操作内置类型

new 和 delete:

在C++中,new 是用来开辟空间的,delete是用来释放空间的!


写法:

申请单个类型的空间:

1、申请空间不初始化

        指针变量 = new 类型;

2、申请空间并初始化

        指针变量 = new 类型(空间的初始值);


申请多个空间

1、申请空间不初始化

        指针变量 = new 类型[个数];

2、申请空间并初始化

        指针变量 = new 类型[个数]{初始化内容};


释放单个类型空间:

        delete 指针变量;

释放多个空间:

        delete[] 指针变量;


代码:

#include <iostream>
using namespace std;

int main()
{
	//动态申请一个int类型的空间
	int* p = new int;
	cout << *p << endl;//打印,不初始化是随机值
	delete p; //释放该空间


	//动态申请一个int类型的空间并初始化
	int* p1 = new int(3);
	cout << *p1 << endl;//打印
	delete p1;//释放该空概念


	//动态申请10个int类型的空间
	int* p2 = new int[10];
	//利用for进行打印
	for (int i = 0; i < 10; i++)
	{
		cout << p2[i] << " ";//不初始化是随机值
	}
	cout << endl;
	delete[] p2; //释放该空间


	//动态申请10个int类型的空间并初始化
	int* p3 = new int[10]{ 1,2,3,4,5,6,7,8,9,10 };
	//利用for进行打印
	for (int i = 0; i < 10; i++)
	{
		cout << p3[i] << " ";//不初始化是随机值
	}
	cout << endl;
	delete[] p3;//释放该空间

	return 0;
}

注意:

在C++中申请和释放单个元素的空间,直接用new 和 delete 。申请和释放连续的多个空间,使用 new[] 和 delete[] 。一定要相互匹配使用!


理解代码:

#include <iostream>
using namespace std;

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

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

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

	//进行释放
	delete ptr4;
	delete ptr5;
	delete[] ptr6;

	return 0;
}


注意:

C++兼容C语言,C++中虽然可以继续使用C语言管理内存的方式。但C++有自己的一套管理内存的方式,切记两者不能混合使用,否则会出现不可预测的结果! 


new 和 delete 操作自定义类型

new 和 delete 与 malloc 和 free 的区别:

区别:

对于自定义类型而言,new 和 delete   在申请和释放空间的时候会去调用自定义类型的构造函数和析构函数(new 调用构造函数,delete调用析构函数)。而 malloc 和 free 则不会。


对于内置类型而言,两者是没有任何区别的,只是动态申请空间!


代码:

#include <iostream>
using namespace std;
 
class A
{
public:
	//构造函数 
 A(int a = 0)
 : _a(a)
 {
 cout << "A():" << this << endl;
//注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
 }
 
 //析构函数 
 ~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;
}

operator new 与 operator delete 函数

operator new 和 operator delete 函数

operator new 和 operator delete :

new和delete 是用户用来动态内存申请和释放的操作符(注意new 和 delete是操作符)

operator new 和 operator delete 是两个全局函数,并不是重载!


底层关系:

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


operator new  的实现:

/*
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)
比特就业课
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
5. new和delete的实现原理
5.1 内置类型
     if (_callnewh(size) == 0)
     {
         // report no memory
         // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
         static const std::bad_alloc nomem;
         _RAISE(nomem);
     }
return (p);
}

operator delete 的实现:

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

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

总结:

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


new和delete的实现原理

内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,

不同的地方是: new/delete申请和释放的是单个元素的空间,new[]和delete[]申请和释放的是连续空间,而且new在申 请空间失败时会抛异常,malloc会返回NULL。


代码:

#include <iostream>
using namespace std;

int main()
{
	//对于内置类型,new和malloc delete 和free 是类似的

	int* ps = (int*)malloc(sizeof(int));
	free(ps);

	int* p = new int;
	delete p;

	char* pps = (char*)malloc(sizeof(char) * 10);
	free(pps);

	char* pp = new char[10];
	delete[] pp;

	//new/delete申请和释放的是单个元素的空间,
	//new[]和delete[]申请和释放的是连续空间

	return 0;
}

自定义类型

new的原理:

        1、调用operator new函数申请空间

        2、在申请空间之后调用构造函数,完成对象的构造


delete的原理:

        1、在空间上执行析构函数,完成对象的资源清理

        2、调用operator delete 进行释放对象空间


new T[N] 的原理:

        1、调用operator new[]  函数,在operator new[] 中实际调用operator new 函数完成N个对象空间的申请

        2、在申请的N个对象空间上,对每个对象都执行一次构造函数,总共执行N次构造函数


delete[] 的原理:

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

        2、调用operator delete[]  来释放空间,实际在operator delete[] 中调用operator delete 来进行释放空间!



代码:

#include <iostream>
using namespace std;

class Date
{
public:
	//构造函数
	Date(int year=0,int month=0,int day=0)
		:_year(year),
		_month(month),
		_day(day)
	{
		cout << "Date(int year=0,int month=0,int day=0)" << endl;
	}

	//析构
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//自定义类型开空间
	// 对于new来说先调用opertor new 函数开空间
	// 再调用构造函数
	// operator new 本质上还是用malloc 来开空间
	// 对于delete 先调用 析构函数
	//再调用operator delete 函数进行释放空间
	//operator delete 实际上是调用free 来进行释放空间!
	Date* p = new Date;
	delete p;

	return 0;
}

定位new表达式(placement-new)

概念:

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


使用格式:

new(place_address)type 或者 new (place_address) type(initializer-list)

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


使用场景:

定位new表达式在实际中一般是配合内存池进行使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的表达式进行显示调构造函数进行初始化!


说明:

    实际使用情况下定位new表达式是与内存池配合使用的
    内存池也属于堆区,是在堆区提前拿到一块空间,供用户使用,
    加强了程序的效率,速度快,用户后续开空间就直接可以在内存池中使用空间
    内存池中的空间对于自定义类型是不会自动调用析构和构造函数的
    此时我们就可以用到定位new表达式进行显式的调用构造和析构!
    p1现在指向的只不过是与A对象相同大小的一段空间,
    还不能算是一个对象,因为构造函数没有执行


代码:

#include <iostream>
#include <string.h>
using namespace std;

class A
{
public:
	//默认构造
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}

	//析构
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;

};

// 定位new/replacement new
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;  // 通过定位new表达式显式的调用构造函数!
	//显示的调用析构函数
	p1->~A();
	free(p1);

	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);//利用定位new表达式显式的调用构造函数,并且给构造函数传参

	//显式的调用析构函数
	p2->~A();
	operator delete(p2);

//实际使用情况下定位new表达式是与内存池配合使用的
// 内存池也属于堆区,是在堆区提前拿到一块空间,供用户使用,
// 加强了程序的效率,速度快,用户后续开空间就直接可以在内存池中使用空间
// 内存池中的空间对于自定义类型是不会自动调用析构和构造函数的
// 此时我们就可以用到定位new表达式进行显式的调用构造和析构!
// p1现在指向的只不过是与A对象相同大小的一段空间,
//还不能算是一个对象,因为构造函数没有执行

	return 0;
}

常见面试题

malloc/free 和 new/delete的区别?

解答:

malloc/free 和 new/delete 的共同点:

都是从堆上申请空间,并且需要用户手动释放!


malloc/free 和 new/delete 的不同点:

1、malloc / free 是函数,new / delete 是操作符

2、malloc / free 申请的空间不会初始化,而new / delete 可以初始化

3、malloc 申请空间时,需要手动计算空间大小并传递,new只需要在后面跟上空间的类型即可,如果是多个对象 [] 中指定对象个数即可!

4、malloc的返回值为void*,在使用时必须强转,new不需要,因为new后面跟的是空间类型!

5、malloc 申请空间失败,返回的是NULL,因此使用时必须判空,new不需要判断,但是要捕获异常!

6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成 空间中资源的清理


内存泄露

什么是内存泄露,内存泄露的危害?

什么是内存泄露:

内存泄露指的是,因为疏忽或错误造成程序未能释放已经不再使用的内存情况,内存泄露并不是指内存物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费!(内存泄露就是空间申请之后不释放,且后续不能再进行释放的情况!)


内存泄露的危害:

长期运行的应用程序出现内存泄露,影响很大,如操作系统、后台服务器等等。出现内存泄露会导致响应越来越慢,最终卡死!(若程序不长期运行,程序结束操作系统会自动进行释放内存,即处理内存泄露问题)


代码:

#include <iostream>
using namespace std;

void MemoryLeaks()
{
	// 1.内存申请了忘记释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;

	// 2.异常安全问题
	int* p3 = new int[10];

	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,
	//p3没被释放.导致出现内存堆区泄露的问题

	delete[] p3;
}

int main()
{
	return 0;
}

内存泄露分类

堆内存泄露

堆内存泄露指的是程序执行中需要通过malloc / realloc / calloc / new 等从堆中分配的一块内存,用完后必须通过相对应的free或delete释放掉,假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法被使用,会出现heap leak(大量泄露)


系统资源泄露

指程序使用系统分配的资源,比方套接字,文件描述符、管道……等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定!


如何检测内存泄露

在VS下使用Windows操作系统提供的_CrtDumpMemoryLeaks()函数进行简单检测,该函数只报出了大概泄露了多少个字节,没有其它更准确的位置信息


代码:


注意:

写代码一定要小心,尤其是动态内存操作时一定要记着释放,但有些情况下还是防不胜防,简单的泄露可以采用上述方式快速定位判断。如果工程比较大,内存泄露位置比较多, 不太好查时,一般都是借助第三方内存泄露检查工具来处理的!


内存泄露检测工具:

在Linux下的内存泄露检测Linux下几款内存泄露的工具

在Windows下使用第三方工具:VLD内存泄露库

其它工具:内存泄露检测工具


如何避免内存泄露

养好良好的编程习惯,有申请就要有对应的释放,用智能指针预防内存泄露,或者用检测泄露的工具进行查询!


总结:

1、事前预防型,如智能指针等!

2、事后查错型,如泄露工具!

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

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

相关文章

基于Springboot的漫画之家系统设计实现

&#x1f49e;文末获取源码联系&#x1f649; &#x1f447;&#x1f3fb; 精选专栏推荐收藏订阅&#x1f447;&#x1f3fb; &#x1f380;Java项目精选实战案例《600套》&#x1f618; https://blog.csdn.net/rucoding/category_12319634.html 文章目录 1、演示视频2、课题背…

QSS盒子模型入门指南:了解和应用基础知识

目录 1. QSS盒子模型的组成部分2. QSS盒子模型的属性3. QSS盒子模型的布局4. QSS盒子模型的调试工具结论 #概述 QSS&#xff08;Qt Style Sheets&#xff09;是一种用于美化和定制化Qt应用程序的样式表语言。了解和掌握QSS盒子模型的基本概念对于创建漂亮的用户界面布局至关重要…

javascript基础二十七:说说 JavaScript 数字精度丢失的问题,解决方案?

一、场景复现 一个经典的面试题 0.1 0.2 0.3 // false 为什么是false呢? 先看下面这个比喻 比如一个数 130.33333333… 这是一个除不尽的运算&#xff0c;3会一直无限循环&#xff0c;数学可以表示&#xff0c;但是计算机要存储&#xff0c;方便下次再使用&#xff0c;但…

IMX6ULL裸机篇之I2C实验-硬件原理图

一. I2C 实验简介 I2C实验&#xff0c;我们就来学习如何使用 I.MX6U 的 I2C 接口来驱动 AP3216C&#xff0c;读取 AP3216C 的传感器数据。 AP3216C是一个三合一的环境光传感器&#xff0c;ALSPSIRLED&#xff0c;ALS是环境光&#xff0c;PS是接近传感器&#xff0c;IR是红外L…

2023 华为 Datacom-HCIE 真题题库 12/12(完结)--含解析

单项选择题 1.[试题编号&#xff1a;190728] &#xff08;单选题&#xff09;以下哪种工具不能用来匹配BGP路由条目&#xff1f; A、基本ACL B、高级ACL C、IP PREFIX LIST D、Community Filter 答案&#xff1a;B 解析&#xff1a;高级ACL是一种用于过滤IPv4报文的ACL&#…

多层级table联动

elementui 多层级table联动&#xff1a; 引用&#xff1a; https://blog.csdn.net/weixin_44780971/article/details/130054925 https://blog.csdn.net/qq_42581563/article/details/114325920 需要了解的属性&#xff1a; select-all 全选的时候执行select &#xff1a; 选择…

MySQL 连接查询

文章目录 一&#xff0c;等值连接二&#xff0c;表别名三&#xff0c;多表等值连接四&#xff0c;自然连接五&#xff0c;自连接六&#xff0c;非等值内连接七&#xff0c;外连接&#xff08;一&#xff09;左外连接&#xff08;二&#xff09;右外连接&#xff08;三&#xff…

Cookie与Session的工作流程

文章目录 Cookiecookie的工作流程1.cookie从哪里来2.cookie到哪里去3.cookie是做什么的 SessionSession工作流程 Cookie与Session都是http协议中的机制,都是用来追踪浏览器用户身份的会话方式.但是又有各自的工作流程. Cookie cookie是浏览器在本地存储数据的一种机制。 cookie…

java从入门到起飞——基础概念

目录 背景注释和关键字注释关键字 常量变量数据类型计算存储单元数据类型分类 标识符小驼峰命名法&#xff08;方法、变量&#xff09;大驼峰命名法&#xff08;类&#xff09; 类型转换自动类型转换强制类型转换 计算机中的数据存储总结 背景 学编程这么长时间了&#xff0c;重…

Java Swing花样玩法:教你用代码制作六一儿童节的精美贺卡(简单版)

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;Java经典程序设计 前言&#xff1a;这篇博客在打开可能会自动播放视频&#xff0c;视频有音乐&#xff0c;请及时静音哈&#x1f642; 目录 ✨前言 ✨引言 ✨简单介绍一下Javaswing这项技术简单介绍一下Javaswing这项技术&a…

电子模块|压力传感器模块HX711---硬件介绍与C51STM32驱动

电子模块|压力传感器模块HX711---硬件介绍与C51&&STM32驱动 实物照片模块简介模块特点 硬件模拟输入供电电源时钟选择串口通讯复位和断电HX711相关部分的 PCB 设计 软件驱动C51软件代码STM32软件代码 实物照片 模块简介 HX711是一款专为高精度称重传感器而设计的24位A…

全志V3S嵌入式驱动开发(音频输出和音频录制)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 之前在芯片公司的时候&#xff0c;基本没有看过音频这一块&#xff0c;只知道有个alsa框架这么个知识点。要驱动音频&#xff0c;需要两部分&#…

10-风险管理:如何应对暗礁风险?系统化风险管理让你安心!

项目已到中期&#xff0c;目前看很顺利&#xff0c;但隐隐不安&#xff1a;项目进展越平稳&#xff0c;我越觉不安。我担心项目会不会存在什么风险&#xff0c;而自己却没发现。 这种担心很必要&#xff0c;因为项目从构思起&#xff0c;就存在风险。光担心没用&#xff0c;项…

如何用LoadRunner 做性能测试?一篇文章教会你

目录 一、loadrunner介绍 二、测试计划 三、创建测试脚本 四、创建测试场景 五、分析结果 六、性能指标 一、loadrunner介绍 loadrunner有三个软件&#xff0c;其中Virtual User Generator是用于录制测试脚本的&#xff0c;是一个虚拟用户生成器。Controller用于创建、运…

CSS常用属性

目录 1.CSS是什么&#xff1f; 2.基本语法 3.引入方式 1.内部样式表 2.行内样式表 3.外部样式 4.基础选择器 1.标签选择器 2.类选择器 3.id选择器 4.通配符选择器 基础选择器总结 5.复合选择器 1.后代选择器 2.子选择器 3.并集选择器 4.伪类选择器 1) 链接伪类…

行业风向:国产新能源汽车如何“扬帆起航”闯世界?

历经十余年的积累和发展&#xff0c;受益于国家财政政策的大力支持、行业技术水平的大幅提升、车企研发与营销费用的大力投入等多重因素&#xff0c;我国新能源汽车走向了高速发展阶段&#xff0c;并一举成为全球最大的新能源汽车市场&#xff0c;在续航里程、环境适应性、整车…

Hooks

私人博客 许小墨のBlog —— 菜鸡博客直通车 系列文章完整版&#xff0c;配图更多&#xff0c;CSDN博文图片需要手动上传&#xff0c;因此文章配图较少&#xff0c;看不懂的可以去菜鸡博客参考一下配图&#xff01; 系列文章目录 前端系列文章——传送门 后端系列文章——传送…

以太网交换机的生成树协议STP

以太网交换机的生成树协议STP 笔记来源&#xff1a; 湖科大教书匠&#xff1a;以太网交换机的生成树协议STP 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 如下图所示以太网中出现链路故障导致部分主机间无法进行通信 如何提高以太网的可靠性&am…

Elasticsearch:复制 - replication

在本篇文章中&#xff0c;我们来讲述 Elasrticsearch 集群中重要的一个概念 replication&#xff0c;也即复制。 了解 Elasticsearch 中的分片复制 默认情况下&#xff0c;索引由单个分片组成&#xff0c;但是如果存储分片的节点出现故障&#xff08;例如磁盘故障&#xff09;…

【Python基础】- 基础数据类型(下)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…