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

🌈 个人主页:十二月的猫-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、《设计模式》、《大话设计模式》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

indexedDB---浏览器本地数据库实现增删改查

关于indexedDB indexedDB的基本使用&#xff0c;可以参考&#xff1a;indexedDB---掌握浏览器内建数据库的基本用法_indexdb浏览器使用-CSDN博客 indexedDB是浏览器本地数据库&#xff0c;既然是数据库就能够实现增删改查&#xff0c;了解了indexedDB的基本使用后&#xff0c…

南信大尹志聪教授为一作在顶级综合性期刊《Natl. Sci. Rev.》发文:传统梅雨停摆,江南缘何不再多烟雨?

文章简介 论文名称&#xff1a;Traditional Meiyu–Baiu has been suspended by global warming 第一作者及单位&#xff1a;尹志聪(教授|南京信息工程大学大气科学学院) 通讯作者及单位&#xff1a;王会军&#xff08;院士|南京信息工程大学大气科学学院&#xff09; 文章发…

茴香豆接入微信个人助手部署

将rag产品接入微信工作群&#xff0c;自动回答问题&#xff0c;香吗&#xff1f;&#xff1f; let‘s go 1、打开openxlab平台&#xff0c;找到茴香豆web产品应用中心-OpenXLab 点击进入&#xff0c;设置知识库名字和密码 2、上传知识库文件和编辑正反例等 3、然后进行测试问答…

探索 LLamaWorker:基于LLamaSharp的.NET本地大模型服务

LLamaWorker 是一个基于 LLamaSharp 项目开发的 HTTP API 服务器。它提供与 OpenAI 兼容的 API&#xff0c;使得开发者可以轻松地将大型语言模型&#xff08;LLM&#xff09;集成到自己的应用程序中。 1. 背景 在人工智能领域&#xff0c;大型语言模型&#xff08;LLM&#xf…

Ruby langchainrb gem and custom configuration for the model setup

题意&#xff1a;Ruby 的 langchainrb gem 以及针对模型设置的自定义配置 问题背景&#xff1a; I am working in a prototype using the gem langchainrb. I am using the module assistant module to implemente a basic RAG architecture. 我正在使用 langchainrb 这个 ge…

如何创建一个vue项目

目录 1.环境准备 2.检查node和npm版本&#xff0c;确定已安装nodejs 3.全局安装vue/cli、webpack、webpack-cli、vue/cli-init 4.检查vue版本,注意V是大写 5.创建vue项目 6.得到的vue项目目录结构如下&#xff1a; 1.环境准备 安装nodejs,或者安装nvm&#xff0c;并使用…

基于盲信号处理的人声分离

1.问题描述 在实际生活中&#xff0c;存在一种基本现象称为“鸡尾酒效应”&#xff0c;该效应指即使在非常嘈杂的环境中&#xff0c;人依然可以从噪声中提取出自己所感兴趣的声音。 在实际应用中&#xff0c;我们可能需要对混合的声音进行分离&#xff0c;此时已知的只有混合…

java的字节符输出流基类、File Writer类和Buffered Writer类

一、字节符输出流基类&#xff1a;Writer 1.属于抽象类 2.常用方法 二、字节符输出流Flie Writer类 1.是writer类的子类 2.以字符为数据处理单元向文本文件中写数据 3.示例 4.实现步骤 三、BufferedWriter类 1.是Writer类的子类。 2.带有缓冲区 默认情况下&#xff0c…

使用 audit2allow 工具添加SELinux权限的方法

1. audit2allow工具的使用 audit2allow 命令的作用是分析日志&#xff0c;并提供允许的建议规则或拒绝的建议规则。 1.1 audit2allow的安装 sudo apt-get install policycoreutilssudo apt install policycoreutils-python-utils 1.2 auditallow的命令 命令含义用法-v--ve…

文件批量重命名001到100 最简单的数字序号递增的改名技巧

文件批量重命名001到100 最简单的数字序号递增的改名方法。最近看到很多人都在找怎么批量修改文件名称&#xff0c;还要按固定的ID需要递增&#xff0c;这个办法用F2或者右键改名是不能做到的。 这时候我们可以通过一个专业的文件批量重命名软件来批量处理这些文档。 芝麻文件…

