【028】C++ 类和对象的 构造函数、析构函数、拷贝构造、初始化列表 详解(最全讲解)

news2024/11/26 12:42:50

C++类和对象的构造函数、析构函数、拷贝构造、初始化列表详解

  • 引言
  • 一、构造函数
    • 1.1、数据初始化和清理
    • 1.2、构造函数概述
    • 1.3、构造函数的定义
    • 1.4、提供构造函数的影响
  • 二、析构函数
  • 三、拷贝构造函数
    • 3.1、拷贝构造的定义
    • 3.2、拷贝构造、无参构造、有参构造 三者的关系
    • 3.3、拷贝构造的调用形式
    • 3.4、拷贝构造的深拷贝和浅拷贝
  • 四、初始化列表
    • 4.1、对象成员
    • 4.2、初始化列表的使用
  • 五、explicit关键字——防止构造函数隐式转换
  • 六、类的对象数组
  • 七、动态对象的创建
    • 7.1、c语言的方式创建动态对象
    • 7.2、new创建动态对象
    • 7.3、delete释放动态对象
    • 7.4、动态对象数组
  • 总结

引言


💡 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
👉
🎖️ CSDN实力新星,社区专家博主
👉
🔔 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、高级编程;掌握各个知识点。
👉
🔔 专栏地址:C++从零开始到精通
👉
🔔 博客主页:https://blog.csdn.net/Long_xu


🔔 上一篇:【027】C++类和对象的基本概念

一、构造函数

1.1、数据初始化和清理

当实例化一个对象的时候,这个对象应该有一个初始化状态,当对象销毁之前应该销毁自己创建的数据。对象的初始化和清理是非常重要的安全问题,一个对象或变量没有初始化时,对其使用的后果是未知的;同样的,使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。

因此,C++提供了构造函数和析构函数,这两个函数被编译器自动调用,完成对象初始化和对象清理工作。对象的初始化和清理工作是编译器强制要求去做的,即使没有提供初始化操作和清理操作,编译器也会自动添加默认的操作,只是默认的初始化操作不会做任何事。所以,编写类应该顺便提供初始化函数。

初始化操作是必须的,由编译器自动调用,开发人员只需要提供初始化函数。

1.2、构造函数概述

C++构造函数是一种特殊的成员函数,用于初始化类的对象。它们具有与类名称相同的名称,并且不返回任何值(包括void)。它们可以带有参数或不带参数,根据需要进行重载。

当创建一个类对象时,构造函数会被自动调用。如果没有定义构造函数,则编译器会提供一个默认的构造函数。默认构造函数不执行任何操作,但确保对象被正确初始化。

在构造函数中,可以对数据成员进行初始化、分配内存空间和执行其他必要的操作

类实例化对象的时候,系统自动调用构造函数,完成对象的初始化。
如果用户不提供构造函数,编译器会自动添加一个默认的构造函数(空函数)。

1.3、构造函数的定义

构造函数名和类名相同,没有返回值(连void都不能有),可以有参数(即可以重载);权限为public。

实例化对象时先给对象开辟空间,然后调用构造函数进行初始化。

格式:

类名()
{
	// 初始化操作
}
类名(参数列表)
{
	// 初始化操作
}

示例:

#include <iostream>
#include <string.h>
class Person {
public:
    char name[32];
    int age;
    
    // 构造函数1
    Person() {
        name = "";
        age = 0;
    }
    
    // 构造函数2
    Person(const char *n, int a) {
        strcpy(name,n);
        age = a;
    }
};

int main() {
   // 使用默认构造函数创建对象p1,隐式调用无参构造函数
   Person p1;
   // 显式调用无参构造函数
   Person p2=Person();

   // 使用第二个构造函数创建对象p3,隐式调用有参构造函数
   Person p3("Tom", 25);
   // 显式调用有参构造函数
   Person p4=Person("Lion",18);

	// 匿名对象,当前语句结束立即释放
	Person();
	Person("Long",20);
   return 0;
}

