面向对象修炼手册(四)(多态与空间分配)(Java宝典)

news2024/12/30 4:06:23

🌈 个人主页:十二月的猫-CSDN博客
🔥 系列专栏: 🏀面向对象修炼手册

💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 

目录

前言 

1 多态

1.1 多态的形式(四种)

1.1.1 重载(专用多态):类型签名区分

签名:

基于类型签名的重载:

1.1.2 改写/重写(包含多态):子父类中,相同类型签名,相同函数名

改写和重载的对比:

改写与遮蔽的对比:

改写机制在不同语言中的区别:

两种不同的改写形式: 

1.1.3 多态变量(复制多态):声明与包含不同

延迟方法:(抽象方法)

多态变量的四种形式: 

1.1.4 泛型(模板) :创建通用工具

概念: 

使用:

1.2 多态运行机制

1.2.1 解释

1.2.2 方法动态绑定过程 

1.2.3 小练习

2 空间分配 

2.1 内存分配方法

2.1.1 静态存储分配

2.1.2  动态存储分配

 2.1.3 堆式存储分配

2.2 内存分配策略 

2.2.1  最小静态空间分配

2.2.2  最大静态空间分配

2.2.3  动态内存空间分配

总结


前言 

前面一讲,我们重点来讲了行为和多态。面向对象修炼手册(三)(行为与多态)(Java宝典)-CSDN博客

在行为中,分为动态行为和静态行为。静态行为和动态行为实施到不同的对象中又构成不同对象,例如静态类型/动态类型;静态类/动态类(静态类是指用于声明变量的类,动态类是指运行时需要动态绑定相关数值的类);静态方法/动态方法。

在多态中,先讲了三种子类父类之间的代码复用情况(三个重),包括重载、重写、重定义。其中重载发生在一个类中,重写发生在父类和子类之间(要求函数名、函数参数都相同),重定义发生在父类和子类之间(要求函数名相同,函数参数不相同)

这一讲,我们从多态的角度再深入分析一下多态,以及内存分配

1 多态

多态、封装和继承是面向对象语言共有的三大特性,其核心思想就是代码复用封装为了是代码复用下的安全(防止复用时修改了原代码),多态和继承就是在封装的前提下要实现代码复用的两大手段

1.1 多态的形式(四种)

1.1.1 重载(专用多态):类型签名区分

  • 重载是在编译时执行的,而改写是在运行时选择的。
  • 重载是多态的一种很强大的形式。
  • 非面向对象语言也支持。
签名:

函数类型签名是关于函数参数类型参数顺序、参数数目返回值类型的描述。

函数签名经常被用在函数重载解析中,因为调用重载的方法从名字上是无法确定你调用的是哪一个方法,而要从你传入的参数该函数的签名来进行匹配,这样才可以确定你调用的是哪一个函数。

基于类型签名的重载:

多个过程(或函数、方法)允许共享同一名称,且通过该过程所需的参数数目顺序类型来对它们进行区分。即使函数处于同一上下文,这也是合法的。

class Example{
    //same name,three different methods
    int sum(int a){return a;}
    int sum(int a,int b){return a+b;}
    int sum(int a,int b,int c){return a+b+c;}
}

关于重载的解析,是在编译时基于参数值的静态类型完成的。不涉及运行时机制。

1.1.2 改写/重写(包含多态):子父类中,相同类型签名,相同函数名

如果子类的方法具有与父类的方法相同的名称和类型签名,称子类的方法改写了父类的方法。

  • 语法上:子类定义一个与父类有着相同名称类型签名相同的方法。
  • 运行时:变量声明为一个类,它所包含的值来自于子类,与给定消息相对应的方法同时出现于父类和子类。

改写替换原则同时出现是代码复用的一个重要手段。不论实际对象是父类还是子类都用父类类型,后续可以在父类类型变量中放入子类/父类,从而使得原代码将父类替换为子类时仍然符合要求。 

改写可看成是重载的一种特殊情况:

  • 接收器搜索并执行相应的方法以响应给定的消息。
  • 如果没有找到匹配的方法,搜索就会传导到此类的父类。搜索会在父类链上一直进行下去,直到找到匹配的方法,或者父类链结束。
  • 如果能在更高类层次找到相同名称的方法,所执行的方法就称为改写了继承的行为
