java 面向对象的注意点

1. 在子类的一个构造方法中,this() 和 super() 只能存在一个,因为他两都必须被定义在构造方法的第一行,但是如果创建子类对象使用的构造方法,由于使用了this() 调用了其他子类的构造方法,导致该构造方法不能再使用super()调用父类的构造方法 ,默认情况下编译器会自动在子类的构造函数第一行添加super() , 但是如果这个构造函数中第一行是使用this() 调用其他的构造函数 ,编译器就不会再自动添加super()了,但是肯定会有一个子类的构造方法使用super()调用父类的构造方法。

class SuperClass{

    public int sex;

    public SuperClass(){}

    public SuperClass(int sex){
        this.sex = sex;
    }

}

class SonClass extends SuperClass{

    public int age;

    public String name;

    public SonClass(){
        // 这里默认会有 super() 来调用父类构造函数
        // 至少有一个构造函数 可以使用 super() 来调用父类构造函数
    }

    public SonClass(String name){
        this(); // 由于使用this()调用无参构造 不能使用super()
        this.name = name;
    }

    public SonClass(String name , int age){
        this(name);// 由于使用this(name)调用其他构造函数 不能使用super()
        this.age = age;
    }

    public SonClass(int sex){
        //这里可以使用super() 调用父类的任意构造函数
        super(sex);
    }

    @Override
    public String toString() {
        return "SonClass{" +
                "sex=" + sex +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}


public class ThisSuperTest {

    public static void main(String[] args) {

        SonClass sonClass = new SonClass("张三");

        System.out.println(sonClass);

    }

}

SonClass{sex=0, age=0, name='张三'}

2.子类成员变量的显示赋值在调用super()实例化父类之后,所以在调用super()的过程中,子类成员变量的值都是默认赋值,直到super()执行完毕,才会对子类成员变量进行显示赋值。

class Super{

    public Super(){
        System.out.println("Super constructor print son age " + getAge());
        printAge("Super");
    }

    public int getAge(){
        return 50;
    }

//这个方法会被子类覆盖,所以不会执行,会执行子类的实现
    public void printAge(String name){
        System.out.println(50);
    }
}

class Son extends Super{

    private int age = 20;

    public Son(){
        super();
        System.out.println("son constructor print age " + age);
        printAge("son");
    }

    public int getAge(){
        return this.age;
    }

    public void printAge(String name){
        System.out.println(name + " call printAge method " + this.age);
    }
}


public class MemberFieldAssignment {

    public static void main(String[] args) {
        Son son = new Son();
    }

}

运行结果:
Super constructor print son age 0  //在调用super()过程中访问age,age还是默认值
Super call printAge method 0 //在调用super()过程中访问age,age还是默认值
son constructor print age 20 //在调用super()完毕之后访问age,age已经显示赋值
son call printAge method 20  //在调用super()完毕之后访问age,age已经显示赋值

3.同名调用

        1.当局部和成员同名时,使用this区分成员,this代表当前对象

        2.当子类和父类的成员同名时,使用super区分父类,super代表父类空间

class SuperClass{

}

class SonClass extends SuperClass{

    public void printHashCode(SonClass this){
        System.out.println(this.hashCode());
        System.out.println(super.hashCode());
    }

}


public class ThisSuperTest {

    public static void main(String[] args) {

        SonClass sonClass = new SonClass();

        SonClass sonClass1 = new SonClass();

        sonClass.printHashCode();
        System.out.println();
        sonClass1.printHashCode();

    }

}

运行结果:
1512981843
1512981843

42768293
42768293

说明this和super是指向同一个堆地址的

4.当子类定义了和父类一摸一样的成员变量,或者重写了父类中的成员方法,这种情况父类成员变量和成员方法 并没有被子类覆盖,只是子类对象访问时优先访问子类的成员方法或者成员变量,当子类找不到时才会去父类空间找。也可以用super直接访问父类的指定非私有成员变量和非私有成员方法

5.如果一个类纯工具类,都是静态方法,在使用时不需要创建对象,为了防止不了解的而创建对象,浪费内存,可以将默认构造方法定义为私有 private,防止创建对象。

6.抽象类注意点

