匿名对象、匿名内部类、多线程(上),学习分享

匿名对象

匿名对象的使用场景:

  1. 当使用对象调用方法只调用一次的时候可以使用。
  2. 可以作为参数传递,但是在传递之前无法做其他操作。比如说给成员变量赋值

使用匿名对象调用方法:

  1. 学生类:
package com.SiyualChen.Demo01;

public class Student {
    private  String name;
    private int age;

   
    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void study(){
        System.out.println("SiyualChen需要好好学习");
    }

  1. 测试类:
package com.SiyualChen.Demo01;

public class Test01 {
    public static void main(String[] args) {
        //创建学生对象,使用学生对象调用方法
        Student stu = new Student();
        stu.study();
        //使用匿名对象调用方法
        new Student().study();

    }

运行结果:

SiyualChen需要好好学习
SiyualChen需要好好学习

Process finished with exit code 0

匿名对象作为参数传递:

package com.SiyualChen.Demo01;

import com.SiyualChen.Demo01.Student;

public class AnonymityObjectDemo01 {
    public static void main(String[] args) {
        //创建学生对象,将学生对象作为参数传递
        Student stu  = new Student();
        showPrint(stu);
        //使用匿名对象调用方法
        showPrint(new Student());

    }
    public static void showPrint(Student stu){
        stu.study();
    }
}

运行结果:

SiyualChen需要好好学习
SiyualChen需要好好学习

Process finished with exit code 0

匿名内部类

什么是内部类
将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类。其他类也称为外部类。

package com.SiyualChen.Demo02;

public class Car {  //外部类
    class engine{  //内部类

    }
}

成员内部类
定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问

  • 定义格式
package com.SiyualChen.Demo02;

public class 外部类 {  //外部类
    class 内部类{  //内部类
    		//代码块
    }
}
  • 访问方式
    外部类名.内部类名 变量名 = new 外部类名().new 内部类名();

成员内部类可以使用的修饰符:private,public,procted,final,static,abstract
代码-内部类访问外部类的成员
代码-成员内部类可以直接访问外部类所有的成员,包括私有的!

package com.SiyualChen.Demo02;

public class Outer {
    private String name ="Outer";
    public void outerMethod(){
        System.out.println("Outer method");
    }

    //成员内部类定义在类中成员位置
    public class Inner{
        public void innerMethod(){
            //访问外部类成员变量
            System.out.println(name);
            //访问外部类的成员方法
            outerMethod();

        }
    }
}

测试类:

package com.SiyualChen.Demo02;

public class Test01 {
    public static void main(String[] args) {
        //外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
        Outer.Inner oi = new Outer().new Inner();
        //调用内部方法
        oi.innerMethod();
    }
}

运行结果:

Outer
Outer method

Process finished with exit code 0

代码-外部类访问内部类的成员
外部类访问内部类成员 需要创建内部类对象,使用内部类对象访问.可以适用对象访问所有的内部类成员.包括私有的!

package com.SiyualChen.Demo02;

public class Outer {
    public static void main(String[] args) {
        Inner inner = new Inner();
        //调用私有成员参数
        System.out.println(inner.name +" "+inner.age);
        //调用内部类的成员方法
        inner.innerMethod();
        
    }
    public static class Inner{
        private  String name ="Inner";
        int age =22;
        public void innerMethod(){
            System.out.println("InnerMethod");
        }
    }
}

运行结果
Inner 22
InnerMethod

Process finished with exit code 0

局部内部类
定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问

  • 定义格式
class 外部类 { 
  //外部类方法
	修饰符 返回值类型 方法名(参数) {
		class 内部类 {
			//其他代码
		}
	}
}
  • 访问方式
    在外部类方法中,创建内部类对象,进行访问
package com.SiyualChen.Demo02.Demo02;

public class Outer {
    public static void main(String[] args) {
        class  Inner{
            private  String name ="SiyualChen";
            public void innerMethod(){
                System.out.println("InnerMethod");
            }
        }
        //创建内部类的对象
        Inner inner = new Inner();
        //通过局部内部类的对象可以访问成员属性,包括私有的
        System.out.println(inner.name);
        inner.innerMethod();
    }
}

运行结果:

SiyualChen
InnerMethod

Process finished with exit code 0

匿名内部类
为了方便大家记忆我们可以将匿名内部类看作一个没有名字的局部内部类

