C++【深入理解继承】

news2024/10/5 9:30:51

文章目录

  • 一、继承概念与定义
  • 二、基类和派生类对象赋值转换
  • 三、派生类的默认成员函数
    • (1)构造函数
    • (2)拷贝构造函数
    • (3)赋值重载
    • (4)析构函数
  • 四、复杂的菱形继承及菱形虚拟继承
    • (1)菱形继承
    • (2)菱形虚拟继承解决数据冗余和二义性的原理
      • (2.1)看现象和方法
      • (2.2) 看原理
      • (2.3)关于菱形虚拟继承的一些问题
      • (2.5)继承总结
  • 五、继承与组合
    • (5.1)概念
    • (5.2)继承与组合比较
    • (5.3)结论

一、继承概念与定义

1、继承的概念:
继承是面向对象程序设计中最重要的一个概念。它允许我们依据另一个类来定义一个类,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样做也达到了重用代码功能和提高执行效率的效果。当创建一个类时,就不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可,这个已有的类称为基类,产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。
继承代表了 is a 关系。例如,学生是人。如下图
在这里插入图片描述

class person {
public:
    void sleep() 
    void walk()
protected:
string _name = "nza";
int _age = 21;  
    
};
//派生类
class student : public person {
public:
    void study()
protected:
int _ID;
};
int main()
{
    Student s;
    s.walk();
   return 0;
}

继承后父类的person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了student复用了person的成员。也可使用监视窗口查看student,可以看到变量的复用。调用walk可以看到成员函数的复用。
2.定义格式:
如上的person就是父类,也称基类,student是子类,也称派生类
3.继承方式:
三种:public继承,protected继承,private继承。
4.访问限定符:
public访问,protected访问,private访问。
5.访问控制和继承:
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
在这里插入图片描述
一个派生类继承了所有的基类方法,但下列情况除外:
基类的构造函数、析构函数和拷贝构造函数。
基类的重载运算符。
基类的友元函数。
6.继承基类成员访问方式的变化
在这里插入图片描述
一般基本不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
总结:
1、基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
7.继承中的作用域:
在继承体系中基类和派生类都有独立的作用域。
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员来进行显示访问)需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。在实际中在继承体系里面最好不要定义同名的成员。

class P
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class S : public P
{
public:
void fun(int i)
{
    A::fun();
    cout << "A" <<i<<endl;
}
};
int main()
{
     S s;
     s.fun(1);
}

S中的fun和A中的fun不是构成重载,因为不是在同一作用域,S中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
8.继承与友元:
友元关系不能继承,就是说基类友元不能访问子类私有和保护成员。
如果一定需要友元关系就要在父类和子类中都声明。
9.继承与静态成员:
如果类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

二、基类和派生类对象赋值转换

先看一下继承中对象内存模型:
在这里插入图片描述
派生类会保留基类的所有属性和行为,每一个派生类的实例都包含一份完整的基类实例数据。
再看赋值转换:
派生类对象 可以赋值给基类的对象或基类的指针或基类的引用。这里有个生动的说法叫切片或者切割。意思是把派生类中父类那部分切来赋值过去,但是基类对象不能赋值给派生类对象,而且基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。如下图:
在这里插入图片描述
因为子类的成员数量一定>=父类的成员数量,所以当一个子类赋值给父类时,子类就可以将属于与父类的成员赋值给父类,剩下的成员父类中不含有。其实就是将子类的成员切片出来赋值给父类。如上图,父类A对象中的成员,子类B对象赋值时将自己含有的A中的成员切片赋值给父类A对象。

详细图解和代码:
在这里插入图片描述