1.4、提供构造函数的影响

  1. 如果用户不提供任何构造函数,编译器默认提供一个空的无参构造。
  2. 如果用户定义了构造函数(不管是有参还是无参),编译器不再提供默认的构造函数。
#include <iostream>
#include <string.h>
class Person {
public:
    char name[32];
    int age;
    // 构造函数
    Person(const char *n, int a) {
        strcpy(name,n);
        age = a;
    }
};

int main() {
   //Person obj;//error,没有无参构造函数
   Person obj("Lion",18);// OK,提供了有参构造
   return 0;
}

二、析构函数

析构函数的定义:函数名和类名称相同,在函数名前面添加~符号,没有返回值,不能重载。

~类名()
{
	// 释放内存操作
}

当对象生命周期结束时,系统自动调用析构函数。先调用析构函数,再释放对象的空间。

示例:

#include <iostream>
using namespace std;

class Data{
public:
	int a;
public:
	Data()
	{
		a=100;
		cout<<"无参构造函数"<<endl;
	}
	Data(int p)
	{
		a=p;
		cout<<"有参构造函数"<<a<<endl;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
	}
};

int main()
{
	Data ob01(200);
	{
		Data ob02(300);
	}
	Data ob03(400);
	return 0;
}

注意:构造和析构的顺序是向反的,先构造的后析构。

一般情况下,空的析构函数就足够;但是,如果一个类中有指针成员,这个类必须写析构函数,释放指针成员所指向的空间。
示例:

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

class Data{
public:
	int a;
	char *name;
public:
	Data()
	{
		a=100;
		cout<<"无参构造函数"<<endl;
	}
	Data(int p,const char *str)
	{
		a=p;
		name=new char[strlen(str)+1];
		strcpy(name,str);
		cout<<"有参构造函数"<<a<<", "<<name<<endl;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
		if(name!=NULL)
			delete[] name;
	}
};

int main()
{
	Data ob01(200,"Lion");
	cout<<ob01.name<<endl;
	return 0;
}

三、拷贝构造函数

拷贝构造本质上是构造函数,它是一种特殊的构造函数。

拷贝构造的调用时机:旧对象初始化新对象才会调用拷贝构造。

如果用户不提供拷贝构造,编译器会自动提供一个默认的拷贝构造,完成赋值操作(浅拷贝)。

3.1、拷贝构造的定义

在C++中,拷贝构造函数是一种特殊的构造函数,它用于创建一个新对象,并将其初始化为已经存在的对象的副本。
当函数有当前类的常引用时就是拷贝构造。

格式:

类名(const 类名 &参数名)
{
	// 相关操作
}

注意:

  • 一旦实现了拷贝构造函数,必须完成赋值操作。
  • 一般情况下,使用系统默认的拷贝构造(浅拷贝)就足够了;只有要完成深拷贝的情况下才需要我们实现拷贝构造函数。

示例:

#include <iostream>
using namespace std;

class Data{
public:
	int a;
public:
	Data()
	{
		a=100;
		cout<<"无参构造函数"<<endl;
	}
	Data(int p)
	{
		a=p;
		cout<<"有参构造函数"<<a<<endl;
	}
	// 拷贝构造
	Data(const Data &ob)
	{
		a=ob.a;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
	}
};

int main()
{
	Data ob01(200);
	Data ob02=ob01;
	return 0;
}

3.2、拷贝构造、无参构造、有参构造 三者的关系

  1. 用户定义了拷贝构造或者无参构造,系统不会自动生成默认的无参构造函数,即会屏蔽无参构造。
  2. 用户定义了有参构造或者无参构造,不会影响系统自动生成默认的拷贝构造。

示例一:实现了拷贝构造,编译器不自动生成默认的构造函数。

#include <iostream>
using namespace std;

class Data{
public:
	int a;
public:
	// 拷贝构造
	Data(const Data &ob)
	{
		a=ob.a;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
	}
};

