【c++深入系列】:new和delete运算符详解
🔥 本文专栏:c++
🌸作者主页:努力努力再努力wz
💪 今日博客励志语录:
“生活不会向你许诺什么,尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去,终有一天,你会站在最亮的地方,活成自己曾经渴望的模样。”
★★★ 本文前置知识:
类和对象(上)
类和对象(中)
类和对象(下)
new
1.认识new运算符
那么我们之前在学习c语言的时候,如果我们要在堆上申请空间,那么我们可以调用malloc函数,那么malloc函数会在堆上开辟连续的空间并且返回这个空间的首元素的地址,那么此时在堆上申请的这片空间的生命周期就和程序是一样长,并且它的生命周期可以由我们用户在自己控制,通过调用free函数来释放在堆上申请的空间
那么在学习了c++之后,引入了类和对象,那么我们此时在堆上申请开辟的空间存储的数据类型不仅只有内置类型,还有自定义类型
那么此时就要注意创建堆对象和创建栈对象的区别,那么所谓的堆对象,就是我们调用malloc函数在堆上申请了一片空间,那么这个空间存储的数据类型是自定义类型,而栈对象就是我们在函数内部所定义的局部的自定义类型的变量
//创建堆对象
type* A=(type*)malloc(sizeof(type));
//创建栈对象
type A;
那么对于栈对象来说,那么栈对象的生命周期则是由编译器来控制,那么一旦你定义了一个局部的自定义类型的变量,那么在对象实例化的同时,那么编译器会自动调用该对象的无参或者全缺省的构造函数,而一旦函数调用结束,那么函数栈帧会被销毁,那么此时编译器会清理在函数内定义的局部变量,其中就包括该局部对象,那么编译器会自动的调用其析构函数来清理该局部对象的资源
而对于堆对象来说,那么堆对象的生命周期则是由我们用户自己来决定,那么我们可以在任意一个时刻来调用free函数来释放在堆上开辟的对象,而对于堆对象来说,编译器便不会在自动调用该堆对象的无参或者全缺省的构造函数,那么意味着当我们创建好一个堆对象的时候,那么此时这个对象的内容是随机值,未初始化的,那么为了解决这个问题,我们可以去定义一个完成对象内容的初始化的函数,调用malloc创建完对象之后再调用该初始化的函数来完成初始化
那么这个方式肯定是没问题,但是如果硬要说一个缺点的话,那么就是需要我们程序员去自己定义一个初始化函数,并且还要手动调用它,所以c++在malloc的基础上优化得到了new运算符,虽然new运算符的作用和malloc一样,那么就是在堆上申请空间,但是其中对于在堆上创建对象的时候,那么new与malloc的不同的地方就体现出来了,那么new运算符在申请开辟完空间的时候,会自动调用该对象的构造函数来完成该对象的初始化,也就意味着我们如果采取的原始的malloc的方式来创建一个堆对象的话,那么我们还得分为两步,也就是先malloc创建出一个对象,然后再调用比如init初始化函数,但是对于new来说,那么它则是一步到位,完成空间的开辟以及对象的初始化
我们知道c++是兼容c语言的,你可以在你的c++代码中继续调用malloc在堆上申请空间,但是随着new的诞生,那么malloc就可以逐渐淡出历史舞台了,那么接下来我将会从new如何使用以及其使用的相关细节和new调用的本质等多个维度带你全面剖析new运算符,那么相信你看完之后一定会对new爱不释手的
2.new运算符如何使用
那么知道了什么是new运算符之后,那么接下来我们就得知道在语法层面上如何使用new,那么我们使用new在堆上开辟空间,那么就要指明开辟空间的类型,那么就需要在new运算符后面跟上你要开辟的数据类型,那么这个数据类型可以是内置类型也可以是自定义类型,那么开辟成功之后会返回得到一个你在堆上开辟的连续空间的首元素的地址
那么我们认识最基本的,也就是如何new一个内置类型:
//new一个int类型
int* ptr=new int;
//new一个double类型
double* ptr=new double;
//new一个float类型
float* ptr=new float;
那么new相比于malloc强大的就是我们可以在new的同时对申请的空间的内容进行初始化,而对于malloc来说,这些内容都在malloc开辟之后来完成,那么初始化的方式就是后面跟上一个括号,括号里面的内容就是你要初始化的值:
int* ptr=new int(10);
doublue* ptr=new double(20.5);
而我们知道有时候我们在堆上开辟单个变量的需求,还要堆上开辟一个动态的数组,那么这里我们的new也可以实现,那么语法上实现的方式则是:
//开辟一个int类型的长度为10的动态的数组
int* ptr=new int[10];
//开辟一个double类型的长度为10的动态的数组
double* ptr=new double[10];
并且对于数组来说,new也同样支持创建的同时给值进行初始化,那么只不过这里对于数组的各个元素的初始化的值是在花括号当中:
//int
int* ptr=new int[10]{1,2,3,4,5,6,7,8,9,10};
//double
double* ptr=new double[10]={1.2,2.2,3.2,4.2,5.2,6.2,7.2,8.2,9.2,10.2};
那么对于内置类型的数组的初始化不完全,只给部分长度的初始值,那么剩余未初始化的元素的值便是随机值
那么说完了内置类型,我们再来说自定义类型:
那么自定义类型同样可以分为开辟单个自定义类型以及一个自定义类型的数组,那么首先对于开辟单个自定义类型的话,那么语法声明则是和之前内置类型一样,没有什么差别:
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age=20, int height=168, const char* name="WangZhe"):_age(age), _height(height), _name(name){}
};
int main()
{person* a = new person;cout << a->_name << endl;return 0;
}
但是要注意的是一旦我们的类中没有提供无参数或者全缺省的构造函数的话,那么此时我们在new的同时就得带参数的构造函数传递参数:
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age, int height, const char* name):_age(age), _height(height), _name(name){}
};
int main()
{person* a = new person;cout << a->_name << endl;return 0;
}
正确做法:
person* a=new person(20,166,"WangZhe");
其次我们也可以使用new运算符来创建一个自定义类型的一个数组,那么自定义类型的数组和内置类型数组的定义差别不大,关键是自定义类型的数组中,每一个元素的初始化,那么如果该自定义类型提供了无参数或者全缺省的构造函数,那么我们new一个自定义类型的数组,那么其会自动调用这个数组中每一个对象的无参数或者全缺省的构造函数,那么我们可以写一段简单的代码来验证这个行为:
那么我们这里在类中定义的全缺省的构造函数中定义一个打印语句,那么只要其执行了该全缺省的构造函数,那么便会执行了打印语句,所以如果我们new一个长度为10的自定义类型的数组,那么如果其自动调用该数组每一个元素的全缺省构造函数,那么便会执行10次打印语句:
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age=20 ,int height=166, const char* name="WangZhe"):_age(age), _height(height), _name(name){cout << "person()" << endl;}
};
int main()
{person* a = new person[10];return 0;
}
其次如果说该类没有提供无参数或者全缺省的构造函数的话,那么我们定义一个自定义类型的数组就得显示初始化每一个元素,那么初始化的方式就可以通过匿名对象来实现,那么此时它会先调用构造函数来生成每一个匿名对象,然后再调用拷贝构造函数来初始化数组中的每一个元素,但是现代的编译器都会对此模式进行一个优化,也就是调用一次构造和拷贝构造函数优化成了直接调用构造函数,那么我们还是可以写一个代码来验证一下:
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age,int height, const char* name):_age(age), _height(height), _name(name){cout << "person()" << endl;}person(const person& p1){_age = p1._age;_height = p1._height;_name = p1._name;cout << "const person()" << endl;}
};
int main()
{person* a = new person[2]{person(20,166,"WangZhe"),person(18,170,"luoyi")};return 0;
}
而如果我们类中既有无参数或者全缺省的构造函数又有带参数的构造函数,那么这里我们初始化数组的每一个元素,那么可以不用初始化数组的全员元素,可以显示初始化数组的部分元素,然后剩下的元素就自动调用无参数或者全缺省的构造函数来完成
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age,int height, const char* name):_age(age), _height(height), _name(name){}person():_age(20),_height(180),_name("WangZhe"){cout << "person()" << endl;}
};
int main()
{person* a = new person[4]{person(20,166,"WangZhuo"),person(18,170,"luoyi")};return 0;
}
3.new运算符补充
1.new运算符的本质
那么知道了new运算符如何使用之后,我们知道在堆上new一个对象,那么开辟完空间的同时会对调用该对象的构造函数进行初始化,那么你感觉开辟空间以及调用构造函数这两个动作是new的时候同时完成的,但是实则不是,其实new一个对象的本质还是分为了两步来完成的,也就是先在堆上申请开辟空间然后再调用构造函数
而其中对于一个步骤,也就是先在堆上申请开辟空间则是交给了operator new运算符重载函数来完成,而operator new的作用就是在堆上开辟空间但是没有进行初始化,那么一听这个operator new的作用和malloc那么的相似,其实operator new运算符重载函数内部封装了malloc函数,而对于malloc函数来说,那么一旦我们调用malloc函数失败,那么malloc函数则是返回一个错误码,也就是返回NULL,而对于c++这门面向对象的语言来说,那么它不再是采取返回错误码的方式,而是采取抛异常的方式,那么抛出异常,意味着需要外界来接收,那么一旦收到异常,那么就需要对该异常进行特定逻辑的处理,那么其中就会涉及到try catch语句,那么本文肯定不会详细介绍异常,而异常是我c++系列的博客之后的内容了,所以对于不了解的读者来说,你只需知道operator new运算符重载函数内部封装了malloc,但是由于malloc是返回错误码而不是抛异常,所以operator new函数在内部对此进行了抛出异常的逻辑处理
那么我们也可以写一段简单的代码,来验证一下operator new运算符重载函数,那么注意的是operator new函数底层是封装了malloc函数,那么也就意味着operator new函数内部会调用malloc函数,所以我们调用operator new函数的时候,那么一定要传递要开辟的空间的大小,如果operator new调用成功会返回开辟空间的首元素的地址,但是其类型是void* ,所以还要强制类型转化:
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age,int height, const char* name):_age(age), _height(height), _name(name){}person():_age(20),_height(180),_name("WangZhe"){}
};
int main()
{person* a = (person*)operator new(sizeof(person)); return 0;
}
那么这里可以从调试窗口可以看到a指向的对象的值都是随机值,那么验证了operator new只是开辟空间而不进行初始化
而对于第二个步骤,也就是调用构造函数,那么这个过程就是交给了placement new来完成,那么所谓的placement new是一种特殊的new语法,那么它的作用就是为已经开辟好空间的对象显示的调用其构造函数进行初始化,所以在刚才第一个过程,我们调用了operator new函数得到了开辟好的空间的首元素的地址,那么接下来,就可以调用placement new来调用其构造函数进行初始化
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age,int height, const char* name):_age(age), _height(height), _name(name){}person():_age(20),_height(180),_name("WangZhe"){}
};
int main()
{person* a = (person*)operator new(sizeof(person));a = new(a)person(20, 180, "WangZhuo");cout << a->_name << endl;return 0;
}
placement new的语法:
myclass* a=(myclass*)malloc(sizeof(myclass));myclass* b=new(a)myclass(type);
在你的视角下,你使用new运算符来创建一个对象,你看到的结果是既开辟了对象的空间又对开辟的空间进行了初始化,但是在编译器的视角下,那么它则是将new运算符转换成了两步,第一步则是调用operator new函数,然后第二步再是调用placement new来完成初始化
//你的视角
myclass* ptr= new myclass;
//编译器的视角
myclass ptr=(myclass*)operator new(sizeof(myclass));
ptr=new(ptr)myclass();
2.placement new补充
而对于placement new来说,那么它不仅对堆对象显示调用其构造函数,并且对于栈对象以及静态对象来说,也同样可以显示调用其构造函数,那么之前我那期介绍类与对象的博客提到过,一旦你定义了一个局部对象,那么调用其构造函数的时机就是在对象刚被创建实例化的那一刻,并且由编译器来完成调用,那么一旦实例化之后,便不允许再有二次调用构造函数的行为,并且我们无法通过对象以及指针都再来调用构造函数,那么现在引入placement new之后,其实我们可以创建一个局部的对象,然后通过使用placement new来达到二次调用构造函数的效果
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age=20,int height=180, const char* name="WangZhe"):_age(age), _height(height), _name(name){}
};
int main()
{person s1;new(&s1)person(25,180,"pengyuyan");cout << s1._name << endl;return 0;
}
而对象的初始化规定就是在实例化对象的那一刻完成的,你之后如果再调用构造函数会覆盖之前的对象的内容,那么这样做其实没有任何意义,就好比幼儿园本应该是你小时候5到6岁的年纪去上,那么现在正在上大学的年纪的你已经没必要去再读一次幼儿园了,但是如果你说你就想再回味一下童年,那么其实也没有人会阻拦你去重新上一遍幼儿园,而这里如果说你需要再调用构造函数,那么除非你是先对该对象调用了一次析构函数清理了该对象的资源,那么你可以用placement new来二次调用构造函数
并且对于析构函数来说,那么编译器是允许我们多次调用析构函数,那么可以通过指针或者直接通过对象来调用析构函数然后清理对象的资源,接着在调用placement new对其进行所谓的二次初始化
但是placement new真正的应用场景其实不在这里,而是在内存池中,那么我们知道我们调用malloc以及new都是在堆上申请内存空间,而内存空间是由操作系统来进行管理,那么其中我们直接向操作系统去申请内存空间的话,那么会涉及操作系统会去查找相应的空闲的物理内存然后再建立虚拟内存到物理内存的页表映射等等动作,意味着直接找操作系统申请内存是有一定的成本,所以我们会预先先向操作系统申请一批内存,那么malloc申请的内存不会再去找操作系统而是直接从这预先申请好的内存中获取,那么我们只需要对这些已经获取到的内存的内容进行初始化即可,所以就涉及到placement new,而内存池是一个具体的项目,那么我会在后面的博客讲到,那么如果看不懂我刚才说的内容其实没有关系,那么这部分内容只是作为了解,那么掌握我前面所讲的知识便足矣
3.new和malloc的对比
那么new和malloc的对比是面试是十分喜欢问到的一个知识点,那么除了知道new和malloc最显著的区别,也就是一个会调用构造函数一个只是开辟空间,那么new和malloc的不同其实不仅仅是在这一个方面上:
1.自动计算开辟的空间
那么对于malloc函数来说,那么它会以字节为单位来开辟空间,那么在调用malloc开辟空间的时候,那么我们就得在调用之前计算一下开辟空间的大小,那么虽然有sizeof运算符能够简化计算,但是总之我们还是要给malloc函数传递一个开辟空间的字节数,而对于new来说,那么我们只需要告诉它要开辟的类型的数量,比如是开辟是一个int类型还是10个int类型的数组,那么编译器会自己识别到类型,然后自己会调用sizeof运算符,然后交给operator new函数作为参数,也就是说编译器在转化
2.可以同时进行初始化
那么new可以同时进行初始化,而malloc则只能开辟空间,然后在之后的代码中来对开辟的空间进行初始化,而对于new来说,那么它则是一行代码能够办到两件事,虽然我们知道其实底层它这两件事是分开来完成的,但是在语言层面上,我们还是逻辑上任务new可以同时完成空间的开辟以及对于空间内容的初始化
2.自动完成指针的类型
那么我们调用malloc函数,那么malloc函数是不知道我们要在堆上开辟的空间是给什么样的数据类型来存储的,所以到时候需要我们强制类型转换,而对于new来说,那么编译器识别到我们开辟的对象的数据类型,那么会自动的调用operator new来完成类型转换,所以调用new运算符能够精确的得到返回的指针类型
3.对于自定义类型会调用其构造函数
那么这个区别在上文就已经详细的讲到过了,那么底层是因为调用了placement new来做到的
delete
1.认识delete运算符
那么我们知道malloc会配套有free函数来释放空间,那么new肯定也有对应运算符来释放器开辟的空间,那么这个工作就是delete来完成,那么这里free和delete的作用都是释放在堆上申请的空间,但是它们之间肯定有区别,不然又何必又创建一个delete呢,那么这里的区别其实就体现在释放自定义类型上面,那么delete运算符会先调用其析构函数,然后在完成空间的释放,这就是delete运算符
2.delete运算符如何使用
那么delete运算符的使用就很简答,那么它的语法就是在delete后面跟上你之前保存new开辟的对象的指针
person* a = new person;
delete a;
而对于数组来说,那么delete语法上的使用就要不同一点的就是它后面要添加一个[],代表释放的是一个数组
int* ptr = new int[10] {1,2,3,4};
delete[] ptr;
那么对于delete[]来说,如果你释放的是一个自定义类型的数组,那么它会依次调用这个数组的每一个对象的析构函数,而如果你是用的delete而不是delete[],那么意味着它只会调用一次析构函数,那么就会造成内存泄漏的问题出现
那么我们可以用一段简单的代码来验证,其中我在类中自定义了析构函数,并且在析构函数内部添加了一个打印语句,那么意味着调用一次析构函数,就要打印一次语句:
#include<iostream>
using namespace std;
class person
{
public:int _age;int _height;const char* _name;person(int age=20,int height=180, const char* name="WangZhe"):_age(age), _height(height), _name(name){}~person(){cout << "~person()" << endl;}
};
int main()
{person* ptr = new person[4];delete[] ptr;return 0;
}
#include<iostream>
using namespace std;
class person
{........
}
int main()
{person* ptr=new person[4];delete ptr;return 0;
}
那么这里直接运行崩溃了
delete运算符补充
1.delete运算符的本质
那么对于delete运算符的具体过程,那么根据上文的new,我们也可以大致猜测,这里delete运算符并不是同时调用析构以及释放空间的
对于delete运算符来说,当我们调用delete运算符,那么编译器首先会将其转换为两个步骤,那么第一个步骤就是调用创建出来的对象的析构函数,清理对象的资源,而对于析构函数来说,那么它不像构造函数,那么析构函数可以通过指针或者对象来二次调用,但是注意平常我们自己写代码的时候,我们不要自己去手动通过指针或者对象来调用析构函数,因为对于栈对象来说,那么程序执行了你手动调用的析构函数之后,当函数调用结束之后,那么编译器又会调用一次该对象的析构函数,那么这就会导致析构函数被调用了两次,那么如果类中含有动态的资源比如有一个指针成员变量指向了堆上开辟的空间,那么此时执行了两次析构函数,那么意味着会连续释放两次空间,这是一个未定义行为,而手动二次调用析构函数的场景一般是上文所说的后面要进行placement new的时候,那么可以手动调用析构函数
而这里编译器会先调用析构函数来清理资源,然后下一步便是调用operator delete函数,那么operator delete函数内部封装了free函数,所以operator delete函数底层是调用free函数,那么意味着operator new函数会接收一个指针,将指针指向的空间给释放,那么这就是delete运算符涉及到的底层的过程
//你的视角
person* ptr=new person;
//编译器的视角
ptr->~person();
operator delete(ptr);
2.不要new和free以及malloc和delete混用
那么有的小伙伴喜欢new一个对象,然后调用free函数来释放new出来的对象,或者调用malloc开辟出一个对象,然后调用delete来释放对象,那么这样混用,有些时候不会报错,但是你不可能每次都这么幸运,那么这里我们就来认识一下为什么new和free不能混用
那么这里就要涉及到一点底层了,那么你malloc以及new申请连续的内存空间,那么这些内存空间不是全部都是来存储有效内容的,也就是它会有一部分空间来存储这个内存空间的属性,那么这部分就是申请的内存空间的元数据,那么这个元数据一般是位于malloc以及new的内存空间的头部,而new以及malloc返回的地址其实不是真正意义上的所谓的申请的连续的空间的首元素地址,而是有效内容的首元素的地址,那么这个元素局存储的就是这个内存区域的信息,而对于new以及malloc来说,那么该元数据的内容是不同的
对于new来说,那么它开头的元数据记录的就是你申请的元素的个数,而对于malloc来说,那么它的元数据记录的属性则较多,分别是该申请的内存的总大小包括了元数据以及有效数据,以及一个内存的状态以及空闲列表等等,所以当你new一个对象,然后调用free函数的时候,那么free函数默认这个空间是调用malloc开辟的,那么它会首先解析该空间的元数据,那么它由于接收的是有效数据的首元素的地址,那么它首先就得向前移动一个偏移量到元数据所在位置,然后获取该malloc开辟的空间的总大小,但是由于此时是用new运算符开辟出来的对象,那么它的元数据中只有元素的个数,那么这样就会导致free错误的计算出空间的大小,那么会导致非法访问内存,从而引发段错误,同理delete和malloc混用的话,对于delete它只需知道元素个数即可,因为每一个元素的大小,则是由编译器根据指针指向的对象的类型可以计算得到,那么由于malloc的元数据的构造和new的元数据的构造不同,那么此时delete会错误解析得到释放的元数个数
malloc的元数据内存布局:
±---------------±---------------±---------------+
| 块大小 | 分配状态 | 用户可用内存空间
±---------------±---------------±---------------+
new的元数据内存布局:
±---------------±---------------±----±-----------
| 元素个数 (size_t) |
±---------------±---------------±----±-----------
结语
那么这就是本篇博客关于new和delete介绍的所有内容了,那么下一期博客,我会介绍模版,那么学会了模版之后,我们便可以进入c++的STL的学习了,那么我会持续更新,希望你多多关注与支持,那么如果本文有帮组到你的话,还请三连加关注哦,你的支持,就是我创作的最大的动力!
相关文章:
【c++深入系列】:new和delete运算符详解
🔥 本文专栏:c 🌸作者主页:努力努力再努力wz 💪 今日博客励志语录: “生活不会向你许诺什么,尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去,终有一天&…...
基础(测试用例:介绍,测试用例格式,案例)
目录 测试用例介绍 测试用例编写格式 案例 测试用例介绍 用例:用户使用软件的案例场景 测试用例:是为测试项目而设计的测试执行文档 测试用例的作用: 防止漏测是实施测试的标准可以作为测试工作量的评估 测试用例编写格式 用例编号 用例…...
MCP协议,.Net 使用示例
服务器端示例 基础服务器 以下是一个基础的 MCP 服务器示例,它使用标准输入输出(stdio)作为传输方式,并实现了一个简单的回显工具: using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.H…...
Node.js 数据库 事务 项目示例
1、参考:JavaScript语言的事务管理_js 函数 事务性-CSDN博客 或者百度搜索:Nodejs控制事务, 2、实践 2.1、对于MySQL或MariaDB,你可以使用mysql或mysql2库,并结合Promise或async/await语法来控制事务。 使用 mysql2…...
【AI插件开发】Notepad++ AI插件开发实践:支持多平台多模型
引言 上篇文章我们的Notepad插件介绍到Dock窗口集成,本篇将继续完善插件功能,主要包括两个部分: 支持多平台、多模型支持多种授权验证、接口类型 一、多平台 原先的配置项很简单: // PluginConf.h class PlatformConf { publ…...
微信小程序数字滚动效果
效果图 .wxml <view class"container"><view class"container-num" wx:for"{{number}}" wx:key"index"><view class"num-container" style"--h:{{h}}px;--y:{{-item * h }}px;"><view wx:f…...
wx219基于ssm+vue+uniapp的教师管理系统小程序
开发语言:Java框架:ssmuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:M…...
Python 注释进阶之Google风格
文章目录 1. Google 风格 Docstring 的核心特点2. Google 风格的基本结构3. 编写规则和注意事项4. 最常用的 Google 风格 Docstring 示例示例 1:普通函数 示例 2:带默认参数和可变参数的函数示例 3:类示例 4:生成器函数示例 5&…...
写测试文档时,需要的环境配置怎么查看
操作系统 cat /etc/os-releaseCPU信息 lscpu 内存 sudo dmidecode --type memory | grep -E "Size:|Type:|Speed:"硬盘 列出当前系统中 所有块设备(Block Devices) 的信息,并显示指定列(-o 参数) lsblk…...
强化学习的数学原理(十)actor-critic 方法
由于全文太长,只好分开发了。(已完结!在专栏查看本系列其他文章) 个人博客可以直接看全文~ 本系列为在学习赵世钰老师的“强化学习的数学原理” 课程后所作笔记。 课堂视频链接https://www.bilibili.com/video/BV1sd4y167NS/ 第十章 acto…...
多个定时器同时工作时,会出现哪些常见的bug ,如何解决??(定时任务未实时更新但刷新后正常的问题分析)
1. 定时器冲突与覆盖 问题:后设置的定时器可能覆盖先前的定时器,导致前一个定时器失效 原因:未正确管理定时器ID或未清除前一个定时器 2. 性能问题 内存泄漏:未清除不再需要的定时器会导致内存占用不断增加 CPU过载:…...
代码随想录算法训练营day5(哈希表)
华子目录 有效的字母异位词思路 有效的字母异位词 https://leetcode.cn/problems/valid-anagram/description/ 思路 使用哈希表,这里哈希表使用数组先申请一个26空间的大小的数组遍历第一个字符串,记录每个字符出现的次数1遍历第二个字符串,…...
Python(17)Python字符编码完全指南:从存储原理到乱码终结实战
目录 背景介绍一、字符编码核心原理1. 计算机存储本质2. Python3的编码革命3. 主流编码格式对比 二、编码转换核心方法1. 编码(Encode)过程2. 解码(Decode)过程3. 错误处理策略 三、文件操作编码实战1. 文本文件读写2. 二进制模式…...
Node.js 文件读取与复制相关内容
Node.js 文件读取与复制相关内容的系统总结,包括 同步读取、异步读取、流式读取、复制操作、两者对比及内存测试。 🧩 一、Node.js 文件读取方式总结 Node.js 使用 fs(文件系统)模块进行文件操作: 1. 同步读取&#…...
大数据面试问答-HBase/ClickHouse
1. HBase 1.1 概念 HBase是构建在Hadoop HDFS之上的分布式NoSQL数据库,采用列式存储模型,支持海量数据的实时读写和随机访问。适用于高吞吐、低延迟的场景,如实时日志处理、在线交易等。 RowKey(行键) 定义…...
jupyter 文件浏览器,加强版,超好用,免费exe
第一步:github搜索 lukairui的 jupyter-viewer-plus 仓库 第二步: git clone 到本地。 解压zip包 第三步: 进入压缩包,第一次双击打开jupyter-viewer-plus.exe运行,第一次运行后,界面上有一个“设为…...
【AI工具】用大模型生成脑图初试
刚试用了一下通过大模型生成脑图,非常简单,记录一下 一、用大模型生成脑图文件 关键:存在markdown文件 举例:使用Deepseek,输入问题:“针对大模型的后训练,生成一个开发计划,用ma…...
数据结构-树与二叉树
一、树的定义与基本术语 1.1 树的定义 树(Tree)是一种非线性的数据结构,它是由 n(n ≥ 0)个有限节点组成的集合。如果 n 0,称为空树;如果 n > 0,则: 有一个特定的节…...
STL_unordered_map_01_基本用法
👋 Hi, I’m liubo👀 I’m interested in harmony🌱 I’m currently learning harmony💞️ I’m looking to collaborate on …📫 How to reach me …📇 sssssdsdsdsdsdsdasd🎃 dsdsdsdsdsddfsg…...
ARCGIS国土超级工具集1.5更新说明
ARCGIS国土超级工具集V1.5版本更新说明:因作者近段时间工作比较忙及正在编写ARCGISPro国土超级工具集(截图附后)的原因,故本次更新为小更新(没有增加新功能,只更新了已有的工具)。本次更新主要修…...
主流物理仿真引擎和机器人/强化学习仿真平台对比
以下是当前主流的物理仿真引擎和机器人/强化学习仿真平台的特点和适用场景,方便根据需求选择: 🧠 NVIDIA 系列 ✅ Isaac Lab v1.4 / v2 特点: 基于 Omniverse Isaac Sim,属于高端视觉机器人仿真框架v2 更加模块化&a…...
STM32 HAL库内部 Flash 读写实现
一、STM32F407 内部 Flash 概述 1.1 Flash 存储器的基本概念 Flash 存储器是一种非易失性存储器,它可以在掉电的情况下保持数据。STM32F407 系列微控制器内部集成了一定容量的 Flash 存储器,用于存储程序代码和数据。Flash 存储器具有擦除和编程次数的…...
C++学习:六个月从基础到就业——面向对象编程:构造函数与析构函数
C学习:六个月从基础到就业——面向对象编程:构造函数与析构函数 本文是我C学习之旅系列的第十篇技术文章,主要讨论C中构造函数与析构函数的概念、特点和使用技巧。这些是C对象生命周期管理的关键组成部分。查看完整系列目录了解更多内容。 引…...
dfs二叉树中的深搜(回溯、剪枝)--力扣129、814、230、257
目录 1.1题目链接:129.求根节点到叶结点数字之和 1.2题目描述:给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。 1.3解法(dfs-前序遍历): 2.1题目链接:814.二叉树剪枝 2.2题目描述&…...
Python Selenium 一小时速通教程
Python Selenium 一小时速通教程 实战案例 一、环境配置(10分钟) 安装Python 确保已安装Python 3.x(官网下载)。 安装Selenium 在终端运行: pip install selenium下载浏览器驱动 Chrome:访问 ChromeDriv…...
通过GO后端项目实践理解DDD架构
最近在工作过程中重构的项目要求使用DDD架构,在网上查询资料发现教程五花八门,并且大部分内容都是长篇的概念讲解,晦涩难懂,笔者看了一些github上入门的使用DDD的GO项目,并结合自己开发中的经验,谈谈自己对…...
MybatisPlus最新版分页无法使用
在使用分页的时候发现分页拦截器关键API会报错,其实根本原因是在之前只需要导入一个mybatisplus依赖,而现在分页似乎被单独分离出来了,需要额外导入新依赖使其支持 <dependency><groupId>com.baomidou</groupId><art…...
【Android学习记录】工具使用
文章目录 一. 精准找视图资源ID1. 准备工作2. 使用 uiautomator 工具2.1. 获取设备的窗口内容2.2. Pull XML 文件2.3. 查看 XML 文件 3. 直接使用 ADB 命令4. 使用 Android Studio 的 Layout Inspector总结 二. adb shell dumpsys activity1. 如何使用 ADB 命令2. 输出内容解析…...
youtube视频和telegram视频加载原理差异分析
1. 客户侧缓存与流式播放机制 流式视频应用(如 Netflix、YouTube)通过边下载边播放实现流畅体验,其核心依赖以下技术: 缓存预加载:客户端在后台持续下载视频片段(如 DASH/HLS 协议的…...
在机器视觉检测中为何选择线阵工业相机?
线阵工业相机,顾名思义是成像传感器呈“线”状的。虽然也是二维图像,但极宽,几千个像素的宽度,而高度却只有几个像素的而已。一般在两种情况下使用这种相机: 1. 被测视野为细长的带状,多用于滚筒上检测的问…...
lwip记录
Index of /releases/lwip/ (gnu.org) 以太网(Ethernet)是互联网技术的一种,由于它是在组网技术中占的比例最高,很多人 直接把以太网理解为互联网。 以太网是指遵守 IEEE 802.3 标准组成的局域网,由 IEEE 802.3 标准规定的主要是位于 参考模…...
Redis清空缓存
尽管redis可以设置ttl过期时间进行指定key的定时删除,但是在某些场景下,比如: 测试时需要批量删除指定库下所有库下所有的数据,则会涉及到缓存清除的话题。 如下为具体的操作及说明: 场景类型操作指令清空当前库下所有…...
WPF 依赖注入启动的问题
原因是在App.xaml 设置了 StartupUri“MainWindow.xaml” 1.依赖注入后启动的主窗体存在无参构造 程序正常启动,但是主窗体界面会弹出2个窗体。 2.依赖注入后启动的主窗体存在有参构造 报错...
Arcgis经纬线标注设置(英文、刻度显示)
在arcgis软件中绘制地图边框,添加经纬度度时常常面临经纬度出现中文,如下图所示: 解决方法,设置一下Arcgis的语言 点击高级--确认 这样Arcgis就转为英文版了,此时在来看经纬线刻度的标注,自动变成英文...
【电子通识】案例:电缆的安装方式也会影响设备的可靠性?
背景 在日常生活中,我们常常会忽略一些看似微不足道的细节,但这些细节有时却能决定设备的寿命和安全性。比如,你知道吗?一根电缆的布置方式,可能会决定你的设备是否会因为冷凝水而损坏。 今天,我们就来聊聊…...
房屋装修费用预算表:45594 =未付14509 + 付清31085【时间:20250416】
文章目录 引言I 房屋装修费用预算表II 市场价参考防水搬运3000III 装修计划整体流程进度细节国补IV 付款凭证(销售单)伟星 PPR +PVC+太阳线+地漏=6500入户门设计通铺大板瓷砖 | 湿贴 3408(地)+3600(加)+5209(墙)=12217元门头铁空调引言 关注我,发送【装修记账】获取预…...
Python文件操作完全指南:从基础到高级应用
目录 一、文件基础概念 1.1 什么是文件? 1.2 文件的存储方式 文本文件 二进制文件 二、Python文件操作基础 2.1 文件操作三步曲 2.2 核心函数与方法 2.3 文件读取详解 基本读取示例 文件指针机制 2.4 文件打开模式 写入文件示例 2.5 高效读取大文件 三…...
03(总)-docker篇 Dockerfile镜像制作(jdk,jar)与jar包制作成docker容器方式
全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.2 上文传送门 2. docker镜像制作一: jdk2.1 制作jdk镜像2.1.1 准备工作2.1.2 jdk镜像的Dockerfile2.1.3 基于Dockerfile构建镜像2.1.4 docker使用镜像运行容器2.1.5 进入jdk1.8容器内测试 3. docker镜像制作二: java镜像(jar包)…...
CUDA的安装
打开nvidia控制面板 找到组件 打开 CUDA Toolkit Archive | NVIDIA Developer 下载CUDA...
四六级听力调频广播有线传输无线覆盖系统:弥补单一发射系统安全缺陷,构建稳定可靠听力系统平台
四六级听力调频广播有线传输无线覆盖系统:弥补单一发射系统安全缺陷,构建稳定可靠听力系统平台 北京海特伟业科技有限公司任洪卓发布于2025年4月16日 随着英语四六级考试的规模不断扩大,听力考试部分的设备可靠性问题日益凸显。传统的无线发射系统存在…...
信创服务器-大国崛起,信创当道!
信创产业是数据安全、网络安全的基础,也是新基建的重要组成部分。在政策的推动下,2020-2022 年,中国信创服务器出货量整体呈现出快速增长的趋势,其中党政、电信、金融等领域采购频次高,单次采购量大,是中国…...
【仿Mudou库one thread per loop式并发服务器实现】SERVER服务器模块实现
SERVER服务器模块实现 1. Buffer模块2. Socket模块3. Channel模块4. Poller模块5. EventLoop模块5.1 TimerQueue模块5.2 TimeWheel整合到EventLoop5.1 EventLoop与线程结合5.2 EventLoop线程池 6. Connection模块7. Acceptor模块8. TcpServer模块 1. Buffer模块 Buffer模块&…...
冒泡与 qsort 排序策略集
今天我们要学习两种排序方法,分别是冒泡排序和qsort函数排序,冒泡排序相对qsort函数排序要简单一点,更易于理解。 1.冒泡排序 冒泡排序(Bubble Sort)是一种简单的排序算法,它通过重复遍历元素列并比较相邻元素来实现排…...
【Linux】第七章 控制对文件的访问
目录 1. 什么是文件系统权限?它是如何工作的?如何查看文件的权限? 2. 解释‘-rw-r--r--’这个字符串。 3. 使用什么命令可以更改文件和目录的权限?写出分别使用符号法和数值法将权限从 754 修改为 775 的命令。 4. 如何修改文…...
网站301搬家后谷歌一直不收录新页面怎么办?
当网站因更换域名或架构调整启用301重定向后,许多站长发现谷歌迟迟不收录新页面,甚至流量大幅下滑。 例如,301跳转设置错误可能导致权重传递失效,而新站内容与原站高度重复则可能被谷歌判定为“低价值页面”。 即使技术层面无误&a…...
socket 客户端和服务器通信
服务器 using BarrageGrab; using System; using System.Collections.Concurrent; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading;namespace Lyx {class Server{private TcpListener listener;private Concurre…...
C实现md5功能
md5在线验证: 在线MD5计算_ip33.com 代码如下: #include "md5.h" #include <string.h> #include "stdio.h"/** 32-bit integer manipulation macros (little endian)*/ #ifndef GET_ULONG_LE #define GET_ULONG_LE(n,b,i) …...
【项目】CherrySudio配置MCP服务器
CherrySudio配置MCP服务器 (一)Cherry Studio介绍(二)MCP服务环境搭建(1)环境准备(2)依赖组件安装<1> Bun和UV安装 (3)MCP服务器使用<1> 搜索MCP…...
第五节:React Hooks进阶篇-如何用useMemo/useCallback优化性能
反模式:滥用导致的内存开销React 19编译器自动Memoization原理 React Hooks 性能优化进阶:从手动到自动 Memoization (基于 React 18 及以下版本,结合 React 19 新特性分析) 一、useMemo/useCallback 的正确使用场景…...
【Qt】QWidget 核⼼属性详解
🍑个人主页:Jupiter. 🚀 所属专栏:QT 欢迎大家点赞收藏评论😊 目录 🏝 一.相关概念🎨二. 核⼼属性概览🍄2.1 enabled🥭2.2geometry🌸 2.3 windowTitle&#…...