Java 中的继承和多态

news2024/11/19 3:47:25

面向对象的三大特性:封装、继承、多态。在这三个特性中,如果没有封装和继承,也不会有多态。

那么多态实现的途径和必要条件是什么呢?以及多态中的重写和重载在JVM中的表现是怎么样?在Java中是如何展现继承的特性呢?对于子类继承于父类时,又有什么限制呢?本文系基础,深入浅出过一遍 Java 中的多态和继承。

多态

多态是同一个行为具有多个不同表现形式或形态的能力。

举个栗子,一只鸡可以做成白切鸡、豉油鸡、吊烧鸡、茶油鸡、盐焗鸡、葱油鸡、手撕鸡、清蒸鸡、叫花鸡、啤酒鸡、口水鸡、香菇滑鸡、盐水鸡、啫啫滑鸡、鸡公煲等等。

多态实现的必要条件

用上面的“鸡的十八种吃法“来举个栗子。

首先,我们先给出一只鸡:

class Chicken{
    public void live(){
        System.out.println("这是一只鸡");
    }
}

1. 子类必须继承父类

对于子类必须继承父类,小编个人认为,是因为按照面向对象的五大基本原则所说的中的依赖倒置原则:抽象不依赖于具体,具体依赖于抽象。既然要实现多态,那么必定有一个作为"抽象"类来定义“行为”,以及若干个作为"具体"类来呈现不同的行为形式或形态。

所以我们给出的一个具体类——白切鸡类:

class BaiqieChicken extends Chicken{ }

但仅是定义一个白切鸡类是不够的,因为在此我们只能做到复用父类的属性和行为,而没有呈现出行为上的不同的形式或形态

2. 必须有重写

重写,简单地理解就是重新定义的父类方法,使得父类和子类对同一行为的表现形式各不相同。我们用白切鸡类来举个栗子。

class BaiqieChicken extends Chicken{
    public void live(){
        System.out.println("这是一只会被做成白切鸡的鸡");
    }
}

这样就实现了重写,鸡类跟白切鸡类在live()方法中定义的行为不同,鸡类是一只命运有着无限可能的鸡,而白切鸡类的命运就是做成一只白切鸡。

但是为什么还要有“父类引用指向子类对象”这个条件呢?

3. 父类引用指向子类对象

其实这个条件是面向对象的五大基本原则里面的里氏替换原则,简单说就是父类可以引用子类,但不能反过来。

当一只鸡被选择做白切鸡的时候,它的命运就不是它能掌控的。

Chicken c = new BaiqieChicken();
c.live();

运行结果:

 

这是一只会被做成白切鸡的鸡

为什么要有这个原则?因为父类对于子类来说,是属于“抽象”的层面,子类是“具体”的层面。“抽象”可以提供接口给“具体”实现,但是“具体”凭什么来引用“抽象”呢?而且“子类引用指向父类对象”是不符合“依赖倒置原则”的。

当一只白切鸡想回头重新选择自己的命运,抱歉,它已经在锅里,逃不出去了。

BaiqieChicken bc = new Chicken();
bc.live();

多态的实现途径

多态的实现途径有三种:重写、重载、接口实现,虽然它们的实现方式不一样,但是核心都是:同一行为的不同表现形式

1. 重写

重写,指的是子类对父类方法的重新定义,但是子类方法的参数列表和返回值类型,必须与父类方法一致!所以可以简单的理解,重写就是子类对父类方法的核心进行重新定义。

举个栗子:

class Chicken{
    public void live(String lastword){
        System.out.println(lastword);
    }
}
class BaiqieChicken extends Chicken{
    public void live(String lastword){
        System.out.println("这只白切鸡说:");
        System.out.println(lastword);
    }
}

这里白切鸡类重写了鸡类的live()方法,为什么说是重写呢?因为白切鸡类中live()方法的参数列表和返回值与父类一样,但方法体不一样了。

2. 重载

重载,指的是在一个类中有若干个方法名相同,但参数列表不同的情况,返回值可以相同也可以不同的方法定义场景。也可以简单理解成,同一行为(方法)的不同表现形式。

举个栗子:

class BaiqieChicken extends Chicken{
    public void live(){
        System.out.println("这是一只会被做成白切鸡的鸡");
    }
    public void live(String lastword){
        System.out.println("这只白切鸡说:");
        System.out.println(lastword);
    }
}