int main()
{
	Data ob01;// error,找不到构造函数
	
	return 0;
}

示例二:实现了有参构造,编译器不自动生成默认的无参构造函数。

#include <iostream>
using namespace std;

class Data{
public:
	int a;
public:
	Data(int p)
	{
		a=p;
		cout<<"有参构造函数"<<a<<endl;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
	}
};

int main()
{
	Data ob01;//error,找不到无参构造函数
	return 0;
}

示例三:实现了有参构造或无参构造函数,不影响编译器自动生成默认的拷贝构造函数。

#include <iostream>
using namespace std;

class Data{
public:
	int a;
public:
	Data()
	{
		a=100;
		cout<<"无参构造函数"<<endl;
	}
	Data(int p)
	{
		a=p;
		cout<<"有参构造函数"<<a<<endl;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
	}
};

int main()
{
	Data ob01(200);
	Data ob02=ob01;// OK
	return 0;
}

3.3、拷贝构造的调用形式

假设存在 Data 这个类。

class Data{
public:
	int a;
public:
	Data()
	{
		a=100;
		cout<<"无参构造函数"<<endl;
	}
	Data(int p)
	{
		a=p;
		cout<<"有参构造函数"<<a<<endl;
	}
	Data(const Data &ob)
	{
		a=ob.a;
		cout<<"拷贝构造函数"<<endl;
	}
	~Data()
	{
		cout<<"析构函数"<<a<<endl;
	}
};

(1)旧对象给新对象初始化,调用拷贝构造。

Data ob01(200);
Data ob02=ob01;// 调用拷贝构造

(2)给对象取别名,不会调用拷贝构造。

Data ob01(200);
Data &ob02=ob01;// 不会调用拷贝构造

(3)普通对象作为函数参数,调用函数时会发生拷贝构造。

void func(Data ob)
{

}

int main()
{
	Data ob01(100);// 有参构造
	func(ob01);// 拷贝构造
	return 0;
}

(4)函数返回普通对象,可能会发生拷贝构造(依赖于编译器:在windows的visual studio下会发生拷贝构造,在linux下不会发生拷贝构造)。

Data getObj()
{
	Data ob1(100);
	return ob1;
}

int main()
{
	Data ob2=getObj();
}

Windows的过程:先创建一个匿名对象,然后调用拷贝构造给匿名对象赋值,ob1释放,ob2接管匿名对象。
在这里插入图片描述
Linux下的过程:进行了优化,避免了拷贝构造,提高效率。不创建匿名对象,ob2直接接管ob1。
在这里插入图片描述

3.4、拷贝构造的深拷贝和浅拷贝

拷贝构造函数可以执行浅拷贝和深拷贝。

浅拷贝:当使用浅拷贝时,只复制指针或引用而不是整个对象。这意味着新对象与原始对象共享相同的内存位置,因此对其中一个对象所做的更改也会影响另一个对象。如果原始对象被删除,则新对象也会失效。

深拷贝:当使用深拷贝时,会复制整个对象及其所有内容,包括指针所指向的数据。这意味着每个对象都有自己独立的内存位置,因此对其中一个对象所做的更改不会影响另一个对象。如果原始对象被删除,则新对象仍然有效。

默认的拷贝构造都是浅拷贝。如果类中没有指针成员,不用实现拷贝构造和析构函数。

如果类中有指针成员,且指向堆区空间,必须实现拷贝构造函数完成深拷贝。

例如:

#include <string.h>
class Person {
public:
    char* name;
    int age;
	Person(int num,const char *str)
	{
		age=num;
		name = new char[strlen(str)+1];
		strcpy(name,str);
	}
    // 拷贝构造函数
    Person(const Person& p) {
        name = new char[strlen(p.name)+1];
        strcpy(name,p.name);
        age = p.age;
    }
    ~Person()
    {
    	if(name!=NULL)
    		delete [] name;
    }
};

