Java——继承(Inheritance)

一、继承简要介绍

1、继承是什么

在Java中,继承是一种面向对象编程的重要特性,它允许一个类(子类或派生类)继承另一个类(父类或基类)的属性和方法。继承的目的是实现代码的重用和设计的层次化。

子类通常被称为派生类(Derived Class),而父类则被称为超类(Super Class)或基类(Base Class)。

2、继承语法

使用关键字 extends 来实现继承。子类可以继承父类的非私有成员(属性和方法)。、

class ChildClass extends ParentClass {// 子类可以添加自己的属性和方法,也可以重写父类的方法
}

3、为什么需要继承

我们可以先创建两个类,一个猫类,一个狗类:

class Cat {public String name;public int age;public double weight;public void meow() {System.out.println("Meow~");}}class Dog {public String name;public int age;public double weight;public void barking() {System.out.println("Woof~");}}

可以发现这两个类中除了两个方法不同,其他的属性都是相同的,这样就造成了大量重复代码。继承就是用来解决这个问题的。

下面我们将猫类和狗类都继承动物类:

class Animal {public String name;public int age;public double weight;public void showName() {System.out.println(name);}}class Cat extends Animal {public void meow() {System.out.println("Meow~");}
}class Dog extends Animal {public void barking() {System.out.println("Woof~");}
}

对于 Animal 的两个子类 Cat 和 Dog 都继承了父类的属性和方法,它们也可以有各自的特有的属性和方法。

4、继承的特点

  1. 子类继承父类的属性和方法: 在Java中,子类可以继承父类中所有的属性和方法。但私有属性和方法只能在父类内部访问,子类无法直接访问。如果需要,可以通过公共方法间接访问父类的私有成员。

  2. 子类的扩展: 子类不仅可以继承父类的成员,还可以定义自己的属性和方法。这允许子类在保持父类功能的基础上进行扩展,添加新的功能或修改已有功能的行为。

  3. 方法重写(Override): 子类可以重写(override)从父类继承的方法,以提供特定于子类的实现。重写的方法必须具有相同的签名(方法名、参数列表和返回类型),并且通常遵循里氏替换原则,即子类对象应该能够替换其父类对象而不影响程序的正确性。

  4. 单继承与多重继承: Java支持单继承,即一个类只能直接继承自一个父类。这与C++不同,C++支持多重继承,即一个类可以继承自多个直接父类。然而,Java通过接口(interface)实现了多重继承的某些方面,一个类可以实现多个接口。

  5. 耦合性: 继承确实可以增加类之间的耦合性,因为子类依赖于父类的结构和行为。这可能导致代码的灵活性和可维护性降低。为了减少这种耦合,设计时应该遵循良好的面向对象设计原则,如使用组合优于继承,以及遵循依赖倒置原则等。

5、单继承和多重继承

1)单继承

单继承是指一个类只能直接继承自另一个类。在Java中,这是支持的

2)多层继承

多层继承是指一个类继承自另一个类,而后者又继承自另一个类,形成一个继承链。在Java中,这也是支持的

3)多重继承

多重继承是指一个类可以同时继承自多个类。在Java中,类不支持多重继承,即一个类不能直接继承自两个或两个以上的类。

这样写就会报错:

这是为了避免潜在的复杂性和歧义,例如“钻石问题”(当两个父类有相同的方法时,子类应该继承哪一个?)。

然而,Java通过接口(Interface)支持了多重继承的概念。一个类可以实现多个接口,从而获得多重继承的某些好处,同时避免了多重继承的复杂性。

二、继承细节

1、子类继承父类所有属性和方法

子类继承父类的所有属性和方法,但是子类不能直接访问父类的私有属性和方法。

我们可以通过调试来观察,确定子类确实继承了父类的所有属性:

package com.pack1;public class Test {public static void main(String[] args) {Sub sub = new Sub();}
}class Base {public int a;protected int b;int c;private int d;}class Sub extends Base {}