这里的白切鸡类中的两个live()方法,一个无参一个有参,它们对于白切鸡类的live()方法的描述各不相同,但它们的方法名都是live。通俗讲,它们对于白切鸡鸡生的表现形式不同。

3. 接口实现

接口,是一种无法被实例化,但可以被实现的抽象类型,是抽象方法的集合,多用作定义方法集合,而方法的具体实现则交给继承接口的具体类来定义。所以,接口定义方法,方法的实现在继承接口的具体类中定义,也是对同一行为的不同表现形式

interface Chicken{
    public void live();
}
class BaiqieChicken implements Chicken{
    public void live(){
        System.out.println("这是一只会被做成白切鸡的鸡");
    }
}
class ShousiChicken implements Chicken{
    public void live(){
        System.out.println("这是一只会被做成手撕鸡的鸡");
    }
}

从上面我们可以看到,对于鸡接口中的live()方法,白切鸡类和手撕鸡类都有自己对这个方法的独特的定义。

在虚拟机中多态如何表现

前文我们知道,java文件在经过javac编译后,生成class文件之后在JVM中再进行编译后生成对应平台的机器码。而JVM的编译过程中体现多态的过程,在于选择出正确的方法执行,这一过程称为方法调用

方法调用的唯一任务是确定被调用方法的版本,暂时还不涉及方法内部的具体运行过程。(注:方法调用不等于方法执行)

在介绍多态的重载和重写在JVM的实现之前,我们先简单了解JVM提供的5条方法调用字节码指令:

invokestatic:调用静态方法。
invokespecial:调用实例构造器方法、私有方法和父类方法。
invokevirtual:调用所有的虚方法(这里的虚方法泛指除了invokestatic、invokespecial指令调用的方法,以及final方法)。
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
invokedynamic:先在运行时动态解析出调用点限定符所应用的方法(说人话就是用于动态指定运行的方法)。

而方法调用过程中,在编译期就能确定下来调用方法版本的静态方法、实例构造器方法、私有方法、父类方法和final方法(虽是由invokevirtual指令调用)在编译期就已经完成了运行方法版本的确定,这是一个静态的过程,也称为解析调用

分派调用则有可能是静态的也可能是动态的,可能会在编译期发生或者运行期才确定运行方法的版本。

而分派调用的过程与多态的实现有着紧密联系,所以我们先了解一下两个概念:

静态分派:所有依赖静态类型来定位方法执行版本的分派动作。
动态分派:根据运行期实际类型来定位方法执行版本的分派动作。

1. 重载

我们先看看这个例子:

public class StaticDispatch {
    static abstract class Human{ }
    static class Man extends Human{}
    static class Woman extends Human{}
    
    public void sayHello(Human guy){
        System.out.println("hello, guy!");
    }
    public void sayHello(Man guy){
        System.out.println("hello, gentleman!");
    }
    public void sayHello(Woman guy){
        System.out.println("hello, lady!");
    }

    public static void main(String[] args){
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(man);
        sr.sayHello(woman);
    }
}

想想以上代码的运行结果是什么?3,2,1,运行结果如下:

hello, guy!
hello, guy!

为什么会出现这样的结果?让我们来看这行代码:

Human man = new Man();

根据里氏替换原则,子类必须能够替换其基类,也就是说子类相对于父类是“具体类”,而父类是处于“奠定”子类的基本功能的地位。

所以,我们把上面代码中的“Human”称为变量man的静态类型(Static Type),而后面的"Man"称为变量的实际类型(Actual Type),二者的区别在于,静态类型是在编译期可知的;而实际类型的结果在运行期才能确定,编译期在编译程序时并不知道一个对象的实际类型是什么。

在了解了这两个概念之后,我们来看看字节码文件是怎么说的:

javac -verbose StaticDispatch.class

我们看到,图中的黄色框的invokespecial指令以及标签,我们可以知道这三个是指令是在调用实例构造器方法。同理,下面两个红色框的invokevirtual指令告诉我们,这里是采用分派调用的调用虚方法,而且入参都是“Human”。

因为在分派调用的时候,使用哪个重载版本完全取决于传入参数的数量和数据类型。而且,虚拟机(准确说是编译期)在重载时是通过参数的静态类型而不是实际类型作为判断依据,并且静态类型是编译期可知的。

所以,在编译阶段,Javac编译期就会根据参数的静态类型决定使用哪个重载版本。重载是静态分派的经典应用。

2. 重写