         1.抽象类不可以创建对象,如果创建了抽象类对象,调用了其抽象方法没有任何意义。
         2.抽象类有构造方法,因为子类构造函数的第一行就是调用父类的构造器,抽象类的构造函           数可以被子类的构造函数第一行调用,用于初始化抽象父类中定义的成员变量。
         3.抽象类中如果没有定义任何的抽象方法,这样一般是为了阻止该类创建对象

7.abstract 不可以组合的关键字
        1.private  如果private修饰抽象方法,无法被子类实现
        2.static    静态方法可以直接用类名调用,如果静态方法是抽象的,调用没有意义
        3.final     final修饰的方法不可以重写,abstract 修饰的方法希望被重写,final修饰的类不可以          被继承,abstract 修饰的类希望被继承

public interface InterfaceStaticField {
    int age = 100;
    void print();
}

 编译完了之后对 InterfaceStaticField.class反编译

8.接口中
        1.成员变量的修饰符固定为 public static final
        2.成员方法修饰符固定 public abstract
        就算我们编码时缺了这些固定修饰符中的一些,编译器也会自动给加上

9.java 多实现比 c++ 多继承的比较
        1.多继承情况下,如果多个父类中定义了申明相同但是实现不同方法,这样子类在调用时,            不知道该调用哪个父类的方法,而出现问题
        2.多实现情况下,子类实现的多个接口中申明了相同的方法,但是这多个方法真正的实现在            子类中,而且只有一种实现,所以调用时肯定是调用的子类的实现,不会出现问题
        3.多实现避免了java 单继承的局限性
        4.java中的接口与接口之间继承关系,而且接口之间支持多继承,同样因为接口没有方法体,方法真正的实现在实现接口的子类中,所以调用时只能调用子类中的唯一实现,不会出问题

10.抽象类继承是 is a 的关系,在定义该体系的基本共性内容
     接口的实现是 like a 的管子,在定义体系额外功能

11.成员变量  成员方法  静态变量  静态方法 在多态情况下的编译和运行

 

class Super2{
    public String name = "父类中定义的名字";

    public static String STATIC_NAME = "父类中定义的静态名称";

    public static void print(){
        System.out.println("父类中的静态方法");
    }

    public String getName(){
        return this.name;
    }
}

class Son2 extends Super2{

    public String name = "子类中定义的名字";

    public static String STATIC_NAME = "子类中定义的静态名称";

    public static void print(){
        System.out.println("子类中的静态方法");
    }

    public String getName(){
        return this.name;
    }
}

public class PolymorphismOverwrite {

    public static void main(String[] args) {

        Super2 super2 = new Son2();

        System.out.println(super2.name);//父类中定义的名字
        System.out.println(super2.getName());//子类中定义的名字
        System.out.println(super2.STATIC_NAME);//父类中定义的静态名称
        System.out.println(Super2.STATIC_NAME);//父类中定义的静态名称

        System.out.println();

        Son2 son2 = new Son2();
        System.out.println(son2.name);//子类中定义的名字
        System.out.println(son2.getName());//子类中定义的名字
        System.out.println(son2.STATIC_NAME);//子类中定义的静态名称
        System.out.println(Son2.STATIC_NAME);//子类中定义的静态名称

    }

}

public class AnonymousInnerClass {

    public static void main(String[] args) {

        new Object(){
            public void print(){
                System.out.println("匿名内部类自定义方法");
            }
        }.print();

    }

}

运行结果:
匿名内部类自定义方法

这里之所以能够成功运行是因为创建的是一个匿名对象,这个对象的类型就是匿名
内部类的类型,只不过这个类型没有起名,我们无法申明该类型的变量,但是这个匿名
对象确确实实是这个匿名内部类类型的实例,而这个类型中定义了新方法print(),所以
编译可以通过,运行也可以通过

这种写法其实没有什么意义,因为我们不知道该用什么类型的变量来指向该匿名内部类对象
,如果父类的类型变量来引用,又无法调用匿名内部类自己定义的方法,因为编译不通过,
父类中没有定义这个方法


public class AnonymousInnerClass {

    public static void main(String[] args) {

        Object obj = new Object(){
            public void print(){
                System.out.println("匿名内部类自定义方法");
            }
        };

        obj.print(); // 这一行编译失败

    }

}

java: 找不到符号
  符号:   方法 print()
  位置: 类型为java.lang.Object的变量 obj

obj.print(); 这里编译失败了,原因是我们不知道这个匿名内部类的类名,
无法申明该类型的变量来引用这个匿名内部类的,只能使用它的父类变量来引用,因为Object
中没有定义print()方法,所以编译不通过



public class AnonymousInnerClass {