这个例子中,父类 Base 有四种访问修饰符修饰的属性,Sub 子类继承自 Base 父类,然后我们在 main 函数中创建一个 Sub 类的对象 sub,然后进行调试,当我们调试到对象 sub 创建成功后,对 sub 对象进行监视,可以看到以下列表:

可以发现这里 Sub 这个子类的对象 sub 是有父类 Base 的四个属性的,即使 d 是 private 修饰的变量,这个子类对象也是继承了的,只不过这个子类对象不能直接访问这个 private 修饰的属性,需要通过公共的方法进行访问。

对于子类不能直接访问父类的 private 修饰的属性,这一点我们在之前的文章Java——访问修饰符中提到过,我们说到 private 修饰的属性和方法只能在同一个类中访问,是不能在其他类中直接访问的。

这里谈到的是在同一个包中的情况,因为在不同包中,父类中默认的访问修饰符修饰的属性和方法也是不能在子类中被访问的。

2、子类构造器必须调用父类构造器

当创建一个子类的对象时,子类的构造器会隐式或显式地调用父类的构造器来完成父类的初始化。这是Java继承机制的一部分,确保父类的状态在子类对象创建时得到正确初始化。

如果子类的构造器没有显式调用父类的构造器,Java编译器会自动插入对父类默认构造器(无参构造器)的调用,也就是 super(); 这个语句。如果父类没有定义默认构造器(无参构造器),或者你想调用父类的某个特定构造器,你需要在子类构造器中显式地使用super()关键字来指定调用父类的某个构造器。

下面给出例子:

class Base {public int data;public Base() {System.out.println("父类构造器被调用");}
}class Sub extends Base {public Sub() {System.out.println("子类构造器被调用");}
}

这里我们给父类写了一个无参构造器,子类也写了一个无参构造器,但是子类中没有显式调用父类的构造器,这样我们创建一个子类对象

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

运行后得到的结果为:

可以发现子类的构造器中虽然没有显式的调用父类的构造器,但是 Java 编译器自动插入了 super() ,以调用我们写的父类的无参构造器,这样在子类构造器被调用时,父类构造器也会被调用,以确保父类的状态在子类对象创建时得到正确初始化。

3、子类构造器默认调用父类无参构造器

当创建子类对象时,不管使用子类的那个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类构造器中使用 super() 指定使用父类的那个构造器,否则就会编译不通过。

package com.pack1;public class Test {public static void main(String[] args) {System.out.println("sub1使用子类构造器Sub()创建");Sub sub1 = new Sub();System.out.println("sub2使用子类构造器Sub(double bar)创建");Sub sub2 = new Sub(2.2);}
}class Base {public int data;public Base() {System.out.println("父类构造器Base()被调用");}public Base(int data) {this.data = data;System.out.println("父类构造器Base(int data)被调用");}
}class Sub extends Base {public double bar;public Sub() {System.out.println("子类构造器Sub()被调用");}public Sub(double bar) {this.bar = bar;System.out.println("子类构造器Sub(double bar)被调用");}
}

运行结果:

可以发现我们的子类中的两个构造器都没有显式使用 super() 调用父类的构造器,虽然父类中有两个构造器,但是子类中的两个构造器都是默认调用了父类的无参构造器(默认构造器)。

如果我们将父类中的无参构造器去掉,这样运行就会报错:

package com.pack1;public class Test {public static void main(String[] args) {System.out.println("sub1使用子类构造器Sub()创建");Sub sub1 = new Sub();System.out.println("sub2使用子类构造器Sub(double bar)创建");Sub sub2 = new Sub(2.2);}
}class Base {public int data;/*public Base() {System.out.println("父类构造器Base()被调用");}*/public Base(int data) {this.data = data;System.out.println("父类构造器Base(int data)被调用");}
}class Sub extends Base {public double bar;public Sub() {System.out.println("子类构造器Sub()被调用");}public Sub(double bar) {this.bar = bar;System.out.println("子类构造器Sub(double bar)被调用");}
}

运行结果:

这是因为在子类构造器中如果没有显式使用 super() 指定调用父类的构造器,则子类构造器的第一行默认有一句话:

super();