我们还是用上面的例子:

public class StaticDispatch {
    static abstract class Human{
        protected abstract void sayHello();
    }
    static class Man extends Human{
        @Override
        protected void sayHello() {
            System.out.println("man say hello");
        }
    }
    static class Woman extends Human{
        @Override
        protected void sayHello() {
            System.out.println("woman say hello");
        }
    }
    public static void main(String[] args){
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();
    }
}

其运行结果为:

man say hello
woman say hello

相信你看到这里也会会心一笑,这一看就很明显嘛,重写是按照实际类型来选择方法调用的版本嘛。先别急,我们来看看它的字节码:

嘶…这好像跟静态分派的字节码一样啊,但是从运行结果看,这两句指令最终执行的目方法并不相同啊,那原因就得从invokevirtual指令的多态查找过程开始找起。

我们来看看invokevirtual指令的运行时解析过程的步骤:

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。

  2. 如果在在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。

  3. 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。

  4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

我们可以看到,由于invokevirtual指令在执行的第一步就是在运行期确定接收者的实际类型,所以字节码中会出现invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个就是Java重写的本质。

总结一下,重载的本质是在编译期就会根据参数的静态类型来决定重载方法的版本,而重写的本质在运行期确定接收者的实际类型

继承

假如我们有两个类:生物类、猫类。

生物类:

class Animal{
  private String name;
  public void setName(String name){
    this.name = name;
  }
  public String getName(){
    return this.name;
  }
}

猫类:

class Cat{
  private String name;
    private String sound;
  public void setName(String name){
    this.name = name;
  }
    public void setSound(String sound){
        this.sound = sound;
    }
  public String getName(){
    return this.name;
  }
    public String getSound(){
        return this.sound;
    }
}

我们知道,猫也是属于生物中的一种,生物有的属性和行为,猫按理来说也是有的。但此时没有继承的概念,那么代码就得不到复用,长期发展,代码冗余、维护困难且开发者的工作量也非常大。

继承的概念

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

简单来说,子类能吸收父类已有的属性和行为。除此之外,子类还可以扩展自身功能。子类又被称为派生类,父类被称为超类。

在 Java中,如果要实现继承的关系,可以使用如下语法:

class 子类 extends 父类{}

继承的基本实现

继承的基本实现如下:

class Animal{
  private String name;
  public void setName(String name){
    this.name = name;
  }
  public String getName(){
    return this.name;
  }
}
class Cat extends Animal{}

public class Test{
    public static void main(String[] args){
        Cat cat = new Cat();
        cat.setName("猫");
        System.out.println(cat.getName());
    }
}

运行结果为:

 

我们可以看出,子类可以在不扩展操作的情况下,使用父类的属性和功能

子类扩充父类

继承的基本实现如下:

class Animal{
  private String name;
  public void setName(String name){
    this.name = name;
  }
  public String getName(){
    return this.name;
  }
}
class Cat extends Animal{
    private String sound;
    public void setSound(String sound){
    this.sound = sound;
  }
  public String getSound(){
    return this.sound;
  }
}

public class Test{
    public static void main(String[] args){
        Cat cat = new Cat();
        cat.setName("NYfor2020")
        cat.setSound("我不是你最爱的小甜甜了吗?");
        System.out.println(cat.getName()+":"+cat.getSound());
    }
}

运行结果为:

 

NYfor2020:我不是你最爱的小甜甜了吗?

我们可以看出,子类在父类的基础上进行了扩展,而且对于父类来说,子类定义的范围更为具体。也就是说,子类是将父类具体化的一种手段

总结一下,Java中的继承利用子类和父类的关系,可以实现代码复用,子类还可以根据需求扩展功能。

继承的限制

1. 子类只能继承一个父类

为什么子类不能多继承?举个栗子.

class ACat{
  public void mewo(){...}
}
class BCat{
  public void mewo(){...}
}
class CCat extends ACat, BCat{
  @Override
  public void mewo(){...?} //提问:这里的mewo()是继承自哪个类?
}

虽说Java只支持单继承,但是不反对多层继承呀!

class ACat{}
class BCat extends ACat{}
class CCat extends BCat{}

这样,BCat就继承了ACat所有的方法,而CCat继承了ACat、BCat所有的方法,实际上CCat是ACat的子(孙)类,是BCat的子类。

总结一下,子类虽然不支持多重继承,只能单继承,但是可以多层继承

