【c++】继承深度解剖

news2024/11/15 21:34:41

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:了解什么事继承,基类和派生类的使用和转换,熟练掌握继承和友元

> 毒鸡汤:青春就像一只容器,装满了不安躁动青涩与偶尔的疯狂。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言  

你肯定听过某男子继承百亿家产吧,继承的东西就等于自己的,在c++中也继承,所继承的儿子,都有父类的属性,这个属性可能是父类的成员函数,成员变量...。我们直接再创建一个类就可以了定义属性,那我们为什么需要继承类的属性呢?今天由问题的引出,我们来探讨c++继承的方便。

⭐主体

学习c++的继承我们按照下面的图解:

🌙继承的介绍

💫继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。

举个栗子:

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "fl"; // 姓名
	int _age = 21;
};

class Student :public Person
{
protected:
	int _stuid;//学号
};

int main()
{
	Student s;
	s.Print();
}

问题分析:

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出Student复用了Person的成员。下面我们使用监视窗口查看Student对象,可以看到变量的复用。调用Print可以看到成员函数的复用。

💫继承的定义


我们把Person类叫做基类(或者父类),把Student叫做派生类(或者子类)

继承关系和访问限定符:


访问限定符中:

  • public成员可以被外部以及继承访问.
  • protected成员,不可以被外部访问但可以被继承访问.
  • private成员,外部和继承都不可以被访问.

继承基类成员访问方式的变化:

类成员/继承方式public继承protected继承 private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见
  • 看起来很复杂,这里有个规律就是:子类成员中的父类成员的访问类型 = 父类访问类型和继承方式中访问权限最小的一个:访问权限从小到大为private < protected < public.
  • 但是有个特殊:就是当父类中成员访问类型是private时,无论子类以什么方式继承,最后在子类中的成员都是不可见的.
  • 记得和private的区分:private的是类内可以访问,类外不可以访问
  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的
  3. . 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强

🌙基类和派生类对象赋值转化

派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。基类对象不能赋值给派生类对象。​​​

举个栗子:

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "小明";
	int _age = 18;
};

class Student :public Person
{
protected:
	int _stuid;
};

int main()
{
	Person p;  //创建Person对象
	Student s; //创建Student对象

	p = s;     //将派生类赋值给基类
	Person* ptr1 = &s; //将派生类的地址赋给基类指针
	Person& pp = s;    //将派生类引用赋给基类引用
}

问题分析:

  • 基类对象不能赋值给派生类对象
  • 子类赋值个父类会发生切片

🌙继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,如果想使用同名成员中的基类,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员。

举个栗子①:

class Person
{
protected:
	string _name = "小郝"; // 姓名
	int _num = 111; // 身份证号
};
//Student类与Person类中都有_num,但是子类(Student类)会把父类中的_num隐藏,此时再访问子类中的_num,便是子类的_num=999
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 学号:" << _num << endl;
		cout << " 身份证号:" << Person::_num << endl;
	}
protected:
	int _num = 999; // 学号
};
void Test()
{
	Student s1;
	s1.Print();
};
int main()
{
	Test();

	return 0;
}

问题分析①:

Student类与Person类中都有_num,但是子类(Student类)会把父类中的_num隐藏,此时再访问子类中的_num,便是子类的_num=999

举个栗子②:

class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i)
	{
		A::fun();
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
	B b;
	//由于B类中fun()与父类A类中fun()重名,所以调用的是B类的fun()函数.
	//想调用A类中的fun(),必须加上作用域
	b.fun(10);
};
int main()
{
	Test();

	return 0;
}

问题分析②:

由于B类中fun()与父类A类中fun()重名,所以调用的是B类的fun()函数,想调用A类中的fun(),必须加上作用域

🌙派生类的默认成员函数

我们知道普通的类有6个默认成员函数,但是如果我们继承了某个类之后,这个子类的6个默认成员函数会做什么呢?“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员又分别有什么作用呢?

💫派生类的构造函数