也就是默认调用父类的无参构造器,但是如果父类没有无参构造器的话,这一句就是会报错的。

如果父类类没有无参构造器或者我们想在子类中调用指定的父类构造器,就可以使用 super() 来指定调用父类的构造器:

package com.pack1;public class Test {public static void main(String[] args) {Sub sub = new Sub(1);Sub sub1 = new Sub(1,2.2);}
}class Base {public int data;/*public Base() {System.out.println("父类构造器Base()被调用");}*/public Base(int data) {this.data = data;System.out.println("父类构造器Base(int data)被调用");}
}class Sub extends Base {public double bar;public Sub(int data) {super(data);System.out.println("子类构造器Sub(int data)被调用");}public Sub(int data, double bar) {super(data);this.bar = bar;System.out.println("子类构造器Sub(int data, double bar)被调用");}
}

运行结果:

4、所有类都是 Object 类的子类

在Java中,所有的类都直接或间接地继承自java.lang.Object类。这里我们以 java.util.Arrays 类为例,可以发现 Arrays 这个类也是继承自 Object 类:

如果你在定义一个类时没有使用extends关键字来指定它继承自哪个类,那么这个类将默认继承自Object类。

也就是说如果我们定义了一个类:

public class MyClass {// 类的内容
}

就相当于:

public class MyClass extends Object {// 类的内容
}

一般情况下我们都是用上面的第一种的写法,不用显式指定某个类继承自 Object 类。

所有的类都直接或间接地继承自java.lang.Object类,这意味着Object类中定义的方法,如toString(), equals(Object obj), hashCode(), getClass(), clone(), finalize()等,都可以被任何Java类所使用,因为它们是继承自Object类的。

5、子类构造器对父类构造器的调用会追溯到 Object 类的构造器

对于 Sub 类是继承自 Base 类,Base 类是继承自 Object 类,所以当我们调用 Sub 类的构造器,必然会调用 Base 类的构造器,调用 Base 类的构造器必然会调用 Object 类的构造器。这种调用关系会一直追溯到 Object 类的构造器。

三、继承本质分析

1、创建一个子类对象在内存中的分布

用以下代码为例,分析创建了一个 C 类对象后,这个对象在内存中的分布(可以发现这几个类是在同一个包中的,访问修饰符是默认,在同一个包中都能被访问):

package com.pack1;public class Test {public static void main(String[] args) {C c = new C();}
}class A {String name = "AAA";double weight;
}class B extends A {String name = "BBB";int age;
}class C extends B {String name = "CCC";
}

可以看到继承关系为:

下面我们详细解释在内存中的布局。

首先在对象 c 还没有创建时,首先要加载类信息,因为是创建 C 类的对象,加载某个类信息前会查找这个类的父类,直到找到这个类的顶级父类 Object 才停止,然后从 Object 依次加载父类信息直到加载到本类信息。这个很像递归加载类信息。

这样说,也就是依次加载 Object 类信息,A 类信息,B 类信息,最后才是 C 类信息。所以在一个类信息加载完成后,它的父类信息也加载完成了:

然后就会对这个对象 c 进行创建:

对于对象 c 会拥有父类所有属性,这里的属性的访问修饰符都是默认,就算是父类中 private 修饰的属性也是会被子类继承的,只不过子类没法直接访问到 private 修饰的父类属性,上面我们已经对这个讲解过了。对象 c 有父类所有属性,而且它们在内存中是连续的。

在上图,可以看到对象 c 的属性被分为了三块,分别是 A 类属性,B 类属性,C 类属性。A 类属性中有父类 A 类中的所有属性,B 类属性中有父类 B 类中的所有属性,C 类属性中有这个子类 C 中所有的属性,这样对象 c 是拥有所有父类属性以及本类的所有属性的。

A 类部分

name = "AAA",weight = 0.0 (默认值)

B 类部分

name = "BBB"(B 类重新定义了 name,隐藏了 A 类中的 name),age = 0(默认值)

C 类部分

