JAVA之面向对象

一、什么是面向对象

1.首先,我们需要知道 Java 程序其实是运行在JVM (Java虚拟机) 上的,使用 Java 编译器编译 Java 程序时,生成的是与平台无关的字节码,这些字节码只面向 JVM。不同平台的 JVM 都是不同的,但它们都提供了相同的接口,这也正是 Java 跨平台的原因。其和 JDK、JRE 的关系如下图所示:
在这里插入图片描述

2.面向对象的定义:其实就是一种程序设计的思想方法。

⭐ 基本特点:

抽象: 对同一类型的对象的共同属性和行为进行概括,形成类(class) 。类是构造对象的模板或蓝图。由类构造(construct) 对象的过程称为创建类的实例 (instance ).
封装: 将抽象出的数据、代码封装在一起,隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性
继承: 在已有类的基础上,进行扩展形成新的类,提高代码复用性。继承是多态的前提
多态: 所谓多态就是同一函数名具有不同的功能实现方式

2.面向对象面向过程区别
(1)面向对象: 先把问题中设计到的概念用对象表示出来,再统筹流程。
在这里插入图片描述

(2)面向过程: 先考虑问题的解决流程,第一步、第二步分步进行设计。
在这里插入图片描述

二、变量和对象引用

1.变量的意义:变量由于要保存数据,所以需要一块空间来储存,这块空间全部在内存上。

2.定义和使用:定义一个符号,在某个范围内代表一个特定的值

**⭐** 在 Java 中,一切都被视为对象,但操纵的标识符实际上是对象的一个引用(reference)。

💬 比如: 下面来创建一个 String 引用,用于保存单词或语句


String s;

这里创建的只是引用,并不是对象。如果此时对 s 应用 String 方法,会报错。因为此时 s 没有与任何事物相关联。因此,一种安全的做法是:创建一个引用的同时便进行初始化:


String s = "小牛肉";

不过这里用到了 Java 语言的一个特性:字符串可以用带引号的文本初始化。

可以显式地将对象变量设置为 null,表明这个对象变量目前没有引用任何对象。

String s = null;

如果将一个方法应用于一个值为 null 的对象上,那么就会产生运行时错误。

三、对象初始化

1.基本类型(四类八种)

Java 是一种强类型语言。这就意味着必须为每一个变量声明一种类型(Python 就是弱类型语言)。
在这里插入图片描述

2.类属性的默认值
int--->0char--->\u000(null)boolen--->falsebyte--->0short--->0long--->0Lfloat--->0.0fdouble--->0.0d

注意:为了代码的可读性更好,最好还是显示的进行初始化

四、函数的基本语法

(1)**类的创建
class关键字,类名一般要求首字母大写,如果这个类是
public修饰则类名要和文件名一致。 类内部可以有属性也可以有方法,每个属性和方法都可以加static以及其他的访问权限控制的修饰符(private、public、protected、defalut)**

下面看一个最简单的 Java 应用程序:

public class FirstSample{
    public static void main(String[] args){
        System.out.println("Hello World!")
    }
}

注意:在 Java 中,文件中的每个类都具有唯一标识符。存储这段源代码的文件名必须为 FirstSample.java (再次提醒大家注意,大小写是非常重要的, 千万不能写成 firstsample.java)

(2)****对象的创建(类的实例化)

new关键字,实例化一个对象的时候会给该实例分配内存空间,针对该实例进行初始化,得到内存地址并返回,使用一个引用来持有。

String s = new String("小牛肉");

new 关键字的意思就是 “给我一个新对象”。new 操作符的返回值也是一个引用

可以理解为 new String(“小牛肉”) 构造了一个 String 类型的对象, 并且它的值是对新创建对象的引用。这个引用存储在对象变量 s 中。

也可以让某个对象变量引用一个已存在的对象:

String str = new String("小牛肉");
String s;
s = str

现在,这两个变量 s 和 str 引用同一个对象 String。
在这里插入图片描述

(3)**类和对象的理解:
是自定义类型,相当于
图纸**
对象是根据类型创建出来的变量,相当于根据图纸盖出来的房子

五、运算符