int main() {
    // 创建Person1
    Person person1(18,"Tom");

    // 使用深拷贝创建Person2
    Person person2 = person1;

    // 打印person1和person2的信息
    cout << "Person1: " << *(person1.name) << ", " << person1.age << endl;
    cout << "Person2: " << *(person2.name) << ", " << person2.age << endl;

    delete person1.name;
    delete person2.name;
    return 0;
}

输出结果:

Person1: Tom, 18
Person2: Tom, 18

使用深拷贝创建的新对象与原始对象是独立的,修改原始对象不会影响新对象。而且可以避免重复释放。

四、初始化列表

4.1、对象成员

在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象,叫做对象成员。

实例化类时,先调用对象成员的构造函数,再调用本身的构造函数。析构函数和构造函数的调用顺序相反;先构造,后析构。

在这里插入图片描述

注意:类会自动调用对象成员的无参构造。

示例:

#include <iostream>
using namespace std;

class A{
public:
	int mA;
public:
	A()
	{
		cout<<"A 无参构造"<<endl;
	}
	A(int num)
	{
		mA=num;
		cout<<"A 有参构造"<<endl;
	}
	~A()
	{
		cout<<"A 析构函数"<<endl;
	}
};

class B{
public:
	int mB;
	A mA;
public:
	B()
	{
		cout<<"B 无参构造"<<endl;
	}
	B(int num)
	{
		mB=num;
		cout<<"B 有参构造"<<endl;
	}
	~B()
	{
		cout<<"B 析构函数"<<endl;
	}
};

int main()
{
	{
		B ob01;
	}
	cout<<"-------------------"<<endl;
	{
		B ob02(100);
	}
	return 0;
}

输出:

A 无参构造
B 无参构造
B 析构函数
A 析构函数
-------------------
A 无参构造
B 有参构造
B 析构函数
A 析构函数

4.2、初始化列表的使用

类想调用对象成员的有参构造,必须使用初始化列表。

格式:

B类的有参构造函数(参数列表):A类的对象名(参数列表)
{
	// 有参构造函数体
}

示例:

#include <iostream>
using namespace std;

class A{
public:
	int mA;
public:
	A()
	{
		cout<<"A 无参构造"<<endl;
	}
	A(int num)
	{
		mA=num;
		cout<<"A 有参构造"<<endl;
	}
	~A()
	{
		cout<<"A 析构函数"<<endl;
	}
};

class B{
public:
	int mB;
	A mA;
public:
	B()
	{
		cout<<"B 无参构造"<<endl;
	}
	B(int num)
	{
		mA.mA=num;
		mB=num;
		cout<<"B 有参构造"<<endl;
	}
	// 初始化列表
	B(int a,int b):mA(a)
	{
		mB=b;
		cout<<"B 有参构造: "<<mB<<endl;
	}
	~B()
	{
		cout<<"B 析构函数"<<endl;
	}
};

int main()
{
	{
		B ob01;
	}
	cout<<"-------------------"<<endl;
	{
		B ob02(100);
	}
	cout<<"-------------------"<<endl;
	{
		B ob03(200,300);
	}
	return 0;
}

输出:

A 无参构造
B 无参构造
B 析构函数
A 析构函数
-------------------
A 无参构造
B 有参构造
B 析构函数
A 析构函数
-------------------
A 有参构造
B 有参构造:300
B 析构函数
A 析构函数

五、explicit关键字——防止构造函数隐式转换

C++提供了关键字explicit 禁止通过构造函数进行的隐式转换;声明为explicit的构造函数不能在隐式转换中使用。

explicit用于修饰构造函数构造函数,防止隐式转换;是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参数构造)而言的。

示例:

#include <iostream>
using namespace std;

class MyString{
public:
	explicit MyString(int n)
	{
		cout<< "MyString(int): " << n <<endl;
	}
	MyString(char *str)
	{
		cout<< "MyString(char *): " << str <<endl;
	}
};