name = "CCC"(C 类重新定义了 name,隐藏了 B 类中的 name

可以看到三个类中都有 name 这个变量,尽管 C 类中存在三个 name 属性,但每个类的 name 属性是独立的。如果在 C 类中访问 name,将优先访问最近的 name 属性,即 C 类中的 name

我么可以对上面的代码进行调试,

可以发现:这三个 name 变量是互相独立的。

2、对属性的访问规则

那么从这个案例中,我们就要考虑一下,如果我们想要访问对应的 name,那么访问规则又是什么呢?

1)直接访问

(1)查看子类是否有该属性,如果子类有该属性且可以访问,则使用子类的这个属性。

(2)如果子类没有该属性,就看父类有没有该属性,如果父类有该属性且可以访问,则使用父类的这个属性。

(3)如果当前父类没有该属性,则接着向上面的父类按规则(2)寻找,直到 Object 类再停止。如果最终没有找到该属性,则报错。

如果一旦找到了这个属性,但这个属性无法访问,就不再向上寻找,会报错。例如:

package com.pack1;public class Test {public static void main(String[] args) {C c = new C();System.out.println(c.age);}
}class A {String name = "AAA";double weight;int age;
}class B extends A {String name = "BBB";private int age;
}class C extends B {String name;
}

就像这里我们通过子类对象访问 age 属性,但是子类 C 类中没有 age 属性,向上面的父类寻找,在父类 B 类中寻找到 private 修饰的 age 属性,这里显然无法访问,虽然上面的父类的父类即 A 类中有可访问的 age 属性,也不会向上找了,在 B 类这里遇到私有的 age 属性就会报错。

例子:

package com.pack1;public class Test {public static void main(String[] args) {C c = new C();System.out.println(c.name);}
}class A {String name = "AAA";double weight;
}class B extends A {String name = "BBB";int age;
}class C extends B {String name = "CCC";}

运行结果:

这个例子中,我们在 main 方法中使用对象 c 对属性 name 进行访问,在子类 C 中就能直接找到 name 属性,并且可以直接访问,则使用子类 C 的 name 属性,子类 C 的属性 name 的默认值为 "CCC",所以这里的运行结果是上面那样。

2)强制访问

通过强制类型转换访问 ((B)c).name

  • 访问 B 类中的 name
  • 输出 "BBB"。

通过强制类型转换访问 ((A)c).name

  • 访问 A 类中的 name
  • 输出 "AAA"。
package com.pack1;public class Test {public static void main(String[] args) {C c = new C();System.out.println("强制类型转换访问B类中的name属性:" + ((B)c).name);System.out.println("强制类型转换访问A类中的name属性:" + ((A)c).name);}
}class A {String name = "AAA";double weight;
}class B extends A {String name = "BBB";int age;
}class C extends B {String name = "CCC";}

运行结果:

这里的强制类型转换我们可以类比 C 语言中的将 int* 类型变量强制转换为 char* 从而可以只访问到 int* 指针变量指向的区域的第一个字节。

对于 main 方法中的 c 变量的本质是对象引用变量:

C c = new C();

它的本质也是类似于指针的,它指向的是一个在堆区的 C 类对象,因为 C 类对象应当拥有父类所有属性和本类所有属性,所以 C 类对象的结构应当是下面这样:

所以这个对象引用变量 c 是可以访问到 A 类、B 类和 C 类三个类(这里其实还有 Object 类,这里暂时忽略)的所有属性的,所以通过 c.name 访问 name 属性,则遵循的规则就是上面的直接访问的规则,从子类 C 中依次向上寻找这个属性。

对于 B 类对象,应当有它的父类所有属性和本类属性,所以 B 类对象的在堆区的结构应该是这样:

所以对于对象 c 强制转换成 B 类对象的引用变量后,(B)c 就只能访问到 A 类和 B 类两个类(这里其实还有 Object 类,这里暂时忽略)的所有属性,这样就可以实现访问到 B 类的 name 属性了,因为在这里使用:

((B)c).name

就只能从 B 类开始向上寻找 name 属性了,这里 B 类中就有可访问的 name 属性,所以就直接访问 B 类的 name 属性了,这里就是强制类型转换实现强制访问特定的类的属性的原理。