① 算术运算符

在 Java 中,使用算术运算符 + 、-、 * 、/ 表示加、减、 乘、除运算。整数的求余操作(有时称为取模) 用 % 表示。

🚨 注意:
整数被 0 除将会产生一个异常, 而浮点数被 0 除将会得到无穷大或 NaN 结果
当参与 /运算的两个操作数都是整数时, 表示整数除法;否则, 表示浮点数除法。

②关系运算符
在这里插入图片描述

③ 自增与自减运算符

int n = 12; 
n ++; // n =13

自增、 自减运算符:n++ 将变量 n 的当前值加 1, n-- 则将 n 的值减 1。

④ 逻辑运算符
在这里插入图片描述
🚨 注意: && 和 || 运算符具有短路 (short-circuiting) 特性:整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到

⑤ 位运算符
位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。位运算符包括:
在这里插入图片描述
另外,还有 >> 和 << 移位运算符,将位模式左移或右移。

最后,>>> 运算符会用 0 填充高位,这与>>不同,它会用符号位填充高位。不存在<<<运算符。

⑥ 三元操作符

格式:布尔表达式 ? 值 1 : 值 2

若表达式计算为 true,则返回结果 值 1 ;如果表达式的计算为 false,则返回结果 值 2。

举个例子:


int i = 5;
int x = i < 10 ? i * 100 : i * 10; // x = 500

六、构造函数

1.如何理解构造函数

Java 会在用户使用对象之前(即对象刚创建完成)帮助用户自动地去调用对象的这个初始化方法,从而保证初始化,而这个能被 Java 自动调用的初始化方法就是「构造函数/构造器」
当然,并不是随便什么方法都能称为构造函数/构造器,它有相关的规定。
💬 以下面这个类为例:

class Test{
    private int birthday;
    private String sex;

    public Test(){ // 无参构造函数

    }

    public Test(int birthday, String sex){ // 有参构造函数
        this.birthday = birthday;
        this.sex = sex;
    }

   
}
public class Demo {

    public static void main(String[] args){
        Test test = new Test(18,"male");
    }
}

可以看到, 「构造函数与类同名且没有返回值」。在构造 Test类的对象时, 构造函数会运行,以便将实例域初始化为所希望的状态。

构造函数与其他的方法有一个重要的不同:
「构造函数总是伴随着 new 操作符的执行被调用, 而不能对一个已经存在的对象调用构造函数来达到重新设置实例域的目的。」

🚩 现阶段对于构造函数我们需要记住以下几点:

1.构造函数与类同名
2.每个类可以有一个以上的构造函数
3.构造函数可以有 0 个、1 个或多个参数
4.构造函数没有返回值
5.构造函数总是伴随着 new 操作一起调用
6.构造函数中尽量不要调用其他的方法,尤其是有可能被重写的方法

注意:(因为当调用的这个方法被子类重写的时候,可能触发多态,导致调用的方法变成子类的方法,而子类方法中的数据成员可能没有被初始化)
**

2.无参构造函数(默认构造函数)

定义:一个无参构造器就是不接收任何参数的构造器,用来创建一个"默认的对象"。如果你创建一个类,「类中没有构造器,那么编译器就会自动为你创建一个无参构造器」。例如:


class Bird {
    
}

public class DefaultConstructor {
    public static void main(String[] args) {
        Bird bird = new Bird(); // 默认构造函数
    }
}

表达式 new Bird() 创建了一个新对象,调用了无参构造器,尽管在 Bird 类中并没有显式的定义无参构造器。毕竟如果一个构造器都没有,我们如何创建一个对象呢。

🚨 但是!!!此处一定要注意:「一旦你显式地定义了构造器(无论有参还是无参),编译器就不会自动为你创建无参构造器」。形象点来说,如果类中有构造器,编译器会觉得 “你已经写了构造器了,所以肯定知道你在做什么,你没有创建默认构造器,说明你本来就不需要”。

3.方法的重载

定义:如果多个方法有「相同的名字、 不同的参数」,便产生了重载。

4.static关键字