  • 作用
    匿名内部类是创建某个类型子类对象的快捷方式。
  • 格式
new 类或接口(){
	//进行方法重写
};

代码演示

package com.SiyualChen.Demo02.Demo03;
//定义一个父类,
public abstract class Animal {
    public abstract void eat();
}
package com.SiyualChen.Demo02.Demo03;

public class Test {
    public static void main(String[] args) {
        Animal a = new Animal() {
            @Override
            public void eat() {
                System.out.println("eating....");
            }
        };
        a.eat();
    }
}

运行结果:

eating....

Process finished with exit code 0

案例一
用匿名对象的方式使用匿名内部类

package com.SiyualChen.Demo02.Demo03;
//定义一个父类,
public abstract class Animal {
    public abstract void eat();
}
package com.SiyualChen.Demo02.Demo03;

public class Test {
    public static void main(String[] args) {
        //使用匿名对象的方式调用
        new Animal() {
            @Override
            public void eat() {
                System.out.println("eating....");
            }
        }.eat();
    }
}

运行结果:

eating....

Process finished with exit code 0

案例二
将匿名内部类对象作为参数传递

package com.SiyualChen.Demo02.Demo03;

public class Test {
    public static void main(String[] args) {
        //使用匿名内部类的方式创建当前类的对象 赋值给父类Animal
        Animal a = new Animal() {
            @Override
            public void eat() {
                System.out.println("eating....将对象作为参数传递");
            }
        };
        //将对象作为参数传递
        eatPrint(a);
        //使用匿名对象的方式传递
        eatPrint(new Animal() {
            @Override
            public void eat() {
                System.out.println("eating...使用匿名对象的方式传递");
            }
        });



    }
    //定义一个方法
    public static void eatPrint(Animal a){
        a.eat();
    }
}

运行结果:

eating....将对象作为参数传递
eating...使用匿名对象的方式传递

Process finished with exit code 0

多线程

线程与进程

  1. 进程
    正在进行的程序。它是内存中的一段独立的空间,可以负责当前应用程序的运行。每一个进程都有它自己的内存空间和系统资源。进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  2. 线程
    在一个进程中,每个独立的功能都需要独立的去运行,这时又需要把当前这个进程划分成多个运行区域,每个独立的小区域(小单元)称为一个线程。

创建线程方式一

  1. 自定义一个类,继承Thread,这个类称为线程类;
  2. 重写Thread类中的run方法,run方法中就是线程要执行的任务代码;
  3. 创建线程类的对象;
  4. 启动线程,执行任务;

代码实现:

package com.SiyualChen.Demo02.Demo04;

public class MyThread extends Thread{
    /*需求:演示:实现多线程方式1:继承Thread。
 * 实现多线程步骤:
 * 	A:定义一个类继承Thread
 *  B:复写Thread类中的run函数
 *  C:创建线程类的对象
 *  D:启动线程,执行任务
 */
    //复写Thread类中的run函数
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i+"one");
        }

    }


}
package com.SiyualChen.Demo02.Demo04;

public class MyThreadDemo01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        //D:启动线程,执行任务
        /*
         * 这里我们启动线程之后,由于MyThread类是一个线程,main函数是一个线程,这里有两个线程
         * 并且两个线程中都输出0~10000,那么根据线程的特点,如果多个线程执行任务,那么CPU执行哪个线程是随机的,
         * 并且不固定,所以这里的打印结果0~10000应该输出一部分MyThread类中的,然后在输出main函数中的,输出的
         * 内容不固定,这样才对
         * run函数中只是执行线程任务的代码,所以这里打印的结果是按顺序打印的,和我们之前调用一般函数并没有区别。
         * 那么怎么能够启动线程呢?
         * 通过查阅API得知需要调用start()函数才能够启动线程
         */
        //但是如果使用线程类对象直接调用run函数,其实这里并没有启动线程,因为启动线程要调用系统资源,开辟内存空间
        //mt.run();
        mt.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println(i+"two");
        }
    }
}

运行结果:

2711two
2712two
2713two
2714two
2715two
2716two
7826one
7827one
7828one
7829one
7830one
7831one
等.....
几个疑问

1)为什么要继承Thread类?

在Java中使用Thread这个类对线程进行描述。而我们现在希望通过自己的代码操作线程,自己的代码应该需要和Thread类之间产生关系。这里我们采用的继承的关系。

当我们继承了Thread类之后,我们自己的类也就变成了线程类。我们自己的类就继承到了Thread类中的所有功能,就具备了操作线程的各种方法,并且自己的类就可以对线程进行各种操作(开启线程等)。

而我们自己定义的类称为了一个线程类,主要是因为可以复写run方法。

2)为什么要复写run方法?