int main()
{
	// 语义不明,给字符串赋值还是初始化?
	// MyString str=100;// 本质是调用MyString(int n)初始化
	
	MyString str(100);

	// 寓意明确,字符串赋值
	MyString str01="abc";
	MyString str02("abc");
	return 0;
}

六、类的对象数组

对象数组本质是数组,数组的每个元素是对象。

  • 对象数组的每个元素都会自动调用构造函数和析构函数。
  • 对象数组不初始化,每个元素调用无参构造。
  • 对象数组的初始化必须显式使用有参构造,逐个元素初始化。

示例:

#include <iostream>
using namespace std;

class Data {
public:
	int mA;
public:
	Data()
	{
		cout << "Data 无参构造" << endl;
	}
	Data(int a)
	{
		mA = a;
		cout << "Data 有参构造:" << mA << endl;
	}
	~Data()
	{
		cout << "Data 析构函数" << endl;
	}
};

int main()
{
	// 对象数组的每个元素都会自动调用构造函数和析构函数。
	{
		Data data[5];

		// 对象数组的初始化必须显式使用有参构造,逐个元素初始化。
		Data data2[5] = { Data(100),Data(200), Data(300), Data(400), Data(500) };

		for (int i = 0; i < 5; i++)
		{
			cout << data2[i].mA << " ";
		}
		cout << endl;
	}
	return 0;
}

输出:

Data 无参构造
Data 无参构造
Data 无参构造
Data 无参构造
Data 无参构造
Data 有参构造:100
Data 有参构造:200
Data 有参构造:300
Data 有参构造:400
Data 有参构造:500
100 200 300 400 500
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数
Data 析构函数

七、动态对象的创建

当创建数组的时候,需要提前知道数组的长度,然后编译器分配预定长度的数组空间;在使用数组时会有这样的问题,数组也许空间太大了,造成空间浪费,数组也许空间不足,不足以存储所有数据;如果能根据需要分配空间是最好的,因此动态就意味着不确定性。

c语言的malloc和free函数可以在运行时从堆中分配存储单元,然而这些函数在C++中不能很好的允许,因为它们不能帮我们完成对象的初始化和释放工作。

7.1、c语言的方式创建动态对象

当创建一个C++对象时会发生两个动作:

  1. 为对象分配内存。
  2. 调用构造函数来初始化对象内存。

第一步能保证实现,第二步是C++强迫这么做的,因为使用未初始化的对象可能会造成程序错误。C语言动态分配内存方法是为了在运行时动态分配内存,C语言的标准库提供了malloc、calloc、realloc函数动态分配内存,提供free函数释放内存;这些函数是有效的,但是是原始的,需要程序员小心使用。

C语言使用动态内存分配函数创建一个类的示例:

class Person{
public:
	int mAge;
	char *mName;
public:
	Person()
	{
		mAge=20;
		mName=(char*)malloc(32);
		strcpy(mName,"Lion");
	}
	void init()
	{
		mAge=20;
		mName=(char*)malloc(32);
		strcpy(mName,"Lion");
	}
	void clean()
	{
		if(mName!=NULL)
			free(mName);
	}
	~Person()
	{
		if(mName!=NULL)
			free(mName);
	}
};

int main()
{
	Person* person=(Person*)malloc(sizeof(Person));
	if(person==NULL)
		return -1;
	// 需要调用初始化函数
	person->init();
	
	//......
	
	// 需要调用清理函数
	persion->clean();
	
	// 释放对象
	free(person);
	
	return 0;
}

C语言风格创建对象的问题:

  1. 必须确定对象的长度。
  2. malloc返回void指针,C++不允许将void赋值给其他任何指针,必须强转。
  3. malloc可能申请内存失败,所有必须判断返回值来确定内存分配成功。
  4. 用户使用对象之前必须记住对他初始化,构造函数不能显式调用初始化(构造函数由编译器自动调用),用户可能忘记调用初始化函数。

C语言的动态内存分配函数太复杂,容易令人混淆,C++中推荐使用new和delete。

7.2、new创建动态对象