在派生类的构造函数中,一共有两个行为:顺序是:先调用父类构造函数,再调用子类的构造函数

  1. 自己成员,跟平常的构造函数一样
  2. 继承的父类成员,必须调用父类的构造函数初始化

如果子类没有显式调用父类构造函数,那么系统会自动调用父类的默认构造函数,(默认构造函数有两种:1.没有写,系统自动生成 2. 写了,但是没有参数 3.写了,但是参数是全缺省的)

举个栗子:

class Person
{
public:
	//父类的默认构造函数
	Person(const string& name = "hyx")
		:_name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name;
};
class Student :public Person
{
public:
	Student()
		:_num(1)
	{
		cout << "Student()" << endl;
	}
private:
	int _num;

};
int main()
{
	Student s;
	return 0;
}

问题分析:

这个例子子类没有显式调用父类的构造函数,所以系统会先自动调用的父类的默认构造函数,然后再调用子类的构造函数.

问题拓展:

父类和子类的成员变量也都初始化了,但是如果父类没有默认构造函数,而子类也没有显式调用,那该怎么初始化呢?直接报错,错误原因是:父类没有合适的默认构造。

那我们该怎么显式调用它呢?正确的办法是用一种类似于匿名对象的方法,在子类初始化列表进行初始化,如下:

class Student :public Person
{
public:
	Student()
		:_num(1)
		, Person("hyx")//注意不能是_name = name,可以这样理解:子类中会把父类当做一个整体来调用,而不是单独的类成员.
	{
		cout << "Student()" << endl;
	}
private:
	int _num;

};

派生类的构造函数 必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。

💫派生类的拷贝构造

在派生类的拷贝构造函数中,也是需要完成两个行为:

  1. 对于子类自己的成员,直接调用自己的拷贝构造(内置类型拷贝,自定义类型调用它的拷贝构造)
  2. 对于继承的父类成员,将调用父类的拷贝构造函数初始化.(如果没有显式调用,则系统会自动调用父类的拷贝构造函数)

注意这里是显式调用,一般情况下我们不需要显式调用,默认生成的就够用,除非在需要深拷贝的情况下,才需要显式调用父类所写的拷贝构造函数,至于为什么可以用子类对象构造父类对象,这不是不可以的吗?其实这里是切片,将子类中父类的部分切除下来并且拷贝给子类.

💫派生类中的operator=赋值运算符重载

和上面的几乎完全一致.在派生类中使用“=”运算符,也会有两个行为完成.

  1. 子类调用自身的“=”运算符重载
  2. 自动调用父类的"="的运算符重载

一般对内置类型不用处理,对于自定义类型需要当显式调用的时候,一定要加上上父类的类域,因为子类的“=”运算符重载会将父类的“=”运算符隐藏,进而无限递归.

举个栗子:

//Person类:父类“=”运算符重载
Person& operator=(const Person& p)
{
	cout << "Person& operator=(const Person& p)" << endl;
	if (this != &p)
		_name = p._name;

	return *this;
}
//Student类:子类“=”运算符重载
Student& operator= (const Student& s)
{
	if (this != &s)
	{
		//注意不要直接写operator=(s),因为子类的会隐藏父类的拷贝构造,进而造成无限递归,栈溢出
		Person::operator=(s);
		_num = s._num;
	}
}

💫派生类中的析构函数

调用顺序:

  1. 先调用派生类的析构函数
  2. 再调用基类的析构函数

  • 如果不写,则会默认生成析构函数,对于内置类型不做处理,自定义类型调用对应的析构函数.
  • 如果子类和父类都有析构函数,子类析构的函数跟父类的析构函数构成隐藏!既然构成隐藏,直接加上作用域就可以了.
     

问题探讨:为什么会出现隐藏?

这是由于子类继承父类,因为是父类先构造初始化的,子类最后再构造的.所以符合后定义的先析构,先定义的后析构,子类后定义,所以子类先被析构.因为这些变量存储在栈里面,在局部栈帧中,先进后出,后进先出.所以为了保证这个顺序,如果自己显式调用析构,则这个顺序就无法保证,所以系统会自动调用.自己不需要显式调用父类的析构函数,每个子类的析构函数后面会自动调用父类的析构函数.所以显式调用的话会有重复,重复的话,普通的变量没什么,但是如果是一个指针,连续被释放两次,一定会出错的.

