【第四节】C++的派生与继承

目录

一、继承特性

二、派生类的定义格式

三、派生类的继承方式

3.1 三种继承方式概述

3.2 接口继承和实现继承

四、派生类的构造和析构函数

五、类的成员重定义

六、多重继承

6.1 多继承

6.2 多继承中的二义性问题

七、虚基类

八、总结


一、继承特性

        在生物学中,遗传(或称继承)是指所有生物从其祖先那里继承特定特征的过程。这一概念同样适用于设计复杂系统,它提供了一种将系统组件组织成继承结构的方法,以便于系统描述,并且是实现代码重用的强大工具。

        在C++中,代码重用是一个核心特性。与C语言通过复制和修改代码来重用代码的低级方式不同,C++提供了继承和组合两种高级方式来实现代码重用。通过继承或组合现有类来创建新类,而不是从头开始编写。通常,这些类已经过充分测试,因此在继承时,潜在的错误仅可能出现在新增的代码部分。

        组合重用涉及将一个类作为另一个类的对象成员,通过委托成员对象来实现功能。另一种方式是继承。

继承的优势包括:

  1. 继承允许使用已存在的类来创建新类,新类继承了原有类的所有属性和操作,并可以在其基础上进行修改和扩展。

  2. 新类被称为派生类或子类,而原有类被称为基类或父类。

  3. 派生类是基类的具体化,通常情况下,派生类的表示范围比基类要小得多。

比如教师是一个人,人不一定是一个教师,派生类范围小且具体,基类范围大更抽象。

C++的继承支持单继承和多继承
单继承:派生类只有一个直接基类的继承方式
多继承:派生类有多个直接基类的继承方式

二、派生类的定义格式

单继承的基本格式如下:
class<派生类名>:<继承方式><基类名>
{
//<派生类新成员的定义>
}

其中,派生类名就是派生类的名字,并且派生类是按指定的继承方式派生的。继承方式有:
A. public  公有继承
B.private  私有继承
C.protected  保护继承

代码示例:

class CClassA{
public:CClassA(int nNum) {m_nNumA = nNum;}void print() {cout << m_nNumA << endl;}
private:int m_nNumA;};class CClassB :public CClassA {
public:void print() {cout << m_nNumB << endl;}
private:int m_nNumB;
};

多继承的定义方式和单继承差不多。
class<派生类名>:<继承方式1)<基类名1〉,<继承方式2><基类名2>,……
{
//<派生类新成员定义>
};
多继承派生类有多个基类,基类名之间用逗号分隔,每个基类名前都应有一个该基类的继承方式说明,假如不写继承方式的话,默认的继承方式为私有继承。

三、派生类的继承方式

3.1 三种继承方式概述

        类的继承方式主要包括三种:

  1. 公有继承(public):在这种方式下,基类的每个成员在派生类中保持其原有的访问权限不变。

  2. 私有继承(private):选择私有继承时,基类的所有成员在派生类中均变为私有成员,且这些成员无法被派生类的子类进一步访问。

  3. 保护继承(protected):在保护继承模式下,基类中的公有成员和保护成员在派生类中转变为保护成员,而私有成员则保持其私有性质。

        无论采用哪种继承方式,派生类的成员函数及友元函数均有权访问基类中的公有成员和保护成员,但无法触及私有成员。特别地,在公有继承情况下,派生类的对象仅能访问基类的公有成员;而在保护继承和私有继承情况下,派生类的对象则无法访问基类中的任何成员。

3.2 接口继承和实现继承

        在面向对象编程中,我们通常将类的公有成员函数视为其接口,这些接口对外界提供服务和功能。

  • 公有继承(public inheritance):在这种继承模式下,基类的公有成员函数在派生类中保持其公有性质,即基类的接口被完整地继承并成为派生类的接口。因此,这种继承方式被称为接口继承。通过公有继承,父类的接口得以传递至子类,并且子类还可以继续向下派生至孙子类。

  • 实现继承:对于私有继承(private inheritance)和保护继承(protected inheritance),派生类并不继承基类的公有接口。这意味着派生类不再对外提供基类的公有接口,其目的仅在于重用基类的实现细节。因此,这两种继承方式被称为实现继承

    • 保护继承:保护继承导致基类的公有接口在派生类中失效,这些接口只能在派生类内部被调用。尽管如此,保护继承仍然允许派生类继续向下派生至孙子类。

    • 私有继承:私有继承同样使得基类的所有接口仅能在派生类内部使用。然而,与保护继承不同的是,私有继承切断了基类与后续孙子类之间的直接联系,即私有继承的派生类无法再向其子类传递基类的接口。