**
1.static表示该属性或者该方法,和实例无关,和类有关,并通过类名.属性的方式来访问,static修饰的属性在内存中只有一份。
2.static与字面意思的静态没有任何关系。

class StaticTest {
    static int i = 47;
}

在这里插入图片描述


StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();

4.this关键字

❓思考: 假设某个类中有一个方法 peel 且没有重载,如果我们使用了相同的构造函数创建了该类的两个对象 a 和 b,并且分别都调用了 peel 方法。那么我们怎么知道调用的是对象 a 的 peel()方法还是对象 b 的 peel() 方法呢?


class Banana {
    void peel(int i) {
        /*...*/
    }
}
public class BananaPeel {
    public static void main(String[] args) {
        Banana a = new Banana(), b = new Banana();
        a.peel(1);
        b.peel(2);
    }
}

实际上,编译器做了一些底层工作,peel() 方法中其实还有一个「隐式参数」,该参数隐密地传入了一个指向调用该方法对象的引用。「也就是说谁调用了这个方法,这个方法的隐式参数就指向谁」。因此,上述例子中的方法调用像下面这样:

Banana.peel(a, 1)
Banana.peel(b, 2)

这是在内部实现的,你不可以直接这么编写代码,编译器不会接受,我这样写只是为了让大家明白隐式参数的意义。

假设现在在方法内部,你想获得调用该方法的当前对象的引用。但是,对象引用是被秘密地传达给编译器的,并不在参数列表中。为此,Java 中的关键字 this 就派上用场了 。

「this 关键字只能在非静态方法内部使用」。当你调用一个对象的方法时,this 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用该类的其他方法,不需要使用 this,直接调用即可,this 自动地应用于其他方法上了。因此你可以像这样:


public class Apricot {
    void pick() {
        /* ... */
    }

    void pit() {
        pick();
        /* ... */
    }
}

在 pit() 方法中,你可以使用 this.pick(),但是没有必要。编译器自动为你做了这些。this 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如,用在 return 语句中返回对当前对象的引用:


public class Leaf {

    int i = 0;

    Leaf increment() {
        i++;
        return this;
    }

    void print() {
        System.out.println("i = " + i);
    }

    public static void main(String[] args) {
        Leaf x = new Leaf();
        x.increment().increment().increment().print();
    }
}

注意:increment() 方法通过 this关键字返回当前对象的引用,因此在相同的对象上可以轻易地执行多次操作。

② 在构造函数中调用另一个构造函数
关键字 this 还有另外一个含义。「如果构造函数的第一个语句形如 this(…), 这个构造函数将调用同一个类的另一个构造函数」,这样可以避免代码重复。下面是一个典型的例子:


public class Flower {
    int petalCount = 0;
    String s = "initial value";

    Flower(int petals) {
        petalCount = petals;
        System.out.println("Constructor w/ int arg only, petalCount = " + petalCount);
    }