总结归纳:

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。

🌙继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。也就相当于你爸爸的朋友不是你的朋友这个道理。

举个栗子:

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s) {
	cout << p._name << endl;
	cout << s._stuNum << endl;//这里会报错
}
int main()
{
	Person p;
	Student s;
	Display(p, s);
}

运行结果:

结果分析:

可以看到虽然子类继承了父类,而父类的友元函数Display只能访问父类的成员,而子类的却不可以被访问 (但是子类依然可以调用父类的成员)

🌙继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例

举个栗子:

class Person
{
public:
	Person() { count++; }
	static int count;
};

int Person::count = 0;

class Student :public Person
{};

int main()
{
	Person p;
	Student stu;
	cout << "Person:" << p.count << endl;//打印2
	cout << "Student:" << stu.count << endl;//打印2
}

问题分析:

Student继承了Person,count也被继承了,但是因为count是静态成员变量,所以只会有一个count,因此Person创建对象p,count++,Student创建对象sty,count++,所以结果都为2

🌙复杂的菱形虚拟继承及菱形虚拟继承

💫单继承与多继承


  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承.
  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承.
  • 菱形继承:菱形继承是多继承的一种特殊情况.

 💫菱形继承的二义性

举个栗子:

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	// 这样会有二义性无法明确知道访问的是哪一个
	Assistant a;
	a._name = "peter";

	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

问题分析:

Student和Teacher继承了Person,各有一份_name,Assistant 又继承了Student和Person,就有两份_name,如果在Assistant中访问_name,就会发生访问出现二义性的问题。这就相当于一位老师在教室说:请你起来回答问题。这个“你”到底是班上的哪一位同学,我们不得而知,必须指明姓名才行。对于菱形继承的二义性,我们只需要指定作用域即可。

💫菱形继承所造成的数据冗余

举个栗子:

class test1
{
public:
	int a[10000];
};
class test2 :public test1
{};
class test3 :public test1
{};
class test4 :public test2, public test3
{};

int main()
{

	test4 t;
	cout << sizeof(test4) << endl;
	return 0;
}

问题分析:

本来test4只想继承一份a[10000],但是由于菱形继承,结构导致了test4继承了两份,如果数据量小,所占空间小,还可以忽略不计,但是这里40000字节(4*10000),极大了造成了数据冗余和空间上的浪费 。

💫虚拟继承解决菱形继承所造成的数据冗余和二义性

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份

举个栗子:

class Person
{
public:
	string _name; // 姓名
	int arr[10000];
};
class Student : virtual public Person
{
protected:
	int _num; //学号
};
class Teacher : virtual public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};

int main()
{
	Assistant A;
	A._name = "fffl";
	cout << A._name << endl;
	cout << sizeof(A) << endl;
	return 0;
}

问题分析:

为了解决数据冗余和二义性,C++采用虚拟继承来解决它们。使用关键字virtual就可变为虚拟继承,只需要在菱形继承的中间层,也就是Student和Teacher,在它们继承方式前面加上virtual即可。

💫虚拟继承解决数据冗余和二义性的原理

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型

举个栗子:

class A {
public:
	int _a;
};

class B : public A {
public:
	int _b;
};

class C : public A {
public:
	int _c;
};

//class D : public C, public B
class D : public B, public C {
public:
	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

问题分析:

通过调用内存窗口发现:在D创建的对象d中,确实有两份_a,因为B和C都继承了A。此外还有就是在子类创建的对象中,先在内存的低地址为其先继承的类开辟空间,再在高地址为后继承的类开辟空间,最后再为自己开辟空间。先继承的在前面,后继承的在后面

🌙继承的总结和反思