class A
{};
class B : public A
{};
int  main ()
{
  B sobj ;
 // 1.子类对象可以赋值给父类对象/指针/引用
  A pobj = sobj ;
  A* pp = &sobj;
  A& rp = sobj;
 
 //2.基类对象不能赋值给派生类对象
  sobj = pobj;
 
  // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
  pp = &sobj
  B* ps1 = (B*)pp; // 这种情况转换是可以的。
 
  pp = &pobj;
  B* ps2 = (B*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
}

三、派生类的默认成员函数

(1)构造函数

基类中的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅只是对于普通函数,对于基类的构造函数和析构函数时不能被继承的。派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
下面的代码中Person有默认构造函数,则在子类的构造函数中,会先调用父类的默认构造函数完成父类成员的初始化,然后在调用子类的构造函数初始化子类的成员。

class Person
{
public:
	//父类构造函数
	Person(string name = "父类")//父类有默认构造函数
		:_name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name;
};
class Student : public Person
{
public:
	//子类构造函数
	Student(string name, int num)
		:_num(num)
	{
		cout << "Student()" << endl;
	}
protected:
	int _num;
};

int main()
{
	Student s("nza", 1);

	return 0;
}

如果父类没有默认构造函数,不传参就无法初始化,则需要在子类的构造函数中显式调用父类构造函数完成父类成员的初始化,可以使用初始化列表来完成构造函数的任务,其中初始化列表可以使用基类的构造函数来完成和填充派生类中的数据的初始化。如下:

class Person
{
public:
	//父类构造函数
	Person(string name)//如果不传参无法初始化
		:_name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name;
};
class Student : public Person
{
public:
	//子类构造函数
	Student(string name, int num)
		: Person(name)//调用父类构造函数初始化
		, _num(num)
	{
		cout << "Student()" << endl;
	}
protected:
	int _num;
};

int main()
{
	Student s("nza", 1);

	return 0;
}

总之构造函数的调用顺序是按照继承的层次自顶向下,从基类再到派生类。不管初始化列表的顺序如何,派生类构造函数总是先调用基类的构造函数,再执行其他代码。

(2)拷贝构造函数

和构造函数的思想一样,派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。

class Person
{
public:
	//父类构造函数
	Person(string name = "父类")//父类有默认构造函数
		:_name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

protected:
	string _name;
};
class Student : public Person
{
public:
	//子类构造函数
	Student(string name, int id)
		:_id(id)
		, Person(name)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		: Person(s)//直接传s,通过切片拿到父类的部分
		, _id(s._id)
	{
		cout << "Student(const Student& s)" << endl;
	}
protected:
	int _id;
};

int main()
{
	Student s("nza", 1);
	Student s2(s);//拷贝构造
	return 0;
}

(3)赋值重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。因为函数名相同构成隐藏,所以需要显式调用。

class Person
{
public:
	//父类构造函数
	Person(string name = "父类")//父类有默认构造函数
		:_name(name)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

protected:
	string _name;
};
class Student : public Person
{
public:
	//子类构造函数
	Student(string name, int num)
		:_num(num)
		, Person(name)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		: Person(s)//直接传s,通过切片拿到父类的部分
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator = (const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator =(s);
			_num = s._num;
		}
		return *this;
	}
protected:
	int _num;
};

int main()
{
	Student s("nza", 1);
	//Student s2(s);//拷贝构造
	Student s3("kd", 7);
	s = s3;//赋值重载
	return 0;
}

在这里插入图片描述

(4)析构函数

和构造函数类似,析构函数也不能被继承。在创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类的构造函数,再执行派生类的构造函数,在销毁派生类对象时,析构函数的执行顺序和继承顺序正好相反,即先执行派生类析构函数,再执行基类的析构函数。
在上面的代码分别加入如下析构代码:

    ~Person()
	{
		cout << "~Person()" << endl;
	}
	~Student()
    {
      cout<<"~Student()" <<endl;
    }

结果:
在这里插入图片描述
结果和前面讲的一致,派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。

四、复杂的菱形继承及菱形虚拟继承

(1)菱形继承

先说单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
在这里插入图片描述
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。
在这里插入图片描述
菱形继承:菱形继承是多继承的一种特殊情况。
在这里插入图片描述
看如下代码:

class A
{
public:
	string _name; // 姓名
};
class B : public A
{
protected:
	int _num; //学号
};
class C : public A
{
protected:
	int _id; // 职工编号
};
class D : public B, public C
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{

	D d;
	d._name = "peter";
	
}

main函数里面这样写会有二义性无法明确知道访问的是哪一个。如图:
在这里插入图片描述

这时候换成下面的代码即需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决。如下代码和图:

    D d;
	d.B::_name = "nza";
	d.C::_name = "kd";

在这里插入图片描述

从上面面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在D的对象中A成员会有两,会造成歧义,比如说正常一个人的身份就是一份。
这就是菱形继承,由于最底层的派生类继承了两个基类,同时这两个基类有又继承的是一个基类,而会造成最顶部基类的两次调用,会造成数据冗余及二义性问题。数据冗余性的本质是空间浪费,多存了;二义性是不知道要访问谁。

(2)菱形虚拟继承解决数据冗余和二义性的原理

(2.1)看现象和方法

用虚继承来解决,在继承方式前加上virtual,先解决刚才的冗余问题。
在这里插入图片描述如上我们解决了菱形继承的两个问题,也就是加virtual形成虚拟菱形继承。那么原理是怎样的?

(2.2) 看原理

先看如下代码:

class A
{
public:
	int _a;
};
// class B : public A
class B : /*virtual*/ public A
{
public:
	int _b;
};
// class C : public A
class C : /*virtual*/ public A
{
public:
	int _c;
};
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;
}

没加virtual,内存里观察到可以看到数据冗余:
在这里插入图片描述
加了virtual:如图:
在这里插入图片描述

在这里可以看到,这里D对象中将A放到的了对象组成的最下面。
这个A同时属于B和C,那么B和C如何去找到公共的A?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量,通过偏移量可以找到下面的A。如下图,B通过指找到偏移量,加上20字节正好到了A地址处,同理C也是。
在这里插入图片描述
这里虚继承要解决数据冗余性和二义性,把它们放到了一个公共的位置A,A属于B也属于C。

(2.3)关于菱形虚拟继承的一些问题

为什么不直接存A的地址?
因为把它们指向一个表不仅能存偏移量,还要存其他的东西的偏移量,还要解决其他问题。
所需空间变大了吗是亏还是赚?
解决数据冗余性是要两个指针的成本,如果类A变大,是8字节不亏不赚,如果是16字节转8字节。如果小,也不完全亏,至少解决问题了,没有节省也没有浪费太多,而且成本永远是固定的8字节。而指向的空间几乎可以忽略不计,一个类可能会有多个对象,但是它们还是指向一个表。
基类有多个成员就有多个指针?
并不是,如果在基类A中增加成员,不需要增加指针,派生类各自还是只存一个,因为编译器通过偏移量找到第一个位置,按声明顺序算,简单来说就是挨着的,通过找到第一个位置,按顺序可能需要内存对齐或者直接跳过多个字节,来找到后面的位置。如图:
在这里插入图片描述

(2.5)继承总结

多继承就是C++语法中的一个复杂体现,也是缺陷之一,因为有多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,更不要设计出菱形继承,否则在性能以及复杂度上会很棘手。

五、继承与组合

(5.1)概念

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象,比如B继承A,B都是一个A。组合是一种has-a的关系。假设D组合了C,每个D对象中都有一个C对象。

(5.2)继承与组合比较

如图:
在这里插入图片描述

对象继承:它允许根据基类的实现来定义派生类的实现。这种通过生成派生类的复用被称为白箱复用。“白箱”是相对可视化的,在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,继承就像跟团旅游中的团体旅游,受限制于自由,如上图,B可以直接用A的两个成员,但是A改动保护可能会影响B,耦合度高,即关联度高。
对象组合:是类继承之外的另一种复用选择。复杂的功能可以通过组合对象来获得。对象组合要求被组合的对象具有良好定义的接口,这种复用风格被称为黑箱复用,也就是被组合对象的内部细节是不可见的。组合类之间没有很强的依赖关系,优先使用对象组合有利于保持每个类被封装,组合像是跟团旅游中的自由旅游,如上图,D可以直接用C的一个成员(public),间接用另外一个成员(通过访问public中的func来间接访问protected中的成员),C改动保护和私有成员基本不会影响D,耦合度低,即关联低