抖音集团基于 Apache Doris 的实时数据仓库实践

作者&#xff1a;字节跳动数据平台 在直播、电商等业务场景中存在着大量实时数据&#xff0c;这些数据对业务发展至关重要。而在处理实时数据时&#xff0c;我们也遇到了诸多挑战&#xff0c;比如实时数据开发门槛高、运维成本高以及资源浪费等。 此外&#xff0c;实时数据处…

input()函数——输入

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 input()函数可以提示并接收用户的输入&#xff0c;将所有的输入按照字符串进行处理&#xff0c;并返回一个字符串&#xff0c;input()函数的…

调频信号FM的原理与matlab与FPGA实现

平台&#xff1a;matlab r2021b&#xff0c;vivado2023.1 本文知识内容摘自《软件无线电原理和应用》 调频(FM)是载波的瞬时频率随调制信号成线性变化的一种调制方式&#xff0c;音频调频信号的数学表达式可以写为&#xff1a; Fm频率调制&#xff0c;载波的幅度随着调制波形…

LLM文本数据集775TB:覆盖32个领域,444个数据集

大语言模型在各领域展现出巨大潜力&#xff0c;其性能在很大程度上依赖于训练和测试所用的数据集。然而&#xff0c;目前在如何构建和优化这些数据集方面&#xff0c;尚缺乏统一的认识和方法论。下面从五个方面整合和分类了LLM数据集的基本内容&#xff1a;预训练语料库、指令微…

【第14章】探索新技术:如何自学SD3模型(找官方资料/精读/下载/安装/3款工作流/效果测试)ComfyUI基础入门教程

近期,也就是2024年6月12日,StabilityAI开源了最新的SD3模型的2B版本,而神奇的是,ComfyUI早在6月11号就已经适配了SD3!相比之下,SD WebUI 的更新速度却远远落后... 所以,如果想要尝试一些AI绘画领域的新技术,ComfyUI是一个非常值得投入时间学习的工具。 这节课,我们就…

什么是API?如何进行API对接?

目录 一、API和API对接的定义 二、API接口的应用场景 三、为什么需要API对接 四、如何进行API对接 GET请求 POST请求 五、API对接的注意事项 在这个数字化时代&#xff0c;API像一把万能钥匙&#xff0c;让数据流动起来&#xff0c;创造出无限可能。本文旨在介绍API及其…

分享一个 MySQL 简单快速进行自动备份和还原的脚本和方法

前言 数据备份和还原在信息技术领域中具有非常重要的作用&#xff0c;不论是人为误操作、硬件故障、病毒感染、自然灾害还是其他原因&#xff0c;数据丢失的风险都是存在的。如果没有备份&#xff0c;一旦数据丢失&#xff0c;可能对个人、企业甚至整个组织造成巨大的损失。 …

6毛钱SOT-23封装28V、400mA 开关升压转换器,LCD偏置电源和白光LED应用芯片TPS61040

SOT-23-5 封装 TPS61040 丝印PHOI 1 特性 • 1.8V 至 6V 输入电压范围 • 可调节输出电压范围高达 28V • 400mA (TPS61040) 和 250mA (TPS61041) 内部开关电流 • 高达 1MHz 的开关频率 • 28μA 典型空载静态电流 • 1A 典型关断电流 • 内部软启动 • 采用 SOT23-5、TSOT23…

【会议征稿,IEEE出版】第三届机器人、人工智能与智能控制国际会议(RAIIC 2024,7月5-7)

第三届机器人、人工智能与智能控制国际会议&#xff08;RAIIC 2024&#xff09;将于2024年7月5-7日中国绵阳举行。 RAIIC 2024是汇聚业界和学术界的顶级论坛&#xff0c;会议将邀请国内外著名专家就以传播机器人、人工智能与智能控制领域的技术进步、研究成果和应用做专题报告…

呼叫中心项目需要关注什么?

呼叫中心系统项目合作的关键要素可以归纳如下&#xff1a; 1、明确合作目标和需求&#xff1a; 首先&#xff0c;需要明确呼叫中心系统项目的合作目标&#xff0c;例如提高客户满意度、降低成本、提升服务效率等。 同时&#xff0c;需要详细分析项目的具体需求&#xff0c;包括…