  1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。否则在复杂度及性能上都有问题
  2. 多继承可以认为是C++的缺陷之一,很多后来的语言都没有多继承,如Java
  3. 继承和组合

3.1public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象

3.2组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象

3.3优先使用对象组合,而不是类继承

3.4继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高

3.5对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装

3.6实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合

🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

启动CMD/powershell命令窗口时,设置默认的python虚拟环境 in window10

启动CMD/powershell命令窗口时&#xff0c;设置默认的python虚拟环境 in window10 本文有两个目的&#xff1a; CMD命令窗口&#xff0c; 设置默认启动的python环境PowerShell命令窗口&#xff0c; 设置默认启动的python环境 CMD命令窗口&#xff0c; 设置默认启动的python环…

企业计算机服务器中了360勒索病毒如何解密,360后缀勒索病毒处理流程

对于众多的企业来说&#xff0c;企业的数据是企业发展的核心&#xff0c;越来越多的企业开始注重企业的数据安全问题&#xff0c;但随着网络技术的不断发展与应用&#xff0c;网络黑客的攻击加密手段也在不断升级。近期&#xff0c;云天数据恢复中心接到多家企业的求助&#xf…

PDN分析及应用系列二-简单5V电源分配-Altium Designer仿真分析-AD

PDN分析及应用系列二 —— 案例1:简单5V电源分配 预模拟DC网络识别 当最初为PCB设计打开PDN分析仪时,它将尝试根据公共电源网络命名法从设计中识别所有直流电源网络。 正确的DC网络识别对于获得最准确的模拟结果非常重要。 在示例项目中已经识别出主DC网络以简化该过程。 …

人工智能之Tensorflow程序结构

TensorFlow作为分布式机器学习平台&#xff0c;主要架构如下&#xff1a; 网络层&#xff1a;远程过程调用(gRPC)和远程直接数据存取(RDMA)作为网络层&#xff0c;主要负责传递神经网络算法参数。 设备层&#xff1a;CPU、GPU等设备&#xff0c;主要负责神经网络算法中具体的运…

【旧文搬运】为你的 Laravel 应用添加一个基于 Swoole 的 WebSocket 服务

做了一个基于 Swoole 的 WebSocket 扩展包&#xff0c;可以用来做实时状态推送&#xff0c;或者自定义消息处理实现 im&#xff0c;有需要的可以看看: [giorgio-socket] 使用方法 安装 安装扩展包 composer require wu/giorgio-socket发布配置文件 php artisan vendor:pu…

可莉炸鱼

情况有s*k>n&#xff0c;最多炸鱼数为n s*k<n&#xff0c;最多炸鱼数为s*k 将s*k转化为k个s相加&#xff0c;每次结果与n比较 #include<iostream> #include<vector> #include<algorithm> using namespace std; #define endl \n #define ll long lon…

Java---文件,流✨❤️

文章目录 1.遍历文件夹2.遍历子文件夹3.练习流4.以字节流的形式读取文件内容5.以字节流的形式向文件写入数据顶折纠问6 .写入数据到文件 1.遍历文件夹 一般说来操作系统都会安装在C盘&#xff0c;所以会有一个 C:\WINDOWS目录。 遍历这个目录下所有的文件(不用遍历子目录) 找出…

pyqt5怎么返回错误信息给页面(警告窗口)

在软件设计中&#xff0c;我们可能会遇到对异常的处理&#xff0c;有些异常是用户需要看到的&#xff0c;比如说&#xff0c;当我们登录出错的时候&#xff0c;后端需要给我们返回响应的错误信息&#xff0c;就像下图实现的这样。 类似这种效果&#xff0c;我们该如何实现&…

选择何种操作系统作为网站服务器

选择操作系统时&#xff0c;需考虑稳定性、安全性、成本、兼容性和技术支持等因素&#xff0c;常见选项有Windows Server和Linux发行版。 选择网站服务器的操作系统是一个关键的决策&#xff0c;因为它将影响到网站的性能、稳定性、安全性以及未来的扩展性&#xff0c;目前市场…

ES向量功能实战:向量搜索

1 缘起 项目需要&#xff0c;向量搜索使用ES&#xff0c;为了顺利使项目顺利交付&#xff0c;开始学习ES的稠密向量功能&#xff0c;本文即ES向量的实践&#xff1a;增删查改。ES从7.x版本支持向量功能&#xff0c;为测试ES向量功能&#xff0c;需要使用7.x及以上的版本。本文…

算法刷题day20:二分

目录 引言概念一、借教室二、分巧克力三、管道四、技能升级五、冶炼金属六、数的范围七、最佳牛围栏 引言 这几天一直在做二分的题&#xff0c;都是上了难度的题目&#xff0c;本来以为自己的二分水平已经非常熟悉了&#xff0c;没想到还是糊涂了一两天才重新想清楚&#xff0…

vue3 vite项目一运行就401(Unauthorized)

问题&#xff1a;项目一执行&#xff1a; pnpm run dev, 启动就出错&#xff0c; Failed to load resource: the server responded with a status of 401 (Unauthorized) 分析&#xff1a; 项目之前是正常运行的&#xff0c;没有问题&#xff0c;回溯刚刚改动&#xff0c;还原…

W5300驱动说明

W5300是一款带有硬件协议栈的网络芯片&#xff0c;内部拥有128K的缓存&#xff0c;最大支持8路socket通信&#xff0c;与MCU之间通过16位数据总线通信&#xff0c;通信速度远超W5500之类以SPI作为通信接口的网络芯片&#xff0c;特别适合对高速网络传输有需求的应用。 本次使用…

基于springboot+vue的疫苗发布和接种预约系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

关于工业 24V 电源转换电路参考设计

一. 概述 在工业邻域的应用中&#xff0c;部分电路板输入电源为 24V&#xff0c;而电路板上 MCU 及外设等的供电多为 3.3V、5V 以及 12V&#xff0c;因此设计将 24V 降压转换为各种电压非常有必要。常用的电源转换芯片有 DCDC 及 LDO 等&#xff0c;了解选型依据及其电路…

DolphinScheduler——调度系统数仓任务编排规范

原文大佬的这篇DS数仓任务编排规范有借鉴意义&#xff0c;这里摘抄下来用作学习和知识沉淀。 前言 在使用DolphinScheduler&#xff08;以下简称DS&#xff09;做数仓任务管理时&#xff0c;数据建模分层落地到调度上缺少规范&#xff0c;往往比较随意&#xff0c;例如将所有任…

OpenCV 4基础篇| OpenCV图像的裁切

目录 1. Numpy切片1.1 注意事项1.2 代码示例 2. cv2.selectROI()2.1 语法结构2.2 注意事项2.3 代码示例 3. Pillow.crop3.1 语法结构3.2 注意事项3.3 代码示例 4. 扩展示例&#xff1a;单张大图裁切成多张小图5. 总结 1. Numpy切片 语法结构&#xff1a; retval img[y:yh, x…

【JavaEE进阶】CSS选择器的常见用法

CSS选择器的主要功能就是选中页面指定的标签元素&#xff0c;选中了元素&#xff0c;才可以设置元素的属性。 CSS选择器主要有以下几种: 标签选择器类选择器id选择器复合选择器通配符选择器 接下来用代码来学习这几个选择器的使用。 <!DOCTYPE html> <html lang&q…

【C++】set、multiset与map、multimap的使用

目录 一、关联式容器二、键值对三、树形结构的关联式容器3.1 set3.1.1 模板参数列表3.1.2 构造3.1.3 迭代器3.1.4 容量3.1.5 修改操作 3.2 multiset3.3 map3.3.1 模板参数列表3.3.2 构造3.3.3 迭代器3.3.4 容量3.3.5 修改操作3.3.6 operator[] 3.4 multimap 一、关联式容器 谈…

Angular基础---HelloWorld---Day1

文章目录 1. 创建Angular 项目2.对Angular架构的最基本了解3.创建并引用新的组件&#xff08;component&#xff09;4.对Angular架构新的认识&#xff08;多组件&#xff09;5.组件中业务逻辑文件的编辑&#xff08;ts文件&#xff09;6.标签中属性的绑定(1) ID的绑定(2) class…