(5.3)结论

因为互相影响越小越好,优先使用对象组合,而不是类继承,这就符合软件工程的一个重要思想:低耦合,高内聚 。
但是继承也是非常重要的,有些关系就适合继承那就用继承,而且要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

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

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

相关文章

python 不指定参数个数---args基础用法

前言&#xff1a; 在有些时候&#xff0c;设计函数的时候&#xff0c;可能不知道要传入的参数类型或者参数个数&#xff0c;此时args可以很好地解决。 一、*args的基本用法 1。传入不指定个数的参数&#xff0c; 2。参数的类型也不指定&#xff0c;可以是任意类型数据&…

k8s学习(三十五)飞腾2000+麒麟V10离线部署metrics-server

文章目录1、下载metrics-server配置文件2、下载推送metrics-server镜像3、修改metrics-server配置4、启动metrics-server1、下载metrics-server配置文件 在有网机器上从网站https://github.com/kubernetes-incubator/metrics-server下载 拷贝其到离线机器K8S的master节点。 2…

九龙证券|又3个涨停,退市风险急升!

*ST新海退市危险急剧上升&#xff01; 到4月14日&#xff0c;*ST新海收盘价接连14个买卖日低于1元/股。按照退市新规&#xff0c;若*ST新海在接下来6个买卖日收盘价继续低于1元/股&#xff0c;将触及买卖类强制退市景象而终止上市&#xff0c;公司股票将不进入退市整理期。 面…

Android Audio音量设置原理流程分析

Android Audio音量设置原理流程分析 简介 本篇文章主要介绍Android音量设置从App应用层到framework层执行流程&#xff0c;以及相关的细节和原理分析&#xff0c;建议在阅读此文章前去看博主的混音理论篇的声音的音量属性和声音相关公式的推导章节&#xff0c;这对阅读时理解音…

2023年泰迪杯数据挖掘挑战赛B题完整数据分析与预测(5.针对完整数据的组合预测-机器学习+深度学习)

背景 2023年泰迪杯完整数据最新出炉&#xff0c;博主根据最新完整数据对原来的预测方案进行了调整&#xff0c;采用机器学习深度学习的组合预测来实现最终预测 全部数据已经出炉&#xff0c;可以看出训练样本和预测样本都增加了十倍&#xff0c;这对于数据的处理复杂程度也有…

linux驱动开发 - 04_Linux 设备树学习 - DTS语法

文章目录Linux 设备树学习 - DTS语法1 什么是设备树&#xff1f;2 DTS、DTB和DTC3 DTS 语法3.1 dtsi 头文件3.2 设备节点3.3 标准属性1、compatible 属性2、model 属性3、status 属性4、#address-cells 和#size-cells 属性5、reg 属性6、ranges 属性7、name 属性8、device_type…

FreeRTOS 任务切换

文章目录一、PendSV 异常二、FreeRTOS 任务切换场合1. 执行系统调用 taskYIELD()2. 系统滴答定时器(SysTick)中断 SysTick_Handler三、PendSV 中断服务函数 PendSV_Handler()四、查找下一个要运行的任务 vTaskSwitchContext()五、FreeRTOS 时间片调度六、时间片调度实验RTOS 系…

ECF机制:信号 (Signal)

&#x1f4ad; 写在前面&#xff1a;ECF (异常控制流) 机制是存在于系统的所有层级中的&#xff0c;所以这一块的知识我们需要系统地去学习。前几章我们探讨过了异常 (Exceptions)&#xff0c;由硬件触发&#xff0c;在内核代码中处理。讲解了进程的上下文切换 (Process Contex…