2. private修饰不可直接访问,final修饰不可修改

private修饰

对于子类来说,父类中用private修饰的属性对其隐藏的,但如果提供了这个变量的setter/getter接口,还是能够访问和修改这个变量的。

class ACat {
    private String sound = "meow";
    public String getSound() {
        return sound;
    }
    public void setSound(String sound) {
        this.sound = sound;
    }
}

class BCat extends ACat {
}

public class Test {
    public static void main(String[] args) {
        BCat b = new BCat();
        b.setSound("我不是你最爱的小甜甜了吗?");
        System.out.println(b.getSound());
    }
}

final修饰

父类已经定义好的final修饰变量(方法也一样),子类可以访问这个属性(或方法),但是不能对其进行更改

class ACat {
    final String sound = "你是我最爱的小甜甜";
    public String getSound() {
        return sound;
    }
    public void setSound(String sound){
        this.sound = sound; //这句执行不了,会报错的
    }
}

class BCat extends ACat {
}

总结一下,用private修饰的变量可以通过getter/setter接口来操作,final修饰的变量就只能访问,不能更改

3. 实例化子类时默认先调用父类的构造方法

在实例化子类对象时,会调用父类的构造方法对属性进行初始化,之后再调用子类的构造方法。

class A {
    public A(){
        System.out.println("我不是你最爱的小甜甜了吗?");
    }
    public A(String q){
        System.out.println(q);
    }
}

class B extends A {
    public B(){
        System.out.println("你是个好姑娘");
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
    }
}

运行结果为:

 

我不是你最爱的小甜甜了吗?
你是个好姑娘

从结果我们可以知道,在实例化子类时,会默认先调用父类中无参构造方法,然后再调动子类的构造方法。

那么怎么调用父类带参的构造方法呢?只要在子类构造方法的第一行调用super()方法就好。

class A {
    public A(String q){
        System.out.println(q);
    }
}

class B extends A {
    public B(){
        super("我是你的小甜甜?");
        System.out.println("你是个好姑娘");
    }
}

public class Test {
    public static void main(String[] args) {
        B b = new B();
    }
}

运行结果为:

 

我是你的小甜甜?
你是个好姑娘

在子类实例化时,默认调用的是父类的无参构造方法,而如果没有父类无参构造方法,则子类必须通过super()来调用父类的有参构造方法,且super()方法必须在子类构造方法的首行

总结一下,Java继承中有三种继承限制,分别是子类只能单继承、父类中private修饰的变量不能显式访问和final修饰的变量不能改变,以及实例化子类必定会先调用父类的构造方法,之后才调用子类的构造方法。

类是怎么加载的?

(此处只是粗略介绍类加载的过程,想了解更多可参考《深入理解Java虚拟机》)

类加载过程包括三个大步骤:加载、连接、初始化

这三个步骤的开始时间仍然保持着固定的先后顺序,但是进行和完成的进度就不一定是这样的顺序了。

1. 加载:虚拟机通过这个类的全限定名来获取这个类的二进制字节流,然后在字节流中提取出这个类的结构数据,并转换成这个类在方法区(存储类结构)的运行时数据结构

2. 验证:先验证这字节流是否符合Class文件格式的规范,然后检查这个类的其父类中数据是否存在冲突(如这个类的父类是否继承被final修饰的类),接着对这个类内的方法体进行检查,如果都没问题了,那就把之前的符号引用换成直接引用

3. 准备:为类变量(static修饰的变量)分配内存(方法区)并设置类变量初始值,而这里的初始值是指这个数据类型的零值,如int的初始值是0;

4. 解析:在Class文件加载过程中,会将Class文件中的标识方法、接口的常量放进常量池中,而这些常量对于虚拟机来说,就是符号引用。此阶段就是针对类、接口、字段等7类符号引用,转换成直接指向目标的句柄——直接引用

5. 初始化:这阶段是执行static代码块和类构造器的过程,有小伙伴可能会疑惑类构造器不是默认static的吗?详情请看这个博客:

 https://www.cnblogs.com/dolphin0520/p/10651845.html

总结一下,类加载的过程中,首先会对Class文件中的类提取并转换成运行时数据结构,然后对类的父类和这个类的数据信息进行检验之后,为类中的类变量分配内存并且设置初始值,接着将Class文件中与这个类有关的符号引用转换成直接引用,最后再执行类构造器。