    public static void main(String[] args) {

        Object obj = new Object(){
            public String toString(){
                return "匿名内部类的toString方法";
            }
        };

        obj.toString();

    }

}

运行结果:
匿名内部类的toString方法

这里因为父类中定义了 toString() 方法,编译通过了,匿名内部类作为子类重写了父类的
方法,运行看的是实际创建的对象类型中的实现,所以运行也没有问题,而且调用了匿名内部类
重写以后的方法



class ObjectImpl extends Object{
    public void print(){
        System.out.println("子类自定义方法");
    }
}

public class AnonymousInnerClass {

    public static void main(String[] args) {

        ObjectImpl object = new ObjectImpl();
        object.print();

    }

}

这里的子类我们知道子类的类型,可以直接申明子类类型的变量,所以可以用
子类类型的变量引用子类对象,调用子类自己定义的方法

由以上可以看出:

        编译的时候都是检查的申明的变量类型中有没有定义我们要使用的 成员变量,成员方法,静态变量,静态方法,如果申明了就可以编译通过 (编译的时候是看等号左边)

        运行的时候 成员变量使用等号左边的,成员方法使用等号右边的如果右边没有再使用左边的,静态方法和静态变量都是使用等号左边的

12.内部类可以直接访问外部类成员变量,其实是内部类对象直接访问外部类对象的成员,因为内部类对象只能由外部类对象创建,如果外部类成员变量改变了呢

class Outer {

    private int number = 10;

    class Inner {
        public void print(){
            System.out.println("inner class print " + number);
        }
    }

    public void invokeInnerClassPrint(){
        //print();
        //外部类必须先创建内部类的对象才可以访问内部类
        Inner inner = new Inner();
        //Inner inner = this.new Inner(); 
        //这里的this相当于一个Outer对象类似 new Outer().new Inner();
        inner.print();
    }

    public void setNumber(int newNumber){
        this.number = newNumber;
    }
}


public class InnerAccessOuterMember {

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.invokeInnerClassPrint();

        outer.setNumber(20);
        outer.invokeInnerClassPrint();
    }

}

运行结果:
inner class print 10
inner class print 20

这里的内部类相当于外部类的一个成员,类似于一个成员变量和成员方法,如果没有外部类对象,
这个内部类是不存在的,一旦创建了外部类的对象,这个外部类对象的内部类也就确定了,
也就是说这个内部类是对象级别的,是属于指定对象一个类,通过指定外部类对象创建的内部类对象,
访问都是当前外部类对象的成员,如果外部类对象的成员改变了,它所创建内部类对象访问的外部类
成员也会发生变化

   

class Outer1 {

    private int number = 10;

    class Inner {
        public void print(){
            System.out.println("inner class print " + number);
        }
    }

    public void invokeInnerClassPrint(){
        //print();
        //外部类必须先创建内部类的对象才可以访问内部类
        Inner inner = new Inner();
        inner.print();
    }

    public void setNumber(int newNumber){
        this.number = newNumber;
    }
}


public class CreateInnerClassInstance {

    public static void main(String[] args) {
        Outer1 outer = new Outer1();

        Outer1.Inner inner = outer.new Inner();

        inner.print();

        outer.setNumber(20);
        inner.print();
        
    }

}

运行结果:
inner class print 10
inner class print 20

这里其实和之前的实质是一样的,换了一种写法,这里是在外部类的外部创建内部类的对象,
由于内部类是外部类的成员,在外部类外部无法直接访问内部类创建对象,要访问外部类的
成员,必须先创建外部类的对象,通过外部类对象访问内部类创建内部类对象,通过指定外部类
对象创建的内部类对象,访问都是当前外部类对象的成员,如果外部类对象的成员改变了,它所创建
内部类对象访问的外部类成员也会发生变化

静态内部类相当于一个外部类 ,因为如果是静态内部类,就和其他的静态成员一样,随着外部类的加载,静态内部类也会被加载 (静态成员随着类的加载而加载),所以在不创建外部类对象的情况下,内部类已经存在了,所以不用先创建外部类对象也可以创建内部类对象,只是语法格式上需要显示外部类签名,表明要创建的内部类是在哪个外部类中定义的