    Flower(String s, int petals) {
        this(petals);
        //- this(s); // Can't call two!
        this.s = s; // Another use of "this"
        System.out.println("String & int args");
    }
    

🚨注意: 尽管可以用 this调用一个构造器,但是「不能」调用两个。此外,「必须将构造函数调用至于最起始处即第一行」,否则编译器会报错。并且,编译器「不允许」你在一个构造器之外的方法里调用构造器。

另外,这个例子展示了 this 的另一个用法。参数列表中的变量名 s 和成员变量名 s 相同,会引起混淆。「你可以通过 this.s 表明你指的是成员变量 s,从而避免重复」。

七、封装

1.定义: 在面向对象程序设计方法中,封装(Encapsulation) 是指一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。

通俗来说,可以认为封装就是一个保护屏障,防止某个类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过 「严格的访问控制」

「封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段」。
2.如何实现封装: 通过访问修饰符用于修饰被封装的类的访问权限
从“最大权限”到“最小权限”依次是:
在这里插入图片描述
使用原则:能用private就用private

总结:
1. 使成员成为 public。那么无论是谁,无论在哪,都可以访问它。(任意包)
2. 赋予成员默认包访问权限(defalut),不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。(同类包)
3. 继承的类既可以访问 public 成员,也可以访问 protected 成员(但不能访问 private 成员)。只有当两个类处于同一个包内,它才可以访问包访问权限的成员。(同类包和其他包的子类)
4. private只能在类内部访问

🚨注意: 当外部需要调用private属性时,提供访问器(accessor)和修改器(mutator)方法(也称为"get/set" 方法),从而读取和改变值。

八、组合

所谓组合(Composition),就是 「在新类中创建现有类的对象」 。不管是继承和组合,都允许在新类中直接复用旧类的「公有」方法或字段。

举个例子,比如说所有的动物都拥有心跳 beat 和呼吸 breath,我们将心跳和呼吸抽象成一个类 Animal,这个类就称为现有类,现在有一个动物:猫 Cat,那么 Cat 这个类就称为新类,「将 Animal 类的对象嵌入 Cat 这个类中,Cat 就具有了心跳和呼吸」,这就使用了组合。

通俗来说 Cat 拥有 Animal,即 「has-a」 的关系。以后再有其他动物的出现,比如狗 Dog,也同样将 Animal 类嵌入其中使其具有心跳和呼吸即可,不必重复的写心跳和呼吸方法的代码。这便是组合的全部意义。UML 类图如下:
在这里插入图片描述
代码示例如下:


public class Animal {
 public void beat(){
  System.out.println("My heart is beating");
 }
 public void breath(){
  System.out.println("I'm breathing");
 }
}

Cat 拥有 Animal,不仅拥有了呼吸和心跳功能,并且还可以添加自己的新属性,使其具有新的方法:

public class Cat {
    // 组合
 private Animal animal;
    // 使用构造函数初始化成员变量
 public Cat(Animal animal){
  this.animal = animal;
 }
    // 通过调用成员变量的固有方法使新类具有相同的功能
 public void breath(){
  animal.breath();
 }
    // 通过调用成员变量的固有方法使新类具有相同的功能
 public void beat(){
  animal.beat();
 }
    // 为新类增加新的方法
 public void run(){
  System.out.println("I'm running");  
 }
}

这样,Cat 这个新类拥有了三种方法:breath / beat / run:

// 显式创建被组合的对象实例 animal
Animal animal = new Animal();
// 以 animal 为基础组合出新对象实例 cat
Cat cat = new Bird(animal);
// 新对象实例 cat 可以 breath()
cat.breath();
// 新对象实例 cat 可以 beat()
cat.beat();
// 新对象实例 cat 可以 run()
cat.run();

以上便是组合实现复用的方式,Cat 对象由 Animal 对象组合而成,如上面的示例代码,在创建 Cat 对象之前先创建 Animal 对象,并利用这个 Animal 对象来创建 Cat 对象。

实际上,组合表示出来的是一种明确的「整体-部分」的关系。而对于继承来说,是将某一个抽象的类,改造成能够适用于不同特定需求的类。

九、继承

1.存在的意义:可以更好的代码重用(以类为单位,在类与类之间实现继承,关键字为extends)
在这里插入图片描述
当子类要调用父类的私有属性时可以使用getter/setter方法。

同上面组合代码,继承的UML图如下:
在这里插入图片描述

我们仍然以组合代码为例子:


public class Cat extends Animal{
    // 为新类增加新的方法
 public void run(){
  System.out.println("I'm running");  
 }
}

Cat 继承 Animal 后,自动拥有了父类 Animal 中的方法 beat 和 breath,并可以直接调用,代码如下:


Cat cat = new Cat();
// 子类实例 cat 可以 breath()
cat.breath();
// 子类实例 cat 可以 beat()
cat.beat();
// 子类实例 cat 可以 run()
cat.run();

以上便是继承实现复用的方式,Cat 继承自抽象的类 Animal,并将其改造成能够适用于某种特定需求的类。

🚨注意: 继承不能被滥用!!! 要符合is-a 语义。(使用组合要符合has-a语义)

十、方法的覆盖/重写

定义: 子类继承父类后,不仅可以直接调用父类的方法,还可以对父类的方法进行重写,使其拥有自己的特征。仍然以上面的 Cat 和 Animal 为例,假设 Cat 继承 Animal 后,对 Animal 原生的呼吸方法 breath 很不满意,但是你不能不呼吸对吧,所以这个时候就可以直接对 breath 方法的方法体进行重写。

「🚨注意,重写和重载不同」,重载指的是两个方法具有相同的名字,但是不同的参数,而 「重写不仅方法名相同,参数列表和返回类型也相同」。示例代码如下:


public class Cat extends Animal{
    ......
    