C++中解决动态内存分配的方案是把创建一个对象所需的操作都结合在一个new的运算符中;当用new创建一个对象时,它就在堆中为对象分配内存并调用构造函数完成初始化。
比如:

Person person=new Person;

new操作符能确定在调用构造函数初始化之前内存分配是成功的,所以不用显式确定调用是否成功。现在在堆中创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆中创建一个对象和在栈中创建一个对象一样简单。

7.3、delete释放动态对象

new表达式的反面是delete表达式。delete先调用析构函数,再释放对象内存。

#include <iostream>
using namespace std;

class Person{
public:
	int mAge;
	char *mName;
public:
	Person()
	{
		mAge=20;
		mName=new char[32];
		strcpy(mName,"Lion");
		cout<<"无参构造"<<endl;
	}
	Person(int num,const char *name)
	{
		cout<<"有参构造"<<endl;
		mAge=num;
		mName=new char[32];
		strcpy(mName,name);
	}
	void show()
	{
		cout<<mName<<" : "<<mAge<<endl;
	}
	~Person()
	{
		cout<<"析构函数"<<endl;
		if(mName!=NULL)
		{
			delete [] mName;
			mName=NULL;
		}
	}
};

int main()
{
	Person *person=new Person;//无参构造
	Person *person2=new Person(20,"Long");//有参构造
	person->show();
	person2->show();

	delete person;
	delete person2;

}

7.4、动态对象数组

当创建一个对象数组时,必须为每个数组元素调用构造函数(默认是调用无参构造),除了在栈上可以聚合初始化,必须提供一个默认的构造函数。

#include <iostream>
using namespace std;

class Person{
public:
	int mAge;
	char *mName;
public:
	Person()
	{
		mAge=20;
		mName=new char[32];
		strcpy(mName,"Lion");
		cout<<"无参构造"<<endl;
	}
	Person(int num,const char *name)
	{
		cout<<"有参构造"<<endl;
		mAge=num;
		mName=new char[32];
		strcpy(mName,name);
	}
	void show()
	{
		cout<<mName<<" : "<<mAge<<endl;
	}
	~Person()
	{
		cout<<"析构函数"<<endl;
		if(mName!=NULL)
		{
			delete [] mName;
			mName=NULL;
		}
	}
};

int main()
{
	Person person[]={Person(100,"Lion"),Person(200,"Tom"),Person(300,"Long")};
	cout<<person[1].mName<<endl;
	
	Person *person2=new Person[20];//无参构造
	delete person2;

}

总结

  1. 构造函数:构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的成员变量。C++中,每个类都至少有一个构造函数,默认情况下,如果没有显式定义构造函数,编译器会自动生成一个默认构造函数。

  2. 析构函数:析构函数与构造函数相对应,用于在销毁对象时释放对象占用的资源。它们通常被用来删除动态分配的内存、关闭文件和释放锁等操作。与构造函数不同的是,析构函数没有参数。

  3. 拷贝构造:拷贝构造是一种特殊的构造函数,它通过复制已有对象创建新的对象。当使用赋值运算符或将一个对象作为实参传递给另一个对象时会调用拷贝构造。默认情况下,编译器会生成一个浅拷贝的拷贝构造函数。如果需要实现深拷贝,则需要手动编写拷贝构造函数。

  4. 初始化列表:初始化列表用于在创建对象时初始化其成员变量。它可以更高效地初始化非静态常量数据成员和引用类型数据成员,并且可以避免某些未定义行为和难以调试的错误。初始化列表由冒号(:)开始,并由逗号(,)分隔多个成员变量及其初始值组成。

构造函数、析构函数和拷贝构造都是类的成员函数,用于初始化对象、释放资源和复制对象。初始化列表可以更高效地初始化数据成员,并避免某些错误。在实际编程中,需要根据具体情况选择适当的方式来实现。

在这里插入图片描述

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

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

相关文章