为什么要使用线程:因为我们希望程序中的某段代码可以同时运行,提高程序的运行效率。

我们定义的类继承了Thread类之后,其实在Thread类中有个run方法,它是开启线程之后,就会直接去运行的方法。而Java在设计线程类(Thread)的时候,就已经明确了线程应该执行的某段代码需要书写在run方法中,也就是说在run方法中的代码开启线程之后才能正常的运行。

我们使用线程的目的是让线程执行后来自己程序中的某些代码, 而Java中规定需要线程执行的代码必须写run方法中,Thread类中的run方法中并没有我们真正需要多线程运行的代码,而开启线程又要去运行run方法,这时我们只能沿用Thread类run方法的定义格式,然后复写run方法的方法体代码。

简单来讲:

​ 设计Thread这个API的人,在设计的时候,只设计了如何启动线程,至于线程要执行什么任务,他并不知道。所以,他这样设计:就是start启动线程之后,JVM会自动的调用run方法。

因此,我们只要把自己的代码写到run方法中,就一定会被执行到。

3)为什么要调用start而不是run?

当书写了一个类继承了Thread类之后,这个子类也变成线程类。这时可以创建这个子类的对象,一旦创建Thread的子类对象,就相当于拥有了当前的线程对象。

创建Thread的子类对象,只是在内存中有了线程这个对象,但是线程还不能真正的去运行。

要让线程真正的在内存运行起来,必须调用start方法,因为start方法会先调用系统资源,启动线程。这样才能够在内存开启一片新的内存空间,然后负责当前线程需要执行的任务。

我们直接通过线程对象去调用run方法,这时只是对象调用普通的方法,并没有调用系统资源,启动线程,也没有在内存中开启一个新的独立的内存空间运行任务代码。只有调用start方法才会开启一个独立的新的空间。并在新的空间中自动去运行run方法。

注意:run方法仅仅是封装了线程的任务。它无法启动线程。

面试题:start方法和run方法的区别?

​ run:只是封装线程任务。

​ start:先调用系统资源,在内存中开辟一个新的空间启动线程,再执行run方法。

4)同一个线程对象是否可以多次启动线程?

不能。如果同一个对象多次启动线程就会报如下异常。

注意:如果想要启动多个线程,可以重新再创建一次自定义线程类的对象调用一次start()函数来启动线程。

OK 相信我们都看到多线程的现象了,那么接下来几天我们就进入多线程的世界!

获取、设置线程名称

通过以上代码的结果,我们发现只能打印出结果,但是不知道是哪个线程打印的,所以接下来我们需要知道是哪些线程打印的结果,那么我们就得先获取线程的名字,然后才能知道是哪些线程打印的结果。

问题一、那么如何获得线程的名字呢?

要想获得当前线程的名字,首先必须先获得当前线程的对象,然后根据当前线程对象调用线程类Thread类中的方法 String getName()就可以获取当前线程的名称。

问题二、如何获取当前线程的对象?
在Thread类中提供一个函数可以返回当前正在执行的线程的对象,这个函数是:

static Thread currentThread()返回对当前正在执行的线程对象的引用。 

说明:这个函数是静态函数,直接通过这个函数所属的类名Thread直接调用即可。

补充:既然有获取线程的名字的函数,那么在Thread类中肯定还会有给线程设置名字的函数,setName(String name)。

void setName(String name) 改变线程名称,使之与参数 name 相同。

需求:演示:获取和设置线程的名称。

分析和步骤:

1)代码和上述代码一致,在run函数中书写getName()函数获取线程的名字;

2)在测试类中分别使用自定义类的线程对象给线程设置新的名字;

代码如下所示:

package com.SiyualChen.Demo02.Demo05;

public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            //获取线程名称
            System.out.println(Thread.currentThread().getName()+" "+i);

        }
    }
}
package com.SiyualChen.Demo02.Demo05;

public class ThreadDemo02 {
    public static void main(String[] args) {
        //线程1
        MyThread2 mt = new MyThread2();
        mt.start();
        //线程2
        MyThread2 mt2 = new MyThread2();
        mt2.start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println(i);
        }
    }

}

运行结果:

Thread-0 965
Thread-0 966
Thread-0 967
Thread-0 968
Thread-0 969
Thread-0 970
Thread-0 971
Thread-0 972
等...

通过以上结果发现多线程是有默认名字的,即Thread-x x是从0开始递增数字。

但是如果我们想给多线程设置我们自己想要的名字,就不会使用默认名字,可以通过Thread类中的setName()进行设置。

代码如下所示:

package com.SiyualChen.Demo02.Demo05;

public class ThreadDemo02 {
    public static void main(String[] args) {
        //线程1
        MyThread2 mt = new MyThread2();
        //修改线程名称
        mt.setName("mt1");
        mt.start();
        //线程2
        MyThread2 mt2 = new MyThread2();
        //修改线程名称
        mt2.setName("mt2");
        mt2.start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println(i);
        }
    }

}

运行结果:

mt1 989
mt1 990
mt1 991
mt1 992
mt1 993
mt1 994
mt1 995
mt1 996
等....

线程创建方式二
通过之前的学习我们得知实现多线程的第一种方式是继承Thread类,那么第二种方式是什么呢?
API描述:

创建线程的另一种方法是声明一个实现Runnable接口的类。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

通过以上API得知,第二种方式是创建一个类来实现Runnable接口,并实现Runnable接口中的run函数。

实现步骤

A:自定义类,实现Runnable接口,这个类就是任务类;
B:实现run方法,run函数中书写的任务代码;
C:创建任务类的对象;
D:创建Thread类对象,并且把任务对象作为参数传递;
构造方法:

  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
package com.SiyualChen.Demo02.Demo06;
//自定义类,实现Runnable接口,这个类就是任务类;
public class MyWork implements Runnable {
    @Override
    public void run() {
    //实现run方法,run函数中书写的任务代码;
        for (int i = 0; i <1000 ; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            
        }
    }
}
package com.SiyualChen.Demo02.Demo06;

public class ThreadDemo01 {
    public static void main(String[] args) {
        MyWork work = new MyWork();
        Thread t1 = new Thread(work,"Siyual");
        Thread t2 = new Thread(work,"Chen");
        t1.start();
        t2.start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

运行结果:

Siyual 0
Chen 0
Siyual 1
Chen 1
Siyual 2
Chen 2
Siyual 3
Chen 3
Siyual 4
Chen 4
Siyual 5
关于调用run方法的疑问

方式1:自定义类继承Thread类,重写run方法,调用start启动线程,会自动调用我们线程类中的run方法。

方式2:自定义类,实现Runnable接口,把任务对象传给Thread对象。调用Thread对象的start方法,执行Thread的run。那么为什么最后执行的是任务类中的run呢?

方式二的好处

A:避免了Java单继承的局限性;

说明:如果使用方式一,那么在Java中一个类只能有一个直接父类,如果一个类已经继承其他的父类,那么当前这个类中假如有需要多线程操作的代码,这时这个类是无法再继承Thread类的。这样就会导致当前这个类中的某些需要多线程执行的任务代码就无法被线程去执行。

B:把线程代码和任务的代码分离,解耦合(解除线程代码和任务的代码模块之间的依赖关系)。代码的扩展性非常好;

说明:Thread类是专门负责描述线程本身的。Thread类可以对线程进行各种各样的操作。如果使用第一种方式,那么把线程要执行的任务也交给了Thread类。这样就会导致操作线程本身的功能和线程要执行的任务功能严重的耦合在一起。

但是方式二,自定义一个类来实现Runnable接口,这样就把任务抽取到Runnable接口中,在这个接口中定义线程需要执行的任务的规则。当需要明确线程的任务时,我们就让这个类实现Runnable接口,只要实现Runnable接口的类,就相当于明确了线程需要执行的任务。

​ 当一个类实现Runnable接口,就相当于有了线程的任务,可是还没有线程本身这个对象。这时我们就可以直接使用Thread这个类创建出线程,然后把任务交给线程。这样就达到任务和线程的分离以及结合。
匿名内部类方式实现线程的创建

使用线程的匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

使用匿名内部类的方式实现Runnable接口,重新复写Runnable接口中的run方法。

package com.SiyualChen.Demo02.Demo07;

public class Demo01 {
    public static void main(String[] args) {
        //使用匿名内部类实现多线程 r表示任务类的对象
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);

                }
            }
        };
        Thread t1 = new Thread(r,"t1");
        Thread t2 = new Thread(r,"t2");

        t1.start();
        t2.start();
    }

}

运行结果:

t2 367
t2 368
t2 369
t1 780
t1 781
t1 782
t1 783
t1 784
t1 785
t2 370
t2 371
t2 372
package com.SiyualChen.Demo02.Demo07;