改写和重载的对比:
  • 继承角度:对于改写来说,方法所在的类之间必须符合父类/子类继承关系,而对于简单的重载来说,并无此要求

  • 类型签名角度:如果发生改写,两个方法的类型签名必须匹配

  • 方法作用角度: 重载方法总是独立的,而对于改写的两个方法,有时会结合起来一起实现某种行为

  • 编译器角度: 重载通常是在编译时解析的,而改写则是一种运行时机制。对于任何给定的消息,都无法预言将会执行何种行为,而只有到程序实际运行的时候才能对其进行确定。

  • 静态动态行为:重载解析是静态的,改写解析是动态的

改写与遮蔽的对比:

遮蔽

是指父类变量接收子类类型,并调用方法或者使用变量时候,使用的父类的方法和变量,而不发生多态的现象。

字段遮蔽: 

class Parent {
    int x = 10;
}

class Child extends Parent {
    int x = 20;
}

public class Main {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        Parent parentReferenceToChild = new Child();

        System.out.println("parent.x = " + parent.x); // 输出10
        System.out.println("child.x = " + child.x); // 输出20
        System.out.println("parentReferenceToChild.x = " + parentReferenceToChild.x); // 输出10
    }
}

静态方法遮蔽:

class Parent {
    static void staticMethod() {
        System.out.println("Static method in Parent");
    }
}

class Child extends Parent {
    static void staticMethod() {
        System.out.println("Static method in Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        Parent parentReferenceToChild = new Child();

        parent.staticMethod(); // 输出 "Static method in Parent"
        child.staticMethod(); // 输出 "Static method in Child"
        parentReferenceToChild.staticMethod(); // 输出 "Static method in Parent"
    }
}
改写机制在不同语言中的区别:
  • Java、Smalltalk等面向对象语言,只要子类通过同一类型签名改写父类的方法,自然便会发生所期望的行为。
  • C++中,需要父类中使用关键字Virtual来表明这一含义(否则会发生遮蔽
两种不同的改写形式: 
  • 代替(replacement):在程序执行时,实现代替的方法完全覆盖父类的方法。即,当操作子类实例时,父类的代码完全不会执行。
  • 改进(refinement):实现改进的方法将继承自父类的方法的执行作为其行为的一部分。这样父类的行为得以保留且扩充.
    • java中使用super.方法名()
    • c++中可以使用 _super::方法名() 或者 使用 父类名::方法名()

建议都是使用改进语义 

1.1.3 多态变量(复制多态):声明与包含不同

多态变量是指可以引用多种对象类型的变量。

这种变量在程序执行过程可以包含不同类型的数值。

对于动态类型语言,所有的变量都可能是多态的。例如python中变量可以放任何对象

对于静态类型语言,多态变量则是替换原则的具体表现。使用多态变量出现了自然就有替换原则

例如:Parent variable=new Child();

延迟方法:(抽象方法)

如果方法在父类中定义,但并没有对其进行实现,那么我们称这个方法为延迟方法

优点:
可以使程序员在比实际对象的抽象层次更高的级别上考虑与之相关的活动。

实际意义:
在静态类型面向对象语言中,对于给定对象,只有当编译器可以确认与给定消息选择器相匹配的响应方法时,才允许程序员发送消息给这个对象。

延迟方法有时也称为抽象方法,并且在C++语言中通常称之为纯虚方法

多态变量的四种形式: 

1、简单多态变量(继承+替换原则)(最原始的多态)

Animal pet;
pet = new Dog();
pet.speak();

 2、接收器变量(内部接口或者父类指向对象声明但未初始化)

多态变量作为一个数值,表示正在执行的方法内部的接收器。包含接收器的变量没有被正常的声明,通常被称为伪变量。

伪变量:

C++,Java,C#:this

class ThisExample{
	public void one(int x){
		value=x+4;
		two(x+3);
	}
	private int value;
	private void two(int y){
		System.out.println(“Value is”+(value+y));
	}
}

等价于:

class ThisExample{
	public void one(int x){
		this.value=x+4;
		this.two(x+3);
	}
	private int value;
	private void two(int y){
		System.out.println(“Value is”+(this.value+y));
	}
}

这个this就是在方法内部指定接收器本身的, 并且任何对象都用这个this指定接收器本身,所以this是多态变量

3、 向下造型(反多态)

向下造型这个变量本质上是取消多态赋值的过程(将父类强制转化为子类,然后赋值给子类)

Father f1 = new Son();

Son s1 = (Son)f1;

但有运行出错的情况:

Father f2 = new Father();

Son s2 = (Son)f2;//编译无错但运行会出现错误

在不确定父类引用是否指向子类对象时,可以用instanceof来判断:

if(f3 instanceof Son){

     Son s3 = (Son)f3;

}

4、纯多态(我的多态依靠其他函数的多态实现)

  • 多态方法支持可变参数的函数。

  • 支持代码只编写一次、高级别的抽象

  • 以及针对各种情况所需的代码裁剪。

  • 通常是通过给方法的接收器发送延迟消息来实现这种代码裁剪的。

延迟消息:延迟实现的函数,在使用函数中并不实现。因此延迟消息一改变,使用延迟消息的函数也会发生变化。因此这个延迟消息本身就是一种多态方法,即使用延迟消息的函数本身也就实现多态。

Class Stringbuffer{
	String append(Object value){
		return append(value,toString());}
   …
}

方法toString在子类中得以重定义。

toString方法的各种不同版本产生不同的结果。

所以append方法也类似产生了各种不同的结果。

Append:一个定义,多种结果。

1.1.4 泛型(模板) :创建通用工具

概念: 

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)
进一步理解,就是我们方法的实现有延迟,这个泛型对方法实现中的参数类型也让它延迟实现。在方法实现中并不直接传入具体的参数,而是在运行时实现。从另一个角度来说,它是动态类型实现的参数。

使用:

泛型类:

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());

 更重要的是,如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型(编译器自己根据传入的类型决定泛型类型)

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