【群智能算法改进】一种改进的浣熊优化算法 改进长鼻浣熊优化算法 改进后的ICOA[1]算法【Matlab代码#41】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 原始COA算法1.1 开发阶段1.2 探索阶段 2. 改进后的ICOA算法2.1 Circle映射种群初始化2.2 Levy飞行策略2.3 透镜成像折射反向学习策略 3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1…

05-DataFrame的数据清洗

数据清洗 import pandas as pd df pd.read_excel("学生成绩.xlsx") df删除重复列 df.drop_duplicates(inplaceTrue) df删除数学成绩 df.drop([数学成绩],axis1, inplaceTrue) df重命名列名字 df.rename(columns{"生活":"自然"},inplaceTrue…

什么是Redis的BigKey,如何处理BigKey?

一、什么是BigKey BigKey通常以Key的大小和Key中成员的数量来综合判定&#xff0c;例如&#xff1a; Key本身的数据量过大&#xff1a;一个String类型的Key&#xff0c;它的值为5 MB。Key中的成员数过多&#xff1a;一个ZSET类型的Key&#xff0c;它的成员数量为10,000个。Ke…

Linux之多线程(下)——线程控制

文章目录 前言一、POSIX线程库1.概念2.pthread线程库是应用层的原生线程库3.错误的检查 二、线程控制1.创建线程——pthread_createpthread_create函数例子创建一个新线程主线程创建一批新线程 2.获取线程ID——pthread_self3.线程等待——pthread_join4.线程终止——return、p…

SSH服务器详解

文章目录 文字接口连接服务器&#xff1a;SSH服务器连接加密技术简介启动SSH服务SSH客户端连接程序SSH&#xff1a;直接登录远程主机的指令使用案例 服务器公钥记录文件&#xff1a;~/.ssh/known_hosts报错解决 模拟FTP的文件传输方式&#xff1a;SFTP使用案例 文件异地直接复制…

Python主要应用的10大领域你是否感兴趣

原文&#xff1a; Python主要应用的10大领域你是否感兴趣 Python 是一门快速发展的编程语言&#xff0c;其在各个领域的应用也在不断增加。根据 TIOBE 编程语言排行榜&#xff0c;Python 在 2021 年排名第 3&#xff0c;仅次于 Java 和 C。根据 Stack Overflow 的开发者调查报…

圆的基本性质

如何确定一个圆&#xff1f; 两个点&#xff1a; 无法确定一个圆&#xff0c;因为只要到这两个点距离相等的点都可以作为圆心&#xff08;在两个点连线的垂直平分线上&#xff09;&#xff0c;因此可以确定无数个圆 三个点&#xff08;且这三个点不能在同一个直线上&#xf…

【MySQL数据库】事务

事务 一、事务1.1事务的概念 二 、事务的ACID特点2.1原子性2.2一致性&#xff08;Consistency&#xff09;2.3隔离性2.4持久性 三、脏读、不可重复读、幻读、丢失更新3.1脏读3.2不可重复读3.3幻读3.4丢失更新 四、事务的隔离级别 一、事务 1.1事务的概念 事务是一种机制、一个…

二叉堆(大顶堆、小顶堆)学习(使用java手写)

二叉堆 我们现在有一个需求&#xff0c;用来存放整数&#xff0c;要求需要提供三个接口 添加元素获取最大值删除最大值 我们可以用我们熟悉的数据结构去解决这些问题 获取最大值删除最大值添加元素描述动态数组/双向链表O(n)O(n)O(1)O(n) 复杂度太高了&#xff08;有序&#x…

redis -- 持久化存储方案

前言 一般情况下&#xff0c;我们存储到redis的数据&#xff0c;是存储到内存中&#xff0c;再存储到硬盘中(这是基于reb方案来实现)因此一旦强制关机,就直接over了。 硬存和内存的区别和联系&#xff1a; 我们用文本编辑器&#xff0c;里面写入一段话&#xff0c;未保存&am…

检测PPG信号的心跳

基于大佬的代码。 PPG信号靠心率 (HR) 进行估计&#xff0c;主要取决于收缩压峰值检测的准确性。与 ECG 不同&#xff0c;PPG 信号形式简单和特定点 少。低振幅 PPG 信号更容易受到噪声污染和其他不良影响的影响&#xff0c;例如baseline drift和wandering。这是由于信号强度与…

从零开始理解Linux中断架构(3)--Armv8体系架构

首先让我们带着问题进入到armv8架构的学习中。linux中断代码分为两部分entry.S @arch\arm64\kernel\entry.S汇编部分和C代码后续处理。汇编代码中处理最为低级的部分,设置硬件中断向量表,保持当前上下文,切换中断堆栈等任务,这是就如我们嵌入式系统看到那样。 @arch\arm64…

Vue3中div自由拖拽宽度和高度。

Vue3中我们会遇到自由拖拽宽度和高度的页面需求&#xff0c;查看很多方法都无法满足当前需求。下面是我们Vue3版本的代码&#xff0c;非常简单主要构想说粗发拖拽方法&#xff0c;把所需要的div的高宽进行拖拽位置进行监听来加减自身div的px值。直接复制粘贴就可以实现效果。根…

20230615整理(字符设备驱动的内部实现)

1.1 字符设备&#xff1a; 以字节流的形式进行访问&#xff0c;而且只能顺序访问的设备叫做字符设备(比如键盘、鼠标) (块设备&#xff1a;有固定访问大小&#xff0c;可以不按顺序访问的设备&#xff0c;比如U盘、硬盘) 针对字符设备编写的驱动叫做字符设备驱动 1.2 当设备驱…

AI实战营:通用视觉框架OpenMMLab底层视觉与MMEditing

目录 图像超分辨率 Super Resolution ​​​ 深度学习时代的超分辨率算法 卷积网络模型SRCNN FSRCNN SRResNet Super-Resolution CNN, SRCNN, 2014 Fast SRCNN 2016 SRResNet 2016 对抗生成网络介绍Ganerative Adversarial Network 基于GAN的模型SRGAN与ESRGAN S…

vite+vue3+ts 报错和解决办法汇总

1. import path from path 时 ts 报错&#xff1a;模块 ""path"" 只能在使用 "allowSyntheticDefaultImports" 标志时进行默认导入。 在 tsconfig.node.json 文件的 compilerOptions 添加配置 "allowSyntheticDefaultImports": true …

【Python】Django 基础知识 一

系列文章目录 提示&#xff1a;阅读本章之前&#xff0c;请先阅读目录 文章目录 系列文章目录前言安装启动项目查看所有子命令主要文件setting 配置项URL 请求路径path 转换器HttpResponse 输出中文乱码 前言 安装 django-admin startproject xxx项目名启动项目 python manag…

ffmpeg 3.4 windows编译安装

准备工作: msys2安装 官网 MSYS2 下载完成后一直下一步即可&#xff0c;安装完成后windows搜索 MSYS2 启动MSYS2 MINGW64 打开窗口后运行以下命令 下载一些编译需要的东西 #修改源 sed -i "s#mirror.msys2.org/#mirrors.ustc.edu.cn/msys2/#g" /etc/pacman.d/mirr…

拔剑四顾心茫然,绿源直呼“行路难”

老牌两轮电动车品牌绿源上市之旅“多歧路”。 6月7日&#xff0c;北京市市场监督管理局公布北京市电动自行车产品质量监督抽查结果&#xff0c;绿源两款电动自行车因存在问题被点名&#xff0c;充电器和蓄电池、整车质量、控制系统等不符合标准。 而就在一周多以前&#xff0c…

指针(四)

文章内容&#xff1a; 1. 数组参数、指针参数 2. 函数指针 3. 函数指针数组 4. 指向函数指针数组的指针 5. 回调函数 文章内容 1. 数组参数、指针参数 地址需要指针接收&#xff0c;指针的地址需要二级指针接受,以此类推...... 1.1 一维数组传参 #include <stdio.…