    // 重写 breath 方法
    @Override
    public void breath(){
  System.out.println("I'm cat, " + super.breath());
 }    
}

@Override 注解即表示方法重写,不过这个也可以不写,JVM 能够自动的识别方法覆盖。

上面这个方法输出的将是 I’m cat, I’m breathing,也就是说,在子类中可以使用 super 关键字调用父类的方法。

另外,一定要🚨注意的是:****「在覆盖一个方法的时候,子类方法不能低于父类方法的可见性」。特别是, 如果超类方法是 public, 子类方法一定要声明为 public。常会发生这类错误:在声明子类方法的时候, 遗漏了 public修饰符。此时,编译器将会把它解释为试图提供更严格的访问权限:
在这里插入图片描述

十一、向上转型、向下转型

① 向上转型
继承最重要的方面不是为子类提供方法。它是子类与父类的一种关系。简而言之,上文我们也说过,这种关系可以表述为「子类是父类的一种类型」。这种描述并非是解释继承的一种花哨方式,这是直接由语言支持的。下面例子展示了编译器是如何支持这一概念的:


Animal cat = new Cat(...); // 向上转型 Cat->Animal

也就是说,「程序中出现父类对象的任何地方都可以用子类对象置换」,这便是 「向上转型」。通过子类对象 (小范围) 实例化父类对象(大范围),这种属于自动转换。事实上,这是「多态」的一种体现。后续文章我们会详细讲解。

🚨需要注意的是:「父类引用变量指向子类对象后,只能使用父类已声明的方法」,但方法如果被重写会执行子类的方法,如果方法未被重写那么将执行父类的方法。

② 向下转型
不仅存在向上转型,还存在向下转型。正像有时候需要将浮点型数值 float 转换成整型数值 int 一样,有时候也可能需要「将某个父类的对象引用转换成子类的对象引用,调用一些子类特有而父类没有的方法」。对象向下转型的语法与数值表达式的类型转换类似,仅需要用一对圆括号将目标类名括起来,并放置在需要转换的对象引用之前就可以了。例如:


Animal animal = new Cat(...); // 向上转型 Cat->Animal
Cat cat = (Cat) animal; // 向下转型 Animal->Cat,animal 的实质还是指向 Cat

在这里插入图片描述

十二、多态

1.什么是多态:
多态就是 「使得同一个行为具有多个不同表现形式或形态的能力」。举个形象点的例子:对于 “打印” 这个行为,使用彩色打印机 “打印” 出来的效果就是彩色的,而使用黑白打印机 “打印” 出来的效果就是黑白的。我们就称 “打印” 这个行为是多态的,彩色打印效果和黑白打印效果就是 “打印” 这个行为的两个不同的表现形式。
在这里插入图片描述
还可以这样理解,「同一个行为在不同的对象上会产生不同的结果」。再举个形象点的例子:比如我们按下 F1 键这个行为:如果当前在 Word 下弹出的就是 Word 帮助和支持;在 Windows 下弹出的就是 Windows 帮助和支持。

2. 多态发生的三个必要条件:

先看下面这段代码,首先,我们有一个基类 Shape,三个子类,并且都重写了基类的 draw 方法:


class Shape {
    void draw() {}
}
 
class Circle extends Shape {
    void draw() {
        System.out.println("Circle.draw()");
    }
}
 
class Square extends Shape {
    void draw() {
        System.out.println("Square.draw()");
    }
}
 
class Triangle extends Shape {
    void draw() {
        System.out.println("Triangle.draw()");
    }
}

下面这几行代码就充分体现了多态性:


Shape circle = new Circle();
Shape square = new Square();
Shape triangle = new Triangle();

「向上转型」 就是多态的体现。同样的一个 draw 方法,在这三个不同的对象上产生了三种不同的行为,多态在此体现的淋漓尽致。

🚨需要注意的是,当使用多态方式调用方法时,编译器会首先检查父类中是否有该方法,如果没有,则编译错误;如果父类中有该方法,并且被子类重写,就会调用子类的这个方法;如果父类的方法没有被子类重写,就会调用父类的方法。


Shape circle = new Circle();
circle.draw(); // 调用的是 Circle 的 eat

简单来说:「当父类引用变量指向子类对象后(多态),只能使用父类已声明的方法,但方法如果被重写会执行子类的方法,如果方法未被重写那么将执行父类的方法」。

结合上述这段简单的代码,我们总结一下多态产生的必要条件

1)继承
2)重写
3)父类引用指向子类对象:Parent p = new Child();