Log.d("泛型测试","key is " + generic.getKey());
Log.d("泛型测试","key is " + generic1.getKey());
Log.d("泛型测试","key is " + generic2.getKey());
Log.d("泛型测试","key is " + generic3.getKey());

泛型接口:

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

//定义一个泛型接口
public interface Generator<T> {
    public T next();
}
/**
 * 传入泛型实参时:
 * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
 * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

泛型方法:

 在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。(重点区分:泛型类中的普通方法和泛型类中的泛型方法

public class GenericTest {
   //这个类是个泛型类,在上面已经介绍过
   public class Generic<T>{     
        private T key;

        public Generic(T key) {
            this.key = key;
        }

        //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey(){
            return key;
        }

        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
        public E setKey(E key){
             this.key = keu
        }
        */
    }

    /** 
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个 
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //当然这个例子举的不太合适,只是为了说明泛型方法的特性。
        T test = container.getKey();
        return test;
    }

    //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

    //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?
    //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
    public void showKeyValue2(Generic<?> obj){
        Log.d("泛型测试","key value is " + obj.getKey());
    }

     /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
    public <T> T showKeyName(Generic<E> container){
        ...
    }  
    */

    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
    public void showkey(T genericObj){

    }
    */

    public static void main(String[] args) {


    }
}

总之就是,不是方法中出现T就是泛型方法。必须要在public和返回值之间增加一个<T>

本质来说,泛型方法的作用就是让方法和类的返回值/参数类型独立化了,可以不同

 具体使用如下:

public class GenericFruit {
    class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple是Fruit的子类,所以这里可以
        generateTest.show_1(apple);
        //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
        //generateTest.show_1(person);

        //使用这两个方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //使用这两个方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

泛型方法能使方法独立于类而产生变化,以下是一个基本的指导原则:

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

 泛型数组:

List<String>[] ls = new ArrayList<String>[10];  // x
List<?>[] ls = new ArrayList<?>[10];  //√
List<String>[] ls = new ArrayList[10];//√

1.2 多态运行机制

多态机制的运行是基于”方法绑定“ 。由于多态中的重写、重载、多态变量、泛型的存在,很多方法/变量需要延迟绑定来实现。想要实现延迟绑定就需要”方法绑定“机制来决定和哪个方法绑定(不能考编译器根据函数签名直接决定)

1.2.1 解释