对于 A 类对象,应当有它的父类所有属性和本类属性,所以 A 类对象的在堆区的结构应该是这样:

所以对于对象 c 强制转换成 A 类对象的引用变量后,(A)c 就只能访问到 A 类一个类(这里其实还有 Object 类,这里暂时忽略)的所有属性,这样就可以实现访问到 A 类的 name 属性了,因为在这里使用:

((A)c).name

就只能从 A 类开始向上寻找 name 属性了,这里 A 类中就有可访问的 name 属性,所以就直接访问 A 类的 name 属性了。

可以看到这里是使用 c 对象的引用变量对对象 c 的多个不同类中的 name 属性进行访问,那如果我们在子类中想要访问父类中 name 属性,当然我们可以使用 super,那如果要访问父类的父类的 name 属性呢,就可以使用强制转换 this 的方法:

package com.pack1;public class Test {public static void main(String[] args) {C c = new C();c.printName();}
}class A {String name = "AAA";double weight;
}class B extends A {String name = "BBB";int age;
}class C extends B {String name = "CCC";public void printName() {System.out.println(name);//这里访问的是最近的name属性,C类的name属性System.out.println(((B)this).name);//这里访问的是B类的name属性System.out.println(((A)this).name);//这里访问的是A类的name属性}
}

运行结果:

以上谈论的基础是 C 类对这些属性有访问权限,如果父类的属性是 private 修饰的,使用强制类型转化也是访问不到的,只能通过公共方法获取。如果访问修饰符是默认,访问的地方与这个属性所在的类不在同一个包中也是无法访问的,也只能通过公共方法获取。

四、super

super 的语法与 this 很类似。

1、super 访问父类属性或方法

在Java中,super关键字是一个引用变量,用于指向当前类的父类。super可以用来访问父类的成员,包括属性和方法,这在子类中非常有用,尤其是当子类和父类中有同名的成员时。

1)super 访问父类属性

使用 super 关键字可以在同一个包中访问父类的非私有属性,在不同包中只能访问父类的 public 和 protected 修饰的属性。

package com.pack1;public class Test {public static void main(String[] args) {Sub sub = new Sub();sub.printName();}
}class Base {String name = "Base";
}class Sub extends Base {String name = "Sub";public void printName() {System.out.println("父类name:" + super.name);System.out.println("子类name:" + name);}
}

可以发现这里子类和父类都有 name 属性,我们可以使用 super.name 访问父类的 name 属性。

运行结果:

2)super 访问父类方法

使用 super 关键字可以在同一个包中访问父类的非私有方法,在不同包中只能访问父类的 public 和 protected 修饰的方法。

package com.pack1;public class Test {public static void main(String[] args) {Sub sub = new Sub();sub.greeting();}
}class Base {public void sayHi() {System.out.println("hi~");}
}class Sub extends Base {public void greeting() {super.sayHi();}
}

运行结果:

这里我们使用 super 关键字对父类方法进行调用。

2、super() 调用父类构造器

上面我们对此已经介绍的很详细了,这里就简单介绍一下。

子类中应当调用父类构造器以初始化继承至父类的属性,使用 super() 就是为了指定调用父类的构造器。如果子类中没有显式调用父类构造器,Java 编译器会自动在子类构造器的第一句加上:

super();

以默认调用父类的无参构造器,但是这样需要确保父类有无参构造器。如果父类没有无参构造器或者我们想自己指定调用特定的父类构造器,就需要使用 super() 传入特定的参数来调用特定的父类构造器。

super() 这个语句必须是子类构造器中的第一行非注释语句。

五、方法重写 / 覆盖(Override)

1、什么是方法重写

重写(Override)是指子类重新定义父类中已有的方法。重写是实现多态的一种方式,允许子类提供一个特定于自己的方法实现。

1. 基本概念

  • 重写方法:子类中定义一个与父类中方法签名(方法名、参数列表)相同的方法。
  • 目的:子类可以根据自己的需求重新实现父类的方法。