在这里插入图片描述

十三、重载和重写

方法的 「重写 Overriding」「重载 Overloading」 都是Java 多态性的表现。

🔸 1)****「方法重写是父类与子类之间多态性的表现」。其子类和父类方法的名字相同,参数个数相同,返回类型也相同,并且子类的访问权限不能比父类的严格,比如父类是 public,那么子类也只能是 public,不能比 public 更严格。也就是说,「方法重写,只有方法体是不一样的,访问权限可以有限制的修改」



class Shape {
    public void draw() {}
}
 
class Circle extends Shape {
    public void draw() {
        System.out.println("Circle.draw()");
    }
}

🚨 其实,上面说的返回类型完全相同并不严格正确。下面我们来解释一下。

首先,我们需要知道 「方法的名字和参数列表称为方法的签名」。例如,draw() 和 draw(String) 是两个具有相同名字, 不同签名的方法。如果在子类中定义了一个与超类签名相同的方法, 那么子类中的这个方法就覆盖/重写了超类中的这个相同签名的方法。

不过,「返回类型不是签名的一部分」, 因此,在覆盖/重写方法时, 一定要保证返回类的兼容性。「允许子类将覆盖方法的返回类型定义为原返回类型的子类型」

例如, 假设 Shape 类有


class Shape {
    public Shape draw() {
     ......
    }
}

在后面的子类 Circle 中, 可以按照如下所示的方式覆盖这个方法


class Circle extends Shape {
    public Circle draw() {
        ......
    }
}

用专业术语来说,这两个 draw 方法具有「可协变的返回类型」。

🔸 2) 方法重载并非多态的必要条件,不过可以理解成 「某个类的多态性的表现」 。所谓方法重载,就是一个类中定义了多个方法名相同,但是参数的数量或者类型不同。方法的返回类型和访问权限可以任意修改,不以它俩作为方法重载的标志。


class Circle extends Shape {
    public void draw() {
        System.out.println("Circle.draw()");
    }
    
    public void draw(int i) {
        System.out.println("Circle.draw()" + i);
    }
}

在这里插入图片描述
总结一下方法重载和重写:
在这里插入图片描述
再附一张经典网图
在这里插入图片描述

十四、接口

1.定义: 接口是通过 interface 关键字定义的,它可以包含一些常量和方法,来看下面这个示例。


public interface Electronic {
    // 常量
    String LED = "LED";

    // 抽象方法
    int getElectricityUse();

    // 静态方法
    static boolean isEnergyEfficient(String electtronicType) {
        return electtronicType.equals(LED);
    }

    // 默认方法
    default void printDescription() {
        System.out.println("电子");
    }
}

1)接口中定义的变量会在编译的时候自动加上 public static final 修饰符,也就是说 LED 变量其实是一个常量。

2)没有使用 private、default 或者 static 关键字修饰的方法是隐式抽象的,在编译的时候会自动加上 public abstract 修饰符。也就是说 getElectricityUse() 其实是一个抽象方法,没有方法体——这是定义接口的本意。

3)从 Java 8 开始,接口中允许有静态方法,比如说 isEnergyEfficient() 方法。

总结:
1.一个类可以实现多个接口
2.接口之间也可以相互继承
3.实现一个接口,必须要把接口里面的抽象方法都实现出来

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值