而且我们可以从第二步看出,在加载类的时候,会先去检查这个类的父类的信息,然后再检查这个类的方法体,也就是说,在加载类的时候,会先去加载它的父类

结语

初学Java的时候知道这些概念,但只是浅尝而止。现在跟着Hollis大佬的《Java 工程师成神之路!》,重新回顾这些知识的时候,发现如果自己只是像以前一样片面了解,那岂不是没有成长?所以在写文章的过程中,尝试写得更加深入且尽量易懂。当然,本人水平有限,如有不正之处,欢迎指正。

坚持写技术文章的确是一件不容易的事情。现在技术更新越来越快,但是依然想把基础再打牢一点。

参考资料:

Java多态性理解

https://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html

从虚拟机指令执行的角度分析JAVA中多态的实现原理

https://www.cnblogs.com/hapjin/p/9248525.html

《深入理解Java虚拟机》

https://blog.csdn.net/wei_zhi/article/details/52780026

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

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

相关文章

常用密码算法介绍

算法种类 根据技术特征,现代密码学可分为三类: 对称算法 说明:加密密钥和解密密钥相同,对明文、密文长度没有限制 子算法: 流密码算法:每次加密或解密一位或一字节的明文或密文 分组密码算法&#xff…

LiveGBS流媒体平台国标GB/T28181功能-国标流媒体服务平台作为上级接入海康大华华为宇视等下级平台及摄像头

LiveGBS国标流媒体服务平台作为上级接入海康大华华为宇视等下级平台及摄像头1、背景说明2、部署国标平台2.1、安装使用说明2.2、服务器网络环境2.3、信令服务配置3、监控摄像头设备接入3.1、海康GB28181接入示例3.2、大华GB28181接入示例3.3、华为IPC GB28181接入示例4、硬件NV…

mysql 存储过程实现从一张表数据迁移到另一种表

通过存储过程迁移数据: 创建表 CREATE TABLE test1 ( idp varchar(255) DEFAULT NULL, brandIdp varchar(255) DEFAULT NULL, namep varchar(1000) DEFAULT NULL, urlp varchar(1000) DEFAULT NULL ) ENGINEInnoDB DEFAULT CHARSETkeybcs2; INSERT INTO t…

2023美国大学生数学建模竞赛(MCM/ICM)报名流程指南

数模乐园作为国内美赛报名最大官方平台,为参加美赛的同学解决国际支付报名难的问题,为同学们省去大部分繁琐流程的同时还附赠纸质证书打印邮寄、美赛赛题解析、美赛专属礼包、赛题翻译等备赛资料 数模乐园已累计为10万同学完成了美赛辅助报名&#xff0…

Android 音视频编解码(三) -- 视频编码和H264格式原理讲解

Android 音视频编解码(一) – MediaCodec 初探 Android 音视频编解码(二) – MediaCodec 解码(同步和异步) 前面学习了 MediaCodec 的基本原理,以及如何解码,在学习MediaCodec 编码之前,先来学习视频是如何编码的,以及最常用的 H2…

亚马逊vs Starday :做跨境电商生意,从哪里开始?

据有关数据统计,中国跨境电商进出口五年增长近十倍,在一众行业面前脱颖而出,成为我国对外贸易新的增长极,然而也正是这样的趋势,使得许多原本从事电商行业的卖家和资本纷纷闻风而动,想要进入市场分一杯羹&a…

3d打印的翘边问题

如何解决3D打印翘边问题 翘边是3D打印中常见的问题之一。为什么在打印的过程中会遇到翘边呢?主要是因为塑料的热胀冷缩,从喷嘴挤出来的塑料在冷却时候会收缩,进而导致模型边缘或者两头翘了起来与平台出现分离。那么如何避免或解决翘边问题呢…

“消费盲返”爆火,一个月能赚1000w?

寒冬已至,疫情还是在断断续续的复发,很多城市也受到严重的影响,封城的通告一出,无疑是给不少的实体企业增添了相当大的噩耗打击,这时候更为磨炼实体企业和创业人看待事情的立场,有些人会觉得疫情的袭来什么…

SSM框架学习记录-SpringBoot_day01

1.SpringBoot简介 SpringBoot是用来简化Spring应用的初始搭建以及开发过程 先回顾一下SpringMVC的开发过程&#xff1a; 创建工程&#xff0c;并在pom.xml配置文件中配置所依赖的坐标&#xff1a; <dependencies><dependency><groupId>javax.servlet</gro…