2. 重写的规则

  • 方法签名必须相同:子类方法的名称和参数列表必须与父类方法完全相同。
  • 返回类型:子类方法的返回类型可以与父类的返回类型一样,也可以是父类方法返回类型的子类型(例如父类返回 Object 类型,子类可以返回 String 类型)。
  • 访问权限:子类方法的访问权限不能比父类方法更严格。例如,如果父类方法是public,子类方法也必须是public
  • 异常:子类方法抛出的异常不能比父类方法抛出的异常更宽泛。子类方法可以不抛出异常,或者抛出父类方法抛出异常的子类异常。

3. 使用@Override注解

  • 注解:在子类方法上使用@Override注解可以明确表示这是一个重写方法。这有助于编译器检查方法签名是否正确,避免拼写错误等问题。

2、方法重写的示例

package com.pack1;public class Test {public static void main(String[] args) {Dog dog = new Dog();dog.makeSound();}
}class Animal {public void makeSound() {System.out.println("Animal sound...");}
}class Dog extends Animal {public void makeSound() {System.out.println("woof...");}
}

运行结果:

这里的 Dog 类中的 makeSound 方法就重写了父类 Animal 中的 makeSound 方法。

六、补充

1、this() 和 super() 的使用都必须是第一行

上面我们提到了 super() 必须是子类构造器的第一句非注释语句,在之前的文章中,我们提到在同一个类中调用其他构造器要使用 this() 语句,this() 语句也应该是构造器的第一句非注释语句。

但是如果我们在一个子类中的构造器中使用 this() 调用另一个构造器,从而使没有地方供 super() 调用,致使父类状态无法正常初始化呢。就像下面这样:

class Base {public Base() {}
}class Sub extends Base {public Sub() {}public Sub(int a) {this();}
}

当我们调用 Sub 的一个参数的构造器时,父类构造器是否是没有被调用呢?

实际上父类构造器是被调用了的,因为我们使用 this() 调用 Sub 的无参构造器时,Sub 的无参构造器中会被编译器默认加上 super(),所以这样就会间接调用父类构造器,完成父类状态初始化。