静态内部类只能访问外部类的静态成员,因为在加载外部类的时候,就要把静态内部类加载,这时候外部类的非静态成员还不存在,所以无法访问,由于加载外部类的静态成员会随着外部类的加载而加载,所以在加载静态内部类时,外部类的静态成员已经存在,可以访问。

class Outer2{

    public static int number = 100;

    static class Inner{

        public static final String NAME = "静态内部类的静态属性";

        public void print(){
            System.out.println("inner class print " + number);
        }

    }

}

public class StaticInnerClass {

    public static void main(String[] args) {
        //这里可以直接创建内部类的对象,只是类名上有外部类的签名
        //表明这个内部类是在哪个外部类中定义的
        Outer2.Inner inner = new Outer2.Inner();
        inner.print();
        //访问静态内部类的 静态成员  Outer.Innter.静态成员
        System.out.println(Outer2.Inner.NAME);
    }
}

运行结果:
inner class print 100
静态内部类的静态属性

如果内部类内部定义了静态成员方法,该内部类必须为静态内部类,如果内部类非静态,内部类内部可以申明静态变量或者静态常量

内部类之所以能访问外部类成员,是因为内部类的方法中可以获取到外部类对象的引用 Outer.this

class Outer3{
    private int number = 100;

    class Inner{
        private int number = 200;

        //public void print(Inner Inner.this){ 这里默认有个参数 Inner Inner.this
        public void print(Inner Inner.this){
            int number = 300;
            System.out.println("局部变量 " + number);
            System.out.println("内部类成员变量 " + this.number);
            System.out.println("内部类成员变量 " + Inner.this.number);
            System.out.println("外部类成员变量 " + Outer3.this.number);
            System.out.println();
            System.out.println("外部类 " + Outer3.this.getClass());
            System.out.println("内部类 " + this.getClass());
            System.out.println("内部类 " + Inner.this.getClass());
        }

    }


    public void invokeInnerClassMethod(){
        Inner inner = new Inner();
        inner.print();
    }
}

public class InnerAccessOuterMemberDirect {

    public static void main(String[] args) {
        new Outer3().invokeInnerClassMethod();
    }

}

局部变量 300
内部类成员变量 200
内部类成员变量 200
外部类成员变量 100

外部类 class com.fll.test.object_oriented.inner_class.Outer3
内部类 class com.fll.test.object_oriented.inner_class.Outer3$Inner
内部类 class com.fll.test.object_oriented.inner_class.Outer3$Inner

13.构造函数 和 构造代码块的 调用顺序 和 执行顺序

class SuperCodeBlock{

    {
        System.out.println("父类构造代码块1 " + getValue());
        setValue(100);
        System.out.println("父类构造代码块2 " + getValue());
    }

    public SuperCodeBlock(){
        super();
        System.out.println("父类构造函数1   " + getValue());
        setValue(200);
        System.out.println("父类构造函数2   " + getValue());
    }

    public int getValue(){
        return 10000;
    }

    public void setValue(int value){

    }

}

public class CodeBlockExecutionOrder extends SuperCodeBlock{

    public int value = 300;

    {
        System.out.println("子类构造代码块1 " + getValue());
        setValue(400);
        System.out.println("子类构造代码块2 " + getValue());
    }

    public CodeBlockExecutionOrder(){
        super();
        System.out.println("子类构造函数    " + getValue());
        setValue(500);
    }

    public int getValue(){
        return this.value;
    }

    public void setValue(int value){
        this.value = value;
    }

    public static void main(String[] args) {
        CodeBlockExecutionOrder codeBlockExecutionOrder = new CodeBlockExecutionOrder();
        System.out.println("对象创建完毕后  " + codeBlockExecutionOrder.getValue());
    }

}

父类构造代码块1 0
父类构造代码块2 100
父类构造函数1   100
父类构造函数2   200
子类构造代码块1 300
子类构造代码块2 400
子类构造函数    400
对象创建完毕后  500

这里我们是通过打印的方式来判断先后顺序,每一个代码块都会是一个栈帧,当代码块之间相互调用时,根据栈的特性,被调用的代码块的栈帧会在上面,且先执行,等上面的执行完了,才会继续执行下面栈帧的剩余的代码。