public class Demo01 {
    public static void main(String[] args) {
        //第一种创建线程的方式
        //1.创建一个类继承Thread
        //2.重写run方法
        //3.创建Thread继承类的对象
        //4.调用start方法
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i <10 ; i++) {
                    System.out.println(getName() + " 正在执行");
                }
            }
        }.start();


        //Runnable方法
        //1.创建一个类实现Runnable接口
        //2.实现run方法
        //3.创建实现类对象
        //4.将实现类对象作为参数创建Thread对象
        //5.启动线程 调用start方法
        //使用匿名内部类实现多线程 r表示任务类的对象
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);

                }
            }
        };
        Thread t1 = new Thread(r,"t1");
        Thread t2 = new Thread(r,"t2");

        t1.start();
        t2.start();
    }

}

线程控制
使用Thread类中的sleep()函数可以让线程休眠,函数如下所示:

static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

说明:这个函数是静态的,使用线程类名调用。使用哪个线程调用就让哪个线程休眠。

代码如下所示:

分析和步骤:

1)创建一个测试类SleepDemo ,并添加main函数;
2)创建一个线程任务类SleepTask 来实现Runnable接口,并实现run函数,打印0到9十个数字,并且使用Thread类调用sleep()函数让当前线程睡一秒,并使用Date类的对象来获得睡眠的时间;
3)在main函数中创建线程任务类对象st;
4)创建线程类对象t.
5)启动线程;

package com.SiyualChen.Demo02.Demo08;



import java.util.Date;

public class SleepTask implements  Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() +" "+i +new Date());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
package com.SiyualChen.Demo02.Demo08;

public class SleepDemo {
    /**
     * 1)创建一个测试类SleepDemo ,并添加main函数;
     *
     * 2)创建一个线程任务类SleepTask 来实现Runnable接口,
     * 并实现run函数,打印0到1000个数字,并且使用Thread类调用sleep()
     * 函数让当前线程睡一秒,并使用Date类的对象来获得睡眠的时间;
     *
     * 3)在main函数中创建线程任务类对象st;
     *
     * 4)创建线程类对象t,
     * @param args
     */
    public static void main(String[] args) {
        SleepTask st = new SleepTask();
        Thread t = new Thread(st);
        t.start();

    }
}

运行结果:

Thread-0 1Mon Nov 26 19:14:24 CST 2018
Thread-0 2Mon Nov 26 19:14:25 CST 2018
Thread-0 3Mon Nov 26 19:14:26 CST 2018
Thread-0 4Mon Nov 26 19:14:27 CST 2018
Thread-0 5Mon Nov 26 19:14:28 CST 2018
Thread-0 6Mon Nov 26 19:14:29 CST 2018
Thread-0 7Mon Nov 26 19:14:30 CST 2018

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

线程案例

package com.SiyualChen.Demo02.Demo09;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class DownLoadThread  extends Thread{
    /*
     * 1,创建一个类去继承Thread类
     * 2,重写run方法
     * 3,创建Thread子类对象
     * 4,调用start方法启动线程(JVM会去调用Thread中的run方法)
     *
     */

    @Override
    public void run() {
        //使用IO流复制文件,由于被重写的run方法没有抛出IO异常,
        // 这里只能选择抓异常。
        try {
            FileInputStream fis  = new FileInputStream("/Users/chensiyuan/Desktop/陈思源简历/276A1503陈.jpg");
            FileOutputStream fos = new FileOutputStream("我的照片.jpg");
            byte[]  bytes = new byte[1024 *4];
            int len = 0;
            while ((len = fis.read(bytes)) != -1){
                System.out.println("正在下载我的照片");
                fos.write(bytes,0,len);
            }
            fis.close();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
package com.SiyualChen.Demo02.Demo09;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class DownLoadThread2 extends Thread {
    @Override
    public void run() {
        try {
            FileInputStream fis = new FileInputStream("/Users/chensiyuan/Desktop/陈思源简历/WechatIMG48.jpeg");
            FileOutputStream fos = new FileOutputStream("我的另一张照片.jpeg");
            byte[] bytes = new byte[1024*4];
            int len=0;
            while ((len = fis.read(bytes)) !=-1) {
                System.out.println("正在下载另一张照片");
                fos.write(bytes,0,len);
            }
            fis.close();
            fos.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package com.SiyualChen.Demo02.Demo09;

public class DownLoadtest {
    /*
     * 1,创建一个类去继承Thread类
     * 2,重写run方法
     * 3,创建Thread子类对象
     * 4,调用start方法启动线程(JVM会去调用Thread中的run方法)
     */
    public static void main(String[] args) {
        DownLoadThread t1 = new DownLoadThread();
        DownLoadThread2 t2 = new DownLoadThread2();

        t1.start();
        t2.start();


    }


}

运行结果:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值