  • Java多态机制是基于“方法绑定(binding”,就是建立method call(方法调用)method body(方法本体)的关联。如果绑定动作发生于程序执行前(由编译器和连接器完成),称为“先期绑定”。对于面向过程的语言它们没有其他选择,一定是先期绑定。比如C编译器只有一种method call,就是先期绑定。(C++有先期联编和后期联编)
  • 当有多态的情况时,解决方案便是所谓的后期绑定(late binding:绑定动作将在执行期才根据对象型别而进行。后期绑定也被称为执行期绑定(run-time binding动态绑定(dynamic binding
  • Java的所有方法,只有finalstaticprivate和构造方法是前期绑定,其他都使用后期绑定。 将方法声明为final型可以有效防止他人覆写该函数。但是或许更重要的是,这么做可以“关闭”动态绑定。或者说,这么做便是告诉编译器:动态绑定是不需要的。于是编译器可以产生效率较佳的程序代码。

1.2.2 方法动态绑定过程 

  1. 编译器检查对象的声明类型和方法名。假设我们调用x.f(args)方法,并且x已经被声明为C类的变量,那么编译器会列举出C类中所有的名称为f的方法和从C类的超类继承过来的f方法
  2. 接下来编译器检查方法调用中提供的参数类型。如果在所有名称为f 的方法中有一个参数类型和调用提供的参数类型最为匹配,那么就确定调用这个方法( 重载解析) 
  3. 当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同x所指向的对象的实际类型相匹配的方法版本。假设实际类型为D(C的子类),如果D类定义了f(args)那么该方法被调用,否则就在D的超类中搜寻方法f(args),依次类推

1.2.3 小练习

 代码如下:

public class  Bird{
	public void  fly(Bird   p) 
 {System.out.println(“Bird fly with Bird”);}
}
public class Eagle extends Bird {
	public void fly(Bird   p)
 {System.out.println(“Eagle fly with Bird!”);}
	public void fly(Eagle e) 
 {System.out.println(“Eagle fly with Eagle!”);}
}
Bird p1 = new Bird () ; 
Bird  p2 = new Eagle () ; 
Eagle p3 = new Eagle () ; 
p1.fly(  p1  ) ;
p1.fly(  p2  ) ;
p1.fly(  p3  ) ;
p2.fly(  p1  ) ;
p2.fly(  p2  ) ;
p2.fly(  p3  ) ;
p3.fly(  p1  ) ;
p3.fly(  p2  ) ;
p3.fly(  p3  ) ;
 

运行结果为:

 关键点:

1、编译阶段根据调用函数的对象类型进行静态绑定——确定函数名字及函数签名,但是不确定函数是否在子类中被重写(也就说并没有真正绑定一个函数)(检查语法正确性也在这一阶段)

2、运行阶段动态绑定方法,这个动态绑定是基于第一步中确定的函数名和函数签名进行的。只是根据调用函数的对象实际类型(子类还是父类),来实际选择和父类还是子类绑定

2 空间分配 

2.1 内存分配方法

内存分配方法是指程序是用什么方法去请求内存分配的

2.1.1 静态存储分配

 静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间

这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求

2.1.2  动态存储分配

也被称为栈式存储分配,它是由一个类似于堆栈的运行栈来实现的。

和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存。

栈式存储分配按照先进后出的原则进行分配。

 2.1.3 堆式存储分配

堆式存储分配专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。

堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。

2.2 内存分配策略 

研究的是这门语言遇到类/方法等内存分配时采用的是什么策略

2.2.1  最小静态空间分配

  • C++使用最小静态空间分配策略,运行高效。

  • 只分配基类所需的存储空间。

    为了防止采用这种策略时因为多态而引发的程序错误(具体参照P179),C++改变了虚拟方法的调用规则:

  • 对于指针 / 引用变量:当信息调用可能被改写的成员函数时,选择哪个函数取决于接收器的动态数值。

  • 对于其他变量:调用虚拟成员函数的方式取决于静态类,而不取决于动态类

2.2.2  最大静态空间分配

无论基类还是派生类,都分配可用于所有合法的数值的最大的存储空间。

这一方案不合适,因为需要找到最大的对象,就需要对继承树上的所有对象都进行扫描,然后找到需要分配最大内存的对象才能

2.2.3  动态内存空间分配

  • 堆栈中不保存对象值。
  • 堆栈通过指针大小空间来保存标识变量,数据值保存在堆中。
  • 指针变量都具有恒定不变的大小,变量赋值时,不会有任何问题。

只分配用于保存一个指针所需的存储空间。在运行时通过对来分配指针对应对象所需的存储空间,同时将指针设为相应的合适值。

总结

本系列内容均来自:山东大学-潘丽老师-面向对象开发技术-课程ppt、《设计模式》、《大话设计模式》

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

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

相关文章

鉴权开发框架Django REST framework的应用场景

目录 一、鉴权开发框架介绍二、Django REST framework是什么三、如何实现认证、权限与限流功能四、Django REST framework的应用场景 一、鉴权开发框架介绍 鉴权开发框架是一种用于实现身份验证和授权的软件开发工具。它可以帮助开发者快速构建安全、可靠的身份验证和授权系统…

喜报!莱佛士学生斩获2024年AIFW新兴设计师奖

近日&#xff0c;2024年东盟国际时装周&#xff08;AIFW&#xff09;在新加坡滨海湾金沙艺术科学博物馆隆重举行。 在这场时尚盛宴中&#xff0c;莱佛士服装设计专业的学生Sonpavarapong SASIPIM&#xff08;Hunny&#xff09;以其出色的设计才华和独特的设计风格脱颖而出&…

二维到三维的华丽转身:3D培训大师点亮汽车维修课件制作的立体新篇章

随着汽车技术的迅猛发展与新能源汽车的快速普及&#xff0c;汽车维修领域亦呈现出日新月异的革新态势。传统的汽车维修培训通常是通过以下两种方式开展&#xff1a;在专业的汽车维修学校进行系统培训和在汽车维修企业或修理站进行实地培训。一般来说&#xff0c;后者往往以前者…

C语言 指针——从函数返回字符串

目录 从函数返回字符指针 编程实现strcat()的功能 小结 从函数返回字符指针 编程实现strcat()的功能 小结 明确字符串被保存到了哪里&#xff0c;明确字符指针指向了哪里  指向字符串常量的字符指针  指向字符数组的字符指针 向函数传递字符串的方法  向函数传…

解决java中时间参数的问题

在java的日常开发中&#xff0c;我们经常需要去接收前端传递过来的时间字符串&#xff0c;同时给前端返回数据时&#xff0c;也会涉及到时间字段的数据传递&#xff0c;那么我们要如何处理这些字段呢&#xff1f; 知识铺垫&#xff1a;java最后返回的时间是时间世界&#xff0…

春秋云境:CVE-2022-25411[漏洞复现]

根据题目提示和CNNVD优先寻找后台管理地址 靶机启动后&#xff0c;使用AWVS进行扫描查看网站结构 在这里可以看到后台管理的登录地址&#xff1a;/admin/&#xff0c;根据题目提示可知是弱口令 尝试admin、123456、admin666、admin123、admin888...等等常见弱口令 正确的账户…

Yolo v5实现细节

Yolo v5实现细节 SiLU激活函数 swish和SiLU激活函数:其中β是常量或者是可学习的参数 首先引入swish函数的表达形式&#xff1a; f ( x ) x ⋅ sigmoid ⁡ ( β x ) f(x)x \cdot \operatorname{sigmoid}(\beta x) f(x)x⋅sigmoid(βx) 如果β 1即SiLU激活函数: f ( x ) x …

Linux 网络:网卡 promiscuous 模式疑云

文章目录 1. 前言2. 问题场景3. 问题定位和分析4. 参考资料 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. 问题场景 调试 Marvell 88E6320 时&#xff0c;发现 eth0 出人意料的进入了 promis…

Linux源码阅读笔记05-进程优先级与调度策略-实战分析

基础知识 Linux 内核当中有 3 种调度策略&#xff1a; SCHED_OTHER 分时调度策略&#xff1b;SCHED_FIFO 实时调度策略&#xff0c;先到先服务&#xff1b;SCHED_RR 实时调度策略&#xff0c;时间片轮转。 如果有相同优先级的实时进程&#xff08;根据优先级计算的调度权值是…

centos上快速搭建zfile文件网站

什么是zfile&#xff1f; zfile文件网站是最方便快捷的在线目录展示程序&#xff0c;支持将本地文件、FTP、SFTP、S3、OneDrive 等存储在网站上展示并浏览&#xff01; 本教程参考&#xff1a; https://docs.zfile.vip/install/os-linux复现 今天的搭建环境是centos7.9 第一…

vscode中的字符缩进问题

问题描述&#xff1a; 如图当一行代码中出现不同类型的字符时&#xff0c;使用tab缩只是插入了固定数量&#xff08;默认4&#xff09;的空格或制表符&#xff0c;仍然无法对齐。 解决方法&#xff1a; vscode找到设置&#xff0c;搜索fontFamily&#xff0c;对应输入框写入mon…

Linux下安装搜狗拼音不能显示中文?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

这5款国内可用的宝藏AI视频工具,不允许有人还不知道!(建议收藏)

文章首发于公众号&#xff1a;X小鹿AI副业 大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 前几天一位粉丝说给…

海外短剧系统:一扇窥探多元文化的奇妙之窗

随着全球化的推进&#xff0c;文化交流的壁垒逐渐消融&#xff0c;我们得以更加便捷地领略到世界各地的独特风情。在这一背景下&#xff0c;海外短剧系统应运而生&#xff0c;它如同一扇扇虚拟的窗户&#xff0c;将我们带入不同国家、不同民族、不同文化的世界&#xff0c;让我…

Navicat Premium Lite绿色免费版

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Navicat Premium Lite概述 Navicat 最近推出了一款名为 Navicat Premium Lite 的免费数据库管理开发工具&#xff0c;专为入门级用户设计。这款工具虽然在功能上与 Navicat…

内网安全【4】SSH隧道技术

1.四大隧道协议 (1)SMB协议 判断&#xff1a;445端口是否开放 (2)ICMP协议 判断&#xff1a;ping命令能通说明使用icmp协议 (3)DNS协议 判断&#xff1a;nslookup www.baidu.com 属于UDP iodine工作原理是 &#xff0c;通过TAP虚拟网卡&#xff0c;在服…

北方银行 - HDFS 现代化快速案例研究

故事很重要&#xff0c;客户故事是最好的。他们提供令人瞠目结舌的统计数据或克服巨大障碍的那些是获得最佳头条新闻的那些。它们也是最难发表的。我们知道&#xff0c;因为我们将与您分享一些我们正在孜孜不倦地努力出版的内容 - 但现在它们将保持匿名。话虽如此&#xff0c;如…

Java:从嵌入式到云时代的编程语言传奇

Java&#xff0c;自1995年面世以来&#xff0c;已成为全球最受欢迎的编程语言之一。起初&#xff0c;Sun Microsystems公司为了解决嵌入式设备编程的难题&#xff0c;开发了Oak语言&#xff0c;这便是Java的前身。随着互联网的兴起&#xff0c;Oak更名为Java&#xff0c;并迅速…

11.异常(java版)

异常的概念 在日常开发时 代码在程序运行过程中 难免会出现一些其奇奇怪怪的问题 有时通过代码很难去控制 比如&#xff1a;数据格式不对、网络不流畅、内存报警等 在Java中 将程序执行过程中发生的不正常行为称为异常 比如我们之前写代码时经常会遇到的&#xff1a; 1.算术…

tldraw白板组件

tldraw 是一个开源的白板组件&#xff0c;10行代码就可以将其接入到 React 项目中&#xff1a; import { Tldraw } from tldraw import tldraw/tldraw.cssexport default function App() {return (<div style{{ position: fixed, inset: 0 }}><Tldraw /></div&…