这里我们用子类覆盖了父类的 getValue()  setValue()方法,这样父类就可以实时获取子类中value的值,或者修改子类型value的值。

这里我们需要知道:
        1.子类构造函数的第一行代码就是使用 super() 调用父类的构造函数
        2.new对象刚开始在堆中开辟内存之后,会将所有的位都置为0,表现为默认初始化后所有的基础类型成员变量都为0,引用型成员变量都为 null
 
我们看到父类的 构造函数比子类的构造代码块先执行,如果父类的构造函数和子类的构造代码块 不存在相互调用关系,肯定是先调用父类构造函数,再调用子类构造代码块,但是父类的构造函数是在子类构造函数的第一行调用的,所以调用顺序是先调用子类构造函数,子类构造调用父类构造函数。根据打印顺序看到,子类构造代码块比子类构造函数中除了super()以外的代码先执行,只能理解为 子类构造函数 先用super()调用父类构造函数,super()执行完,再调用子类自己的构造代码块,构造代码块执行完,再执行子类构造函数自己剩余的代码。

我们还发现父类构造函数中已经将子类的value修改为200,但是到了子类的构造代码块却变成了字面量300,说明父类构造函数执行完以后,会先给成员变量进行显示赋值,再开始调用子类的构造代码块

 从字节码可以看出,构造方法在字节码的被编译为方法 init

这是类 SuperCodeBlock 的截图,包含着每行代码的行数(代码在第几行),可以对照下面的字节码进行理解

public <init>()V
   L0
    LINENUMBER 12 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 6 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u7236\u7c7b\u6784\u9020\u4ee3\u7801\u57571 "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.getValue ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 7 L2
    ALOAD 0
    BIPUSH 100
    INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.setValue (I)V

   L3
    LINENUMBER 8 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u7236\u7c7b\u6784\u9020\u4ee3\u7801\u57572 "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.getValue ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 13 L4
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u7236\u7c7b\u6784\u9020\u51fd\u65701   "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.getValue ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 14 L5
    ALOAD 0
    SIPUSH 200
    INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.setValue (I)V

   L6
    LINENUMBER 15 L6
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;

    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "\u7236\u7c7b\u6784\u9020\u51fd\u65702   "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.getValue ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L7
    LINENUMBER 16 L7
    RETURN

   L8
    LOCALVARIABLE this Lcom/fll/test/object_oriented/SuperCodeBlock; L0 L8 0
    MAXSTACK = 3
    MAXLOCALS = 1

这是 SuperCodeBlock 类编译成字节码以后,init() 方法,也就是构造函数的全部内容 

INVOKESPECIAL java/lang/Object.<init> ()V
调用Object类的构造方法,由于SuperCodeBlock没有直接父类,所以默认父类是Object,SuperCodeBlock构造方法中
首先调用的父类的构造函数,也就是Object的构造函数

LINENUMBER 6 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
接下来执行的是第6行,构造代码块的第1行

LINENUMBER 7 L2
ALOAD 0
BIPUSH 100
INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.setValue (I)V
接下来执行的是第6行,构造代码块的第2行

LINENUMBER 8 L3
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
接下来执行的是第8行,构造代码块的第3行

LINENUMBER 13 L4
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
接下来执行的是第13行,构造函数的第2行(构造函数中除了super()以外,我们自己写的第1行代码)

LINENUMBER 14 L5
ALOAD 0
SIPUSH 200
INVOKEVIRTUAL com/fll/test/object_oriented/SuperCodeBlock.setValue (I)V
接下来执行的是第14行,构造函数的第3行

LINENUMBER 15 L6
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
接下来执行的是第15行,构造函数的第4行

LINENUMBER 16 L7
RETURN
接下来执行的是第16行,构造函数的结束大括号

从上面的可以看出,编译的时候,其实是把构造代码块的内容插入到了构造方法中了,而且是 插在了 super() 的后面,所以我们之前猜测的 先调用子类构造函数 ,子类构造函数先调用super() , super()执行完,在调用构造代码块的猜测是正确的,只不过这里的调用不是通过为构造代码块创建一个栈帧的方式方式,而是直接将其代码插入到super()之后进行调用,所以构造方法中的可以访问到的局部变量this,在构造代码块中也可以访问到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值