四、派生类的构造和析构函数

        基类的构造函数不被继承,派生类中需要声明自己的构造函数。声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化要调用基类构造函数完成。假如基类构造函数没有缺省的构造函数,派生类的构造函数需要给基类的构造函数传递参数。
<派生类名>(<总参数表>)
:<基类构造函数名>(<参数表 1>),<子对象名>(<参数表 2>)
{
//<派生类中数据成员的初始化>
}

请看如下示例代码:

class CBase {
public:CBase(int b) :m_b(b){cout << "CBase ..." << endl;}~CBase() {cout << "~CBasecoutend" << endl;}int m_b;
};class Derived : public CBase {
public:Derived(int b, int d) :m_d(d), CBase(b) {//调用父类构造函数cout << "Derived ..." <<endl;}~Derived(){cout << "~Derived ..."<< endl;}int m_d;
};

        在C++中,当涉及到类的继承和包含对象成员时,构造函数和析构函数的调用顺序遵循特定的规则:

  • 构造函数的调用顺序

    1. 首先,调用基类的构造函数。如果基类提供了无参构造函数,则默认情况下会自动调用该构造函数;若基类没有提供无参构造函数,则派生类构造函数必须显式调用基类的某个构造函数。

    2. 其次,调用数据成员(即对象成员)的构造函数。

    3. 最后,调用派生类自身的构造函数。

  • 析构函数的调用顺序

    1. 首先,调用派生类的析构函数。

    2. 接着,调用数据成员的析构函数。

    3. 最后,调用基类的析构函数。

        值得注意的是,析构函数的调用顺序与构造函数相反,确保了在对象生命周期结束时,按照创建时的逆序进行资源清理和状态还原。

示例代码:

#include <iostream>
using namespace std;
class ObjectB {
public:ObjectB(int objb) :m_obj(objb) {cout << "ObjectB ..." << endl; }~ObjectB() {cout << "~0bjectB ..." << endl;}int m_obj;
};class ObjectD {
public:ObjectD(int objd) :m_objd(objd) {cout << "ObjectD ..." << endl;}~ObjectD() {cout << "~0bjectD ..." << endl;}int m_objd;
};class Base{
public:Base(int b) :m_b(b), m_objb(111) {//对象成员没有默认构造函数,必须在初始化列表中显示调用构造函数。cout << "Base ..." << endl;}Base(const Base& other) :m_objb(other.m_objb), m_b(other.m_b) {cout << "Copy " << "Base ..." << endl;}~Base(){cout << "~Base ..." << endl;}int m_b; ObjectB m_objb;
};class Derived :public Base {
public:Derived(int b, int d) :m_d(d), Base(b), m_objd(222) {//基类,与对象成员没有默认构造函数,必须在初始化列表中显示调用构造函数cout << "Derived .."<<endl;}Derived(const Derived& other): m_d(other.m_d), m_objd(other.m_objd), Base(other) {cout << "Copy" << "Derived ..." << endl;}~Derived() {cout << "~Derived .."<< endl;}int m_d; ObjectD m_objd;
};int main() {Derived d(100, 200);cout << d.m_b << " " << d.m_d << endl; return 0;
}

五、类的成员重定义

        在面向对象编程中,当子类需要对基类的某个成员进行功能上的修改或扩展时,会采用重定义(overwrite)机制。重定义主要分为以下两种情况:

  1. 对基类数据成员的重定义:子类可以重新定义与基类同名的数据成员,此时,派生类对象在访问该数据成员时,将直接引用派生类中的定义,从而隐式地屏蔽了基类中的同名数据成员。

  2. 对基类成员函数的重定义

    • 完全相同的重定义:子类重新定义一个与基类完全相同的成员函数(即函数名、参数列表和返回类型均一致),这种情况下,派生类对象调用该函数时,将执行派生类中的版本,基类的同名函数被隐藏。

    • 参数不同的重定义:子类定义一个与基类成员函数名相同但参数列表不同的函数,尽管参数不同,但这种做法仍属于重定义而非重载。同样地,基类的同名函数会被隐藏,调用时将执行派生类中的函数。

注意事项

  • 当基类与派生类中存在重定义的数据成员时,派生类对象访问的是派生类中的数据成员,基类的数据成员被隐藏。

  • 对于完全相同的重定义成员函数,基类的函数在派生类中被隐藏,调用时执行的是派生类的函数。

  • 即使成员函数名相同但参数不同,这种重定义也会隐藏基类的函数,这是一个需要特别注意的点。

  • 在不同作用域(如基类和派生类)中定义的函数,即使函数名相同且参数不同,也不构成重载,而是重定义。重载是指在同一作用域内,函数名相同但参数列表不同的现象,通常发生在同一个类内部。

示例代码:

#include <iostream>
using namespace std;class Base {
public:Base() :m_x(0) {}int GetBaseX() const {return m_x;}void Show() {cout << "Base::Show ..."<< endl;}int m_x;
};class Derived : public Base {
public:Derived() :m_x(0) {}int GetDerivedX() const {return m_x;}void Show(int n) {cout << "Derived::Show " << n << endl;}void Show() {cout << "Derived::Show .." << endl;}int m_x;
};class Test {
public:Base m_b;int m_x;
};int main() {Derived d;d.m_x = 10; //如果没有重定义的话,它改变的就是基类的d.Base::m_x = 20; //如果想使用基类的重定义的数据成员要是用作用域符号。cout<< d.GetBaseX()<< endl;cout << d.GetDerivedX() << endl; d.Show();d.Base::Show();//想要调用父类的函数可以通过作用域符号cout << sizeof(Derived) << endl;cout << sizeof(Test) << endl;return 0;
}

六、多重继承

6.1 多继承

        有一些事物,在分类学上属于不同的分类,比如野鸭,是鸟,也是鸭。这个时候就需要用到多继承。
        单重继承--一个派生类最多只能有一个基类
        多重继承--一个派生类可以有多个基类,如果派生类有两个或两个以上的直接基类,称为多继承。
如下图所示:


多继承派生类的定义格式:
class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,...
{
    //派生类的类体
}

class CClassC :  public CClassB, private CClassA
{

}

多继承派生类的构造函数格式:
<派生类名>(<总参数表>)
:<基类名1>(<参数表1>),<基类名 2>(<参数表 2>),<子对象名 3>(<参数 3>),…
{
//派生类构造函数
}

CClassC(int nNum)
:CClassB(nNum),CClassA(nNum),mnNum(15)
{
}

        执行顺序:先执行所有基类的构造函数,再执行派生类本身的构造函数,包含子对象在内,请看下面的例子:
class A
{ ... };
class B
{ ... };
classC
{ ... };
class D:public A, public B, public C
{ ... };

        派生类D继承了三个基类,继承方式都是公有继承。类D的成员包含了类A、类B和类C中的成员,以及它本身的成员。如果要创建类D的对象,首先要顺序执行类A、类B和类C的构造函数,再执行派生类D本身的构造函数。

6.2 多继承中的二义性问题

        一般地,派生类成员的访问是唯一的。但是在多继承的情况下,可能出现派生类对其类成员访问的不唯一性,即二义性。下面是出现二义性的两种情况:调用不同类的具有相同名字成员时可能出现二义性:我们以下面这个沙发床例子来说明二义性问题。

示例代码:

#include <iostream>
using namespace std;class Bed {
public:Bed(int weight) :m_weight(weight) {}void Sleep() {cout << "Sleep ..." << endl;}int m_weight;
};class Sofa {
public:Sofa(int weight) :m_weight(weight) {}void watchTV() {cout << "Watch TV ..." << endl;}int m_weight;
};class SofaBed :public Bed, public Sofa {//继承了之后,沙发床既可以睡觉,也可以看电视。
public:SofaBed() :Bed(0), Sofa(0) {FoldIn();}void FoldOut() {cout << "FoldOut ..." << endl;}void FoldIn() {cout << "FoldIn ..." << endl;}
};int main() {SofaBed sofaBed;//sofaBed.mweight=10;//这样访问是不明确的//sofaBed.mweight=20://同上sofaBed.Bed::m_weight = 10;sofaBed.Sofa::m_weight = 20;sofaBed.watchTV();sofaBed.FoldOut();sofaBed.Sleep();return 0;
}

        但是我们思考:沙发床应该只有一个重量,不应该分成两个重量。很自然的我们可以想到:将沙发和床进行抽象为一个整体,比如家具,在家具中安插一个重量,代码如下:

#include <iostream>
using namespace std;class Furniture {
public:Furniture(int weight) :m_weight(weight) {}int m_weight;
};class Bed : public Furniture {
public:Bed(int weight) : Furniture(weight) {}void Sleep() {cout << "Sleep ..." << endl;}int m_weight;
};class Sofa : public Furniture {
public:Sofa(int weight) :Furniture(weight) {}void watchTV() {cout << "Watch TV ..." << endl;}int m_weight;
};class SofaBed :public Bed, public Sofa {
public:SofaBed() :Bed(0), Sofa(0) {FoldIn();}void FoldOut() {cout << "FoldOut ..." << endl;}void FoldIn() {cout << "FoldIn ..." << endl;}
};int main() {SofaBed sofaBed;//sofaBed.m_weight=10;//这样访问是不明确的//sofaBed.m_weight=20://同上sofaBed.Bed::m_weight = 10;sofaBed.Sofa::m_weight = 20;sofaBed.watchTV();sofaBed.FoldOut();sofaBed.Sleep();return 0;
}

        这么安排,可能很多人就会觉得问题已经解决了,但是这里依然还是会出现二义性问题因为从 C++继承的角度讲,Bed中包含一个furniture,sofa中包含一个furnitur。所以重量这个东西还是有两份。

那该怎么办呢?这下就要用虚继承来解决这个问题。

七、虚基类

        在多重继承的架构中,当一个派生类继承自多个基类,而这些基类又共享同一个共同的基类时,访问该共同基类中的成员可能会引发二义性问题。为了解决这一问题,C++引入了虚基类的概念。

        虚基类的引入旨在应对存在共同基类的情况,其主要作用是消除因多次继承同一基类而可能产生的二义性。通过使用虚基类,可以确保在最底层的派生类中,对于共同的基类成员只有一份唯一的副本,避免了数据的重复。

        具体而言,如果一个派生类拥有多个直接基类,而这些直接基类均继承自同一个共同的基类,那么在最终的派生类中,原本会保留该间接共同基类的多份同名数据成员。C++通过提供虚基类的机制,确保在继承过程中仅保留一份共同基类的成员,从而避免了成员的重复和潜在的二义性问题。

虚基类(virtual base class)说明格式如下:
virtual<继承方式><基类名>
virtual public CClassA

代码示例:

#include <iostream>
using namespace std;class Furniture {
public:Furniture(int weight) :m_weight(weight) {}int m_weight;
};class Bed : virtual public Furniture {
public:Bed(int weight) : Furniture(weight) {}void Sleep() {cout << "Sleep ..." << endl;}
};class Sofa : virtual public Furniture {
public:Sofa(int weight) :Furniture(weight) {}void watchTV() {cout << "Watch TV ..." << endl;}
};class SofaBed :public Bed, public Sofa {
public:SofaBed() :Bed(0), Sofa(0) {FoldIn();}void FoldOut() {cout << "FoldOut ..." << endl;}void FoldIn() {cout << "FoldIn ..." << endl;}
};int main() {SofaBed sofaBed;sofaBed.m_weight = 10;return 0;
}

使用虚继承后,可以实现如下的效果,项层类中的数据在对象中只会存在一份。

八、总结

        C++语言提供了类继承的机制,这是面向对象编程中的核心概念之一。在继承关系中,派生类的成员函数和友元函数能够访问基类中所有的公有和保护类型的数据成员及成员函数。然而,派生类对象本身仅能访问基类中的公有数据成员和成员函数。

        多重继承是指一个类从多个基类中继承特性的机制,派生类因此获得了所有基类的属性。当一个类作为两个或更多基类的派生类时,必须在派生类的声明中,在类名和冒号后面列出所有基类的名称,各基类之间以逗号分隔。派生类的构造函数有责任激活所有基类的构造函数,并将必要的参数传递给它们。

        派生类本身也可以成为其他类的基类,从而形成一个继承链。在这种情况下,当派生类的构造函数被调用时,它的所有基类的构造函数也会被依次激活。为了解决多重继承中可能出现的二义性问题,C++引入了虚基类的概念。虚基类的目的是确保公共基类在其派生对象中仅产生一个基类子对象,从而避免因多次继承同一基类而导致的重复和冲突。

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

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

相关文章

vue学习汇总

目录 一、vue基本语法 1.插值表达式 {{}} 2.显示数据(v-text)和(v-html) 3.事件处理(v-on) 4.循环遍历(v-for) 5.判断语法(v-if) 6.元素显示与隐藏(v-show) 7.动态设置属性(v-bind) 8.数据双向绑定(v-model) 9.计算属性 二、vue组件 1.使用组件的三个步骤 2.注册组…

有趣的css - 列表块加载动效

大家好&#xff0c;我是 Just&#xff0c;这里是「设计师工作日常」&#xff0c;今天分享的是用 css 打造一个极简的列表块加载动效。 最新文章通过公众号「设计师工作日常」发布。 目录 整体效果核心代码html 代码css 部分代码 完整代码如下html 页面css 样式页面渲染效果 整…

使用Ollama和Open WebUI管理本地开源大模型的完整指南

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年5月27日12点20分 &#x1f004;️文章质量&#xff1a;96分 目录 ✨️Open-WebUI介绍 优点 &#x1f4a5;部署教程…

Linux命令 jps(Java Process Status)解释

文章目录 1、第一种解释2、第二种解释3、第三种解释 1、第一种解释 jps 命令本身并不是一个标准的 Unix/Linux 命令&#xff0c;但您可能是想提到 jps 的一个变种或误写了 jps 为 jps&#xff0c;而实际上可能是想提及 jps&#xff08;Java Virtual Machine Process Status To…

Power Bi 自定义进度条,圆角框,矩阵图标的实现

最近项目在做Power BI&#xff0c;我总结了几个常用的自定义样式&#xff0c;分享一下做法。 比如我们要实现如图这样的一个样式&#xff1a; 这包含了一个带文字的自定义进度条&#xff0c;矩阵有树型展开以及图标显示&#xff0c;最外面有圆角框包围。我觉得这几个样式出现…

海云安两大金融案例入编行业典范,七大安全领域实力登榜《2024中国金融网络安全全景图》

近日&#xff0c;数说安全与《中国信息安全》杂志联合编写并发布了《2024年中国金融行业网络安全研究报告》&#xff08;以下简称报告&#xff09;、《2024年中国金融行业网络安全案例集》&#xff08;以下简称案例集&#xff09;、《2024年中国金融行业网络安全市场全景图》&a…

VSCode 报错 之 运行 js 文件报错 ReferenceError: document is not defined

1. 背景 持续学习ing 2. 遇到的问题 在VSCode 右键 code runner js 文件报错 ReferenceError: document is not defined eg&#xff1a; // 为每个按钮添加点击事件监听器 document.querySelectorAll(button).forEach(function (button) {button.addEventListener(click, f…

kafka-守护启动

文章目录 1、kafka守护启动1.1、先启动zookeeper1.1.1、查看 zookeeper-server-start.sh 的地址1.1.2、查看 zookeeper.properties 的地址 1.2、查看 jps -l1.3、再启动kafka1.3.1、查看 kafka-server-start.sh 地址1.3.2、查看 server.properties 地址 1.4、再次查看 jps -l 1…

深入探索C++继承机制:从概念到实践的全面指南

目录 继承的概念及定义 继承的概念 继承的定义 定义格式 继承方式和访问限定符 继承基类成员访问方式的变化 默认继承方式 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 继承的方式 菱形虚拟继承 菱形虚拟继承原理 继承…

【嵌入式】RS485通信+硬件

目录 1.物理层 2.协议层 3.收发电路 3.1三极管开关电路 3.2收发原理图解释 1.物理层 对比 CAN 通讯网络&#xff0c;可发现它们的网络结构组成是类似的&#xff0c;每个节点都是由一个通讯控制器和 一个收发器组成&#xff0c;在 RS-485 通讯网络中&#xff0c;节点中的…

Java基础:异常(三)

Java基础&#xff1a;异常&#xff08;三&#xff09; 文章目录 Java基础&#xff1a;异常&#xff08;三&#xff09;1. Java异常体系1.1 错误的分类1.2 异常的分类 2. 异常的捕获与处理2.1 try-catch2.2 finally 3. 异常的抛出4. 自定义异常 1. Java异常体系 Java的异常体系是…

ubuntu20 安装Anaconda

Anaconda官网&#xff1a;Anaconda | The Operating System for AI 然后就开始下载了。下载完 直接打开终端运行 bash Anaconda3-2024.02-1-Linux-x86_64.sh 按回车ENTER 一直空格&#xff0c;直到 yes&#xff0c;回车 确认安装路径&#xff0c;默认即可&#xff0c;直接回车…

SQL Server定期收缩日志文件详细步骤——基于SQL Server 2012

SQL Server定期收缩日志文件详细步骤 一、环境配置1、查看数据库的属性2、文件设置3、备份模式4、查看收缩配置5、查看收缩选项 二、编写作业计划1、选择新建作业2、常规配置3、步骤4、输入内容5、脚本详解6、新建计划7、输入名称、选择执行时间8、查看测试9、查看测试结果 一、…

【最优化方法】实验四 约束最优化方法的MATLAB实现

实验的目的和要求&#xff1a;通过本次实验使学生较为熟练使用MATLAB软件&#xff0c;并能利用该软件进行约束最优化方法的计算。 实验内容&#xff1a; &#xff11;、罚函数法的MATLAB实现 &#xff12;、可行方向法的MATLAB实现 学习建议&#xff1a; 本次实验就是要通…

国际标准引脚,宽范围工作温度–40℃ 到 105℃,3W 1.5KVDC 隔离单输出 DC/DC 电源模块 ——TPL-3W系列

TPL-3W系列是一款额定功率为3W的隔离产品&#xff0c;国际标准引脚&#xff0c;宽范围工作温度–40℃ 到 105℃&#xff0c;在此温度范围内都可以稳定输出3W&#xff0c;并且效率非常高&#xff0c;高达88%&#xff0c;同时负载调整率非常低&#xff0c;对于有输出电压精度有要…

失落的方舟台服封号的解决方法 怎么避免封号?

失落的方舟台服封号的解决方法 怎么避免封号&#xff1f; 失落的方舟这款游戏是由Smile Gate研发的MMORPG游戏&#xff0c;这款游戏将于5月30日正式开服。游戏采用虚幻3引擎&#xff0c;为玩家们提供了丰富的游戏玩法与精美的游戏画面。游戏的背景是在中世纪&#xff0c;玩家们…

网络原理-------TCP协议

文章目录 TCP协议TCP协议段格式TCP原理确认应答机制 (安全机制)超时重传机制 (安全机制)连接管理机制 (安全机制)滑动窗口 (效率机制)流量控制 (安全机制)拥塞控制 (安全机制)延迟应答 (效率机制)捎带应答 (效率机制) 基于TCP的应用层协议 TCP协议 TCP, 即 Transmission Contr…

C# TcpClient

TcpClient 自己封装的话&#xff0c;还是比较麻烦的&#xff0c;可以基于线程&#xff0c;也可以基于异步写&#xff0c;最好的办法是网上找个插件&#xff0c;我发现一个插件还是非常好用的&#xff1a;STTech.BytesIO.Tcp 下面是这个插件作者的帖子&#xff0c;有兴趣的可以…

Mac连接虚拟机(Linux系统)

1.确定虚拟机的IP地址 ifconfig //终端命令&#xff0c;查询ip地址 sudo apt install net-tools 安装完成后再次执行 ifconfig&#xff1a; 2.安装SSH&#xff08;加密远程登录协议&#xff09; (1).安装OpenSSH服务器软件包&#xff1a; sudo apt-get install openssh-ser…

【惊艳视界】Perfectly Clear Workbench:让您的图像瞬间焕发生机!

在数字化时代&#xff0c;图像已成为我们生活中不可或缺的一部分。无论是摄影爱好者&#xff0c;还是专业设计师&#xff0c;都渴望拥有一款能够轻松提升图像质量的神奇工具。今天&#xff0c;我们为您带来了一款图像清晰处理软件的佼佼者——Perfectly Clear Workbench&#x…