Shiro整合SpringBoot项目实战

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

阿里入局,通义千问备受期待

目录官宣内测体验内容鸟鸟分鸟后言继百度文心一言发布三周之后&#xff0c;4月7日阿里通义大模型终于推出通义千问&#xff0c;阿里正式加入ChatGPT战局。下午市场一片大热&#xff0c;对于深耕NLP多年的阿里&#xff0c;大家有足够的期待。 官宣内测 “你好&#xff0c;我叫通…

【SpringBoot】springboot启动热部署

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ SpringBoot——手工启动热部署一、pom.xml导入…

Kotlin 是后端开发的未来

Kotlin 是后端开发的未来 严格类型、命名参数、多范式语言 您今天遇到的每个后端开发人员都会说他们使用 JavaScript、Python、PHP 或 Ruby 编写代码。近年来&#xff0c;您会遇到一小部分人转而使用 Kotlin 作为他们创建 Web 服务器的语言选择。由于我在学习Ktor&#xff0c;所…

深度学习12. CNN经典网络 VGG16

深度学习12. CNN经典网络 VGG16一、简介1. VGG 来源2. VGG分类3. 不同模型的参数数量4. 3x3卷积核的好处5. 关于学习率调度6. 批归一化二、VGG16层分析1. 层划分2. 参数展开过程图解3. 参数传递示例4. VGG 16各层参数数量三、代码分析1. VGG16模型定义2. 训练3. 测试一、简介 …

Html5版音乐游戏制作及分享(H5音乐游戏)

这里实现了Html5版的音乐游戏的核心玩法。 游戏的制作借鉴了&#xff0c;很多经典的音乐游戏玩法&#xff0c;通过简单的代码将音乐的节奏与操作相结合。 可以通过手机进行游戏&#xff0c;准确点击下落时的目标&#xff0c;进行得分。 点击试玩 游戏内的下落数据是通过手打记…

【Pytorch】使用pytorch进行张量计算、自动求导和神经网络构建

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 这是目录张量计算张量的属性和方法&#xff0c;如何使用它们来获取或修改张量的信息和形状张量之间的运算和广播机制&#xff0c;如何使用torch.add(), torch.sub(), torch.mul(), to…

【Redis7】Redis7 持久化(重点:RDB与AOF重写机制)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 持久化&#xff08;重点&#xff1a;RDB与AOF重写机制&#xff09;。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 …

Java项目实战笔记(瑞吉外卖)-4

公共字段自动填充功能 问题分析 前面已经完成了后台系统的员工管理功能开发&#xff0c;在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段&#xff0c;在编辑员工时需要设置修改时间和修改人等字段。这些字段属于公共字段&#xff0c;也就是很多表中都有这些字段…

前端搭建小人逃脱游戏(内附源码)

The sand accumulates to form a pagoda✨ 写在前面✨ 功能介绍✨ 页面搭建✨ 样式设置✨ 逻辑部分✨ 写在前面 上周我们实通过前端基础实现了打字通&#xff0c;当然很多伙伴再评论区提出了想法&#xff0c;后续我们会考虑实现的&#xff0c;今天还是继续按照我们原定的节奏来…

对决:Kubernetes vs Docker Swarm - 谁才是最优秀的容器编排方案?

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 文章目录一、介绍1. 什么是Kubernetes2. 什么是Docker Swarm3. 为什么需要容器编排&#xff1f;二、 架构比较1. Kubern…

Spring框架——IOC、DI

本篇博客主要介绍Java中的IOC和DI&#xff0c;以及在String框架中的应用。首先&#xff0c;我们将对IOC和DI进行概念介绍&#xff0c;然后讲解它们的关系及在String框架中的应用&#xff0c;最后通过一个实例来展示它们的具体用法。 IOC和DI的概念介绍 IOC&#xff08;Invers…