class Base {public Base() {}
}class Sub extends Base {public Sub() {//编译器默认加上 super();}public Sub(int a) {this();}
}

那有没有可能 Sub 子类中的两个构造器中都使用了 this() 语句呢,就像下面这样:

class Base {public Base() {}
}class Sub extends Base {public Sub() {this(2);}public Sub(int a) {this();}
}

这样就没有地方加 super() 来调用父类构造器了,这样就不能正常初始化父类的状态了。

实际上这里也是不可以的,因为构造器不能递归调用,这是错误的。

这里会报错:

上面说构造器不能递归调用,所以当我们只有一个构造器时,也不能使用 this() 语句来调用这个构造器。

class Base {public Base() {}
}class Sub extends Base {public Sub() {this();}
}

这里也会报错:

由上可知,当我们使用 this() 调用其他构造器时,必定有两个以上构造器,而且至少有一个构造器是没有使用 this() 语句的,这个构造器中就可以有 super() 来调用父类构造器(如果没有显式调用,编译器会自动加上 super(); 这个语句,用来调用父类无参构造器)。

其他构造器中最终就必定可以追溯到这个没有使用 this() 语句的构造器,所以这样所有的构造器都直接或间接的调用了父类构造器。

2、子类中对父类对象的访问补充

当子类有和父类属性和方法重名的属性或方法时,为了访问父类的成员,必须通过 super 关键字;如果子类中没有与父类重名的属性或方法,则使用 super、this或直接访问访问父类的属性或方法是一样的效果。 

package com.pack1;public class Test {public static void main(String[] args) {Sub sub = new Sub();sub.show();}
}class Base {public String name = "Base";
}class Sub extends Base {public void show() {System.out.println(name);System.out.println(this.name);System.out.println(super.name);}
}

运行结果:

这里子类中使用了三种方法访问 name 这个属性,这个属性是从父类 Base 中继承的,子类 Sub 中没有与 name 重名的属性,所以第一种方法使用直接访问就是根据上面我们提到的“对属性的访问规则”中的直接访问的规则来从子类中寻找,因为子类 Sub 中没有 name 这个属性,然后向上面的父类中寻找,在父类中寻找到,而且可以访问,所以就是使用父类中的这个 name 属性;第二种方法是使用 this 访问,这样就是对 Sub 类的对象的属性进行访问,这样的访问规则与上面的直接访问的规则是一致的,最终也是在父类中找到 name 这个属性,然后使用这个属性;第三种方法是使用 super 访问,因为 name 这个属性本就是从父类中继承的,使用 super 访问是完全可以的,对于 super 的查找顺序是跳过本类直接从父类开始查找,如果父类查找不到,接着向上面找父类的父类,直到 Object 类。

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

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

相关文章

LT8711GX 国产芯片 Type-C/DP1.4转HDMI2.1 用于加密狗 对接站

描述 LT8711GX是一款高性能的Type-C/DP1.4到HDMI2.1转换器,设计用于将USBType-C源或DP1.4源连接到HDMI2.1收发器。 该LT8711GX集成了一个符合DP1.4标准的接收器和一个符合HDMI2.1标准的发射器。此外,还包括一个CC控制器,用于CC通信&#xff0…

使用kali Linux启动盘轻松破解Windows电脑密码

破解分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。谢谢!! 效果展示: 使用kali Linux可以轻松破解Windows用户及密码 准备阶段: &#xff08…

ASP.NET Core 使用Log4net

1. Nuget安装log4net&#xff0c;图里的两个 2.项目根目录下添加log4net.config.添加下面的代码: <?xml version"1.0" encoding"utf-8"?> <configuration><!-- This section contains the log4net configuration settings --><log…

C语言之常用内存函数以及模拟实现

目录 前言 一、memcpy的使用和模拟实现 二、memmove的使用和模拟实现 三、memset的使用和模拟实现 四、memcmp的使用和模拟实现 总结 前言 本文主要讲述C语言中常用的内存函数&#xff1a;memcpy、memmove、memset、memcmp。内容不多&#xff0c;除了了解如何使用&#x…

细说MCU的ADC模块单通道连续采样的实现方法

目录 一、工程依赖的硬件及背景 二、设计目的 三、建立工程 1、配置GPIO 2、选择时钟源和Debug 3、配置ADC 4、配置系统时钟和ADC时钟 5、配置TIM3 6、配置串口 四、代码修改 1、重定义TIM3中断回调函数 2、启动ADC及重写其回调函数 3、定义用于存储转换结果的数…

一个pdf分割成多个pdf,一个pdf分成多个pdf

在数字化办公和学习中&#xff0c;pdf格式因其良好的兼容性和稳定性而受到广泛欢迎。但有时候&#xff0c;我们可能需要将一个大的pdf文件分割成多个小文件&#xff0c;以便于分享、打印或编辑。今天&#xff0c;我就来教大家几种简单有效的方法&#xff0c;让你轻松实现pdf文件…

基于Wireshark和TiWsPC(Wireshark Packet Converter)的Zigbee抓包

前言 介绍几种Zigbee抓包方式&#xff1a; 1. Ubiqua 使用教程网上非常多也非常清晰&#xff1b; 但是Ubiqua是收费软件&#xff0c;较贵&#xff1b; 我安装过了&#xff0c;费好多事&#xff0c;没安装成功。 2. Killerbee套件 https://github.com/riverloopsec/killerbe…

WACV2023论文速览域迁移Domain相关

Paper1 CellTranspose: Few-Shot Domain Adaptation for Cellular Instance Segmentation 摘要原文: Automated cellular instance segmentation is a process utilized for accelerating biological research for the past two decades, and recent advancements have produc…

Ad-hoc命令和模块简介

华子目录 Ad-hoc命令和模块简介1.概念2.格式3.Ansible命令常用参数4.模块类型4.1 三种模块类型4.2Ansible核心模块和附加模块 示例1示例2 Ad-hoc命令和模块简介 1.概念 Ansible提供两种方式去完成任务&#xff0c;一是ad-hoc命令&#xff0c;一是写Ansible playbook(剧本)Ad-…

【电商纯干货分享】干货速看!电商数据集数据API接口数据分析大全!

数据分析——深入探索中小企业数字化转型&#xff0c;专注提供各行业数据分析干货、分析技巧、工具推荐以及各类超实用分析模板&#xff0c;为钻研于数据分析的朋友们加油充电。 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09…

02浅谈大模型文本生成的背后逻辑

02浅谈大语言模型文本生成的背后逻辑 两个概念&#xff1a; 通俗理解大模型文本生成逻辑 假设有一个prompt&#xff1a;How are you &#xff1f;&#xff0c;输入给大模型&#xff0c;那么大模型使怎么输出&#xff1f;

uni-app x 跨平台开发框架

目录 uni-app x 是什么 和Flutter对比 uts语言 uvue渲染引擎 组合式API的写法 选项式API写法 页面生命周期 API pages.json全局配置文件 总结 uni-app x 是什么 uni-app x&#xff0c;是下一代 uni-app&#xff0c;是一个跨平台应用开发引擎。 uni-app x 是一个庞…

ETAS工具导入Com Arxml修改步骤

文章目录 前言Confgen之前的更改Confgen之后的修改CANCanIfComComMEcuM修改CanNmCanSMDCMCanTp生成RTE过程报错修改DEXT-诊断文件修改Extract问题总结前言 通讯协议栈开发一般通过导入DBC实现,ETAS工具本身导入DBC也是生成arxml后执行cfggen,本文介绍直接导入客户提供的arxml…

动态颤抖的眼睛效果404页面源码

动态颤抖的眼睛效果404页面源码&#xff0c; 源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 动态颤抖的眼睛效果404页面源码

springboot+vue+mybatis图书馆借阅管理系统+PPT+论文+讲解+售后

21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存储达到…

AI大模型时代的存储发展趋势

从2022年下半年&#xff0c;大模型和AIGC这两个词变得极其火热&#xff0c;而GPU的市场也是一卡难求。对于这种迷乱和火热&#xff0c;让我想起了当年的比特币挖矿和IPFS。似乎世界一年一个新风口&#xff0c;比特币、元宇宙、NFT、AIGC&#xff0c;金钱永不眠&#xff0c;IT炒…

IIS数字功放MAX98357开发板/评估系统

前言 MAX98357中文介绍请访问下行链接 MAX98357、MAX98357A、MAX98357B小巧、低成本、PCM D类IIS放大器&#xff0c;具有AB类性能中文说明规格书 一般描述 MAX98357 开发板&#xff08;DEV 板&#xff09;是一个完全组装并经过测试的 PCB&#xff0c;用于评估 MAX98357 I2S …

C++怎么解决不支持字符串枚举?

首先&#xff0c;有两种方法&#xff1a;使用命名空间和字符串常量与使用 enum class 和辅助函数。 表格直观展示 特性使用命名空间和字符串常量使用 enum class 和辅助函数类型安全性低 - 编译器无法检查字符串有效性&#xff0c;运行时发现错误高 - 编译期类型检查&#xf…

2024 年 6 月区块链游戏研报:Pixels 引发 DAU 波动,行业用户留存率差异显著

作者&#xff1a;Stella L (stellafootprint.network) 数据来源&#xff1a;区块链游戏研究页面 2024 年 6 月&#xff0c;加密货币市场遭遇显著回调&#xff0c;比特币跌幅达 7.3%&#xff0c;以太坊更是下跌了 9.8%。此番波动不可避免地波及区块链游戏领域&#xff0c;导致…

Hudi 写入流程(图)

前言 主要为之前总结的源码文章补充流程图。总结一下整体流程说明 之前以Java Client为例,总结了 Insert 源码的整体流程及部分源码,由于各种原因,没有总结完。长时间不看这方面的源码,容易忘记,之前没有总结流程图,现在回忆起来比较麻烦,不如看流程图方便快捷。所以先补…