阶段性回顾(3)

1. 学习指针必须得了解清楚内存&#xff0c;而内存到底是什么东西呢&#xff1f;内存就是电脑上的存储设备&#xff08;除了内存之外&#xff0c;还有硬盘&#xff0c;寄存器等等&#xff09;&#xff0c;那内存到底是来干啥的呢&#xff1f;程序运行的时候会载入到内存当中&am…

Fast Report .NET 2023.1.7-2022-最后版本

通过使用 Fast Report .NET&#xff0c;用户可以构建和创建本质上独立的应用程序以及报表。网。换句话说&#xff0c;这意味着 Fast Report .NET 可以作为所有用户的独立报告工具独立使用。它可以包括一个强大的可视化报告&#xff0c;用于创建和修改报告的过程。用户应用程序可…

Selenium Webdriver 实现原理详解-手工用Postman调用webdriver执行UI测试

目录 1. Selenium 概述 2. 术语解释&#xff1a; 3. Selenium WebDriver 实现原理 4. 安装selenium 客户端&#xff0c;浏览器&#xff0c;驱动 4.1 安装selenium client lib 4.2 安装浏览器和浏览器驱动 4.3 例子代码 4.4 省略浏览器驱动的方法 4.5 测试代码与Webdr…

Linux近期补充

Linux近期补充Linux命令的近期补充Linux命令的近期补充 1.本地服务器链接远端服务器 命令 ssh 远端服务器ip 如 ssh 121.5.151.236 会弹出 登录框 自己输入密码即可2.当前位置 pwd3.查看网络设备 ifconfig4.查看服务器内存 free -h可以看到还有2.3G内存可以用 5.查看磁盘…

ES学习1~23(ECMAcript相关介绍+ECMASript 6新特性)

1 ECMAcript相关介绍 1.1 什么是ECMA ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会&#xff0c;这个组织的目标是评估、开发和认可电信和计算机标准。1994年后该组织改名为Ecma国际。 1.2 什么是ECMScript ECMAScript是由Ecma国际通过…

外网远程访问本地MySQL数据库【cpolar内网穿透】

作为网站运行必备组件之一的数据库&#xff0c;免不了随时对其进行管理维护。若我们没有在安装数据库的电脑旁&#xff0c;但又需要立即对数据库进行管理时&#xff0c;应该如何处理&#xff1f;这时我们可以使用cpolar对内网进行穿透&#xff0c;远程管理和操作MySQL数据库。现…

三叠云甘特图新亮点,可翻页查看数据啦

表单管理 路径 表单 >> 表单设计 功能简介 1.「甘特视图」新增“翻页”功能&#xff0c;用户可以通过翻页查阅更多的数据。 2. 滑动超过显示区域时显示“标记点”&#xff0c;用户可以通过点击标记点快速定位到相应的数据。 3.「列表视图」条件着色功能,修复“系统字…

Linux进程管理

1.什么是程序&#xff1f;具有执行代码和执行权限的文本文件 2.什么是进程&#xff1f;是已启动的可执行程序的运行实例 3.进程的生命周期&#xff1a;由系统程序fork出来的子程序&#xff0c;具备一定的父资源&#xff0c;直到运行完毕 4.进程有哪些组成部分&#xff1f; …

操作系统真相还原_第3章:实模式下跳转指令补充

文章目录数据类型伪指令ret指令call指令jmp指令标志寄存器flags与条件转移数据类型伪指令 byte&#xff1a;字节 word&#xff1a;字 dword&#xff1a;双字 qword&#xff1a;四字 跳转指令指定目标操作数大小 short&#xff1a;字节 near&#xff1a;字 far&#xff1a;双字…

Linux环境下内存泄露检测

linux下内存泄漏检测工具valgrind 该工具可以检测下列与内存相关的问题 : 未释放内存的使用对释放后内存的读/写对已分配内存块尾部的读/写内存泄露不匹配的使用malloc/new/new[] 和 free/delete/delete[]重复释放内存Memcheck。这是valgrind应用最广泛的工具&#xff0c;一个…

vscode插件(个人正在用的)

插件目录any-ruleAuto Close TagAuto Rename Tagbackground-coverChinese (Simplified) (简体中文) Language Pack for Visual Studio CodeDebugger for JavaError LensESLintExtension Pack for JavaImage previewIntelliCodeIntelliCode API Usage ExamplesLanguage Support …