04【面向对象、封装、static】

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 04【面向对象、封装、static】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文


上一篇03【流程控制、JVM内存、数组】

下一篇05【继承、抽象、权限修饰符、final】

目录【JavaSE零基础系列教程目录】


04【面向对象、封装】

一、面向对象

1.1 面向对象概述

1.1.1 什么是对象

在Java中,对象(Object)是指一个具体事物的实例,任何事物都可以使用对象(类)来描述,如猫、狗、计算机、杯子、云、水、空气、叶子、灰尘等看得见的、看不见的、宏观的、微观的、具体的、抽象的都是对象,总之”万物皆对象”;

1.1.2 面向对象程序设计

我们前面就提到过,Java是一门面向对象的编程语言,面向对象是一种程序设计思想,与之对应的还有面向过程程序设计;面向对象是把一个对象的特征(属性)和行为单独封装到对象源代码中;这些属性和行为都被集中到一个地方,这样比把方法或者过程与数据分散开来更为方便和安全,含义更加明确;

1.1.3 面向对象和面向过程

举例:开车去上班

  • 面向过程:去车库提车、拿钥匙、打火、踩离合、挂挡、踩油门、刹车、加油、到公司、找车库停车
  • 面向对象:找个司机(对象)、到公司

可以看得出来,面向过程关注的是步骤,将所有步骤连在一起”我就能到公司上班”,面向对象则关注的是”开车去上班”这个事物整体,完成这件事的步骤全部封装起来,交给指定的对象去做(司机);

1.1.4 面向对象的特点

面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。面向对象的语言中,包含了三大基本特征,即封装、继承和多态。

tips:关于面向对象和面向过程大家不必太过纠结,我们现在是初学者,理解表面意思即可,随着后面的深入学习,我们会对面向对象有着更深刻的理解;

1.2 类和对象

1.2.1 类和对象的区分

在现实世界中,属于同一类的对象很多,类是抽象的,不是具体的,我们人习惯以对象的方式认识现实世界;

例如我说一辆汽车,那你脑海中立马就能够呈现出一辆汽车的模样吧,不管他是什么品牌、什么颜色、什么价格、什么参数等总而言之,都是一辆汽车,都是属于”汽车类”产品;

在这里插入图片描述

tips:类是抽象的,对象是具体的,对象是类的实例化;

类是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征行为特征来描述该类事物。

  • 属性:该事物的状态信息;
  • 行为:该事物的功能信息;

1.2.2 类和对象的举例

  • 类举例:

举例:猫类

属性:名字、体重、年龄、颜色; 行为:走、跑、叫;

  • 实例化对象:

猫对象:

属性:旺财、8kg、6岁、棕色;行为:悄悄的走、飞快的跑、喵喵叫;

1.3 Java类的定义

1.3.1 类的定义格式

public class 类名 {
	//成员变量
    //成员方法
}
  • 定义类: 就是定义类的成员,包括成员变量和成员方法。
  • 成员变量: 和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。
  • 成员方法: 和以前定义方法几乎是一样的。只不过把static去掉,static的作用在面向对象后面课程中再详细讲解。

类的定义格式举例:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Student {
    // 成员变量(属性)
    String name;        //姓名
    int age;            //年龄

    // 成员方法(行为) 
    public void study(){
        System.out.println("学习");
    }

    public void eat(){
        System.out.println("吃饭");
    }
}

1.3.2 类的实例化

前面我们说道过,类是抽象的,不是具体的,类只是负责把事物描述起来,提供模板;对象是类的实例化,是具体的;我们定义好一个Java类后需要通过对象将类进行实例化;

  • 创建对象:
类名 对象名 = new 类名();
  • 使用对象访问类中的成员:
对象名.成员变量;
对象名.成员方法();
  • 练习:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    public static void main(String[] args) {
        //创建对象格式: 类名 对象名 = new 类名();
        Student s = new Student();

        System.out.println("s: " + s);            //s: com.dfbz.demo.Student@1540e19d

        //直接输出成员变量的值
        System.out.println("姓名: " + s.name);      //null
        System.out.println("年龄" + s.age);         //0
        System.out.println("--------");

        //给成员变量赋值
        s.name = "刘德华";
        s.age = 38;

        //再次输出成员变量的值
        System.out.println("姓名: " + s.name);      //刘德华
        System.out.println("年龄: " + s.age);       //18
        System.out.println("---------");

        //调用成员方法
        s.study();          //学习
        s.eat();            //吃饭
    }
}

1.3.3 成员变量的默认值

数据类型 默认值
基本类型 整数(byte,short,int,long) 0
浮点数(float,double) 0.0
字符(char) ‘\u0000’
布尔(boolean) false
引用数据类型 数组,对象,String null
  • 定义手机类并使用:
    • 属性:品牌,价格,颜色;
    • 行为:打电话,发短信

1)定义Phone类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Phone {

    //成员变量
    String brand;
    int price;
    String color;

    //成员方法
    //打电话
    public void call(String name){
        System.out.println("给"+name+"打电话");
    }

    //发短信
    public void sendMessage(){
        System.out.println("大家新年好!");
    }
}

2)定义测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) {
        //创建对象
        Phone p=new Phone();

        System.out.println("品牌: "+p.brand);     // null
        System.out.println("价格: "+p.price);     // 0
        System.out.println("颜色: "+p.color);     // null

        System.out.println("-------");

        p.brand="华为";
        p.price=2999;
        p.color="银白色";

        System.out.println("品牌: "+p.brand);     //华为
        System.out.println("价格: "+p.price);     //2999
        System.out.println("颜色: "+p.color);     //银白色
        System.out.println("------------");

        p.call("刘德华");
        p.sendMessage();
    }
}

1.4 对象内存图

回顾JVM内存,JVM总共分为5大内存区域,寄存器、本地方法栈、方法区、栈内存(虚拟机栈)、堆内存;和我们程序员有关的为方法区、栈内存、堆内存;

  • 方法区:存储类的信息(有多少变量、方法、是什么修饰符修饰的等)、常量信息、静态变量等信息
  • 栈内存(VM栈):方法调用时进栈内存执行,也就是方法运行时消耗的内存;
  • 堆内存:存储类的实例信息,只要是new出来的信息都存在堆内存

new这个类的时候,Jvm去方法区找有没有这个class,没有就加载到方法区,属性方法这些都是在方法区class中的;Jvm加载完后,就根据这个模板在堆中创建对象给属性赋默认值,然后再执行赋值语句给对象赋值;

一个对象内存图:

在这里插入图片描述

两个对象内存图:

在这里插入图片描述

对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。这样哪怕是多个对象,方法信息只保存一份,节约内存空间。

1.5 局部变量和成员变量

变量根据定义位置的不同,我们给变量起了不同的名字,看下列测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Person {
    String name;            // 成员变量

    int age;                // 成员变量

    /**
     * 跑步方法
     */
    public void running() {
        int stretch = 10;           // 局部变量
        System.out.println("跑了" + stretch + "公里");
    }

    /**
     * 自我介绍方法
     * @param name: 姓名(局部变量)
     * @param age: 年龄(局部变量)
     */
    public void intro(String name, int age) {
        System.out.println("大家好,我叫" + name + ",今年" + age + "岁");
    }

    /**
     * 计算方法(列出0~num之间的偶数)
     * @param num: 局部变量
     */
    public void calculate(int num) {

        for (int i = 0; i < num; i++) {
            if (num % 2 == 0) {
                System.out.println(num);
            }
        }
    }
}
  • 局部变量与成员变量:

在这里插入图片描述

关于static关键字我们后面再解释,暂时把不用把实例变量与类变量区分开;

  • 成员变量和局部变量的区别:
    • 成员变量:
      • 1)成员变量定义在类中,在整个类中都可以被访问。
      • 2)成员变量分为类成员变量和实例成员变量,实例变量存在于对象所在的堆内存中。
      • 3)成员变量有默认初始化值。
      • 4)成员变量的权限修饰符可以根据需要,选择任意一个
    • 局部变量:
      • 1)局部变量只定义在局部范围内,如:方法内,代码块内等。
      • 2)局部变量存在于栈内存中,当方法弹栈(执行完毕)后,局部变量销毁;
      • 3)局部变量没有默认初始化值,使用前必须手动赋值;
      • 4)局部变量声明时不指定权限修饰符;

1.6 值传递和引用传递

1.6.1 思考

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04 {
    public static void main(String[] args) {
        Phone p1=new Phone();

        p1.price=2899;

        test(p1);
        System.out.println(p1.price);           // 2899 or 3899?

        int i=20;
        test2(i);

        System.out.println(i);                  // 20 or 50?
    }

    public static void test(Phone p){
        p.price=3899;
    }

    public static void test2(int i){
        i=50;
    }
}

1.6.2 形参和实参

形参也叫形式参数,是一个方法的参数列表中的参数;实参也叫实际参数,是调用者在调用方法时实际传递的参数;

在这里插入图片描述

1.6.3 值传递和引用传递概念

  • 值传递:(参数类型是基本数据类型):方法调用时,实参把它的值传递给对应的形参,形参只是用实参的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形参值的改变不影响实参的值。

  • 引用传递:(参数类型是引用数据类型参数):也称为传地址。方法调用时,实参是对象(或数组),这时实参与形参指向同一个地址,在方法执行中,对形参的操作实际上就是对实参的操作,这个结果在方法结束后被保留了下来,所以方法执行中形参的改变将会影响实参。

在Java中,除了基本数据类型之外的数据类型都是引用数据类型,都是通过new在堆内存开辟空间;

  • 引用传递对象内存图:

在这里插入图片描述

1.7 匿名对象

顾名思义,匿名就是没有名字的对象,在创建对象时,只通过new的动作在堆内存开辟空间,却没有把堆内存空间的地址值赋值给栈内存的某个变量用以存储;

使用场景:

  • 1)如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
  • 2)我们经常将匿名对象作为实参传递给一个方法调用。
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05 {
    public static void main(String[] args) {

        Student student=new Student();
        intro(student);
        student.study();

        // 使用匿名对象传递
        intro(new Student());

        // 使用匿名对象调用方法
        new Student().study();
    }

    public static void intro(Student student) {
        System.out.println("姓名: "+student.name+",年龄: "+student.age);
    }
}

二、封装

2.1 封装概述

封装是面向对象的三大特征之一,面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性

  • 举例:

就拿一辆汽车来说,外部有一个壳,将内部的原件封装起来,至于汽车内部的细节是什么,我们不需要关心,对于用户来说,我们只需要会操作这辆汽车就可以,汽车对外提供方向盘、离合器、油门、以及一些娱乐设备,这样就将内部的东西不在直接暴露给外部,增加了安全性;

我们来举例一段代码,看看没有封装的对象会存在什么问题:

准备一个Student类,具备姓名、年龄等属性

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Student {

    // 学生姓名
    String name;
    
    // 学生年龄
    int age;
}

编写测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {

    public static void main(String[] args) {
        Student s1 = new Student();
        s1.name = "小灰";
        s1.age = 2800;

        intro(s1);

        Student s2 = new Student();
        s2.name = "小蓝";
        s2.age = -280;

        intro(s2);
    }

    public static void intro(Student student) {
        System.out.println("大家好,我叫" + student.name + ",今年" + student.age + "岁");
    }
}

在这里插入图片描述

上述代码是可以正常运行的(编译不会报错),但是对于我们程序逻辑来说是错误的;

Student类未进行封装,其所有属性直接暴露给外部,外部程序可以任意的对Student的属性就行修改,这是非常有安全隐患的,我们的正常年龄可以设置一个正常区间如0~120岁,不可能到2800多岁,也不可能是负的岁数;因此我们要对属性加以管控,而不是直接暴露给外端;

2.2 priavte 关键字

  1. private是一个权限修饰符,代表最小权限。
  2. 可以修饰成员变量和成员方法。
  3. 被private修饰后的成员变量和成员方法,只在本类中才能访问。

封装的原则:将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问

1)使用private 修饰成员变量,代码如下:

public class Student {

    // 学生姓名
    private String name;

    // 学生年龄
    private int age;
}

2)提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Student {

    // 学生姓名
    private String name;

    // 学生年龄
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String _name) {
        name = _name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int _age) {
        if (_age > 0 && _age < 120) {
            age = _age;
            System.out.println("赋值成功,您的年龄为: " + _age);
        } else {
            System.out.println("您输入的年龄不合法," + _age);
        }
    }
}

3)测试类:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {

    public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("小灰");
        s1.setAge(2888);
        s1.setAge(-280);
        s1.setAge(20);

    }

    public static void intro(Student student) {
        System.out.println("大家好,我叫" + student.getName() + ",今年" + student.getAge() + "岁");
    }
}

在这里插入图片描述

经过封装后,属性再也不是直接暴露给外部了,我们可以在外部操作属性之前加以控制;

2.3 this 关键字

this是Java中的一个关键字,代表所在类的当前对象的引用(地址值),即对象自己的引用;

  • 修改Student中的set方法:
public class Student {

    // 学生姓名
    private String name;

    // 学生年龄
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        // this代表当前对象的内存地址值
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age > 0 && age < 120) {
            // this代表当前对象的内存地址值
            this.age = age;
            System.out.println("赋值成功,您的年龄为: " + age);
        } else {
            System.out.println("您输入的年龄不合法," + age);
        }
    }
}

this内存图:

在这里插入图片描述

tips :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。

2.4 构造方法

构造方法也叫构造器,顾名思义就是用来构造类的;当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。

tips:无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。

  • 构造方法的定义格式
修饰符 构造方法名(参数列表){
    // 方法体
}

构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。使用构造方法后,代码如下:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Phone {
    private String name;
    private int price;

    // 无参构造方法
    public Phone() {

    }

    // 有参构造方法
    public Phone(String name, int price) {
        this.name = name;
        this.price = price;
    }
}

构造方法注意事项:

  1. 如果你不提供构造方法,系统会给出无参数构造方法。
  2. 如果你提供了构造方法,系统将不再提供无参数构造方法。
  3. 构造方法是可以重载的,既可以定义参数,也可以不定义参数。

2.5 标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是和公共的,并且具有无参数的构造方法,提供用来操作成员变量的 setget 方法,采用private修饰成员变量。

  • 格式如下:
public class ClassName{
    //成员变量
    //构造方法
    //无参构造方法【必须】
    //有参构造方法【建议】
    //成员方法  
    //getXxx()
    //setXxx()
}

编写符合 JavaBean 规范的类,以学生类为例,标准代码如下:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Phone {
    //成员变量
    private String brand;
    private int price;

    //构造方法
    public Phone() {

    }

    public Phone(String brand, int price) {
        this.brand = brand;
        this.price = price;
    }
    //成员方法  

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

}

三、package包

3.1 包的概述

package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:

package 顶层包名.子包名;

查看我们的代码目录:

在这里插入图片描述

所有的类都在一个目录下(根目录),随着项目的类越来越,我们的项目会变得难以维护;包(package)类似于我们的文件夹,当文件多了,我们可以创建多个文件夹对文件进行归类,包则是对Java源代码进行归类;

3.2 创建包

使用IDEA创建包:

在这里插入图片描述

包的名称一般为公司倒写域名,例如com.alibaba、com.baidu等;.代表分割,com.dfbz实际创建了一个二级目录;

在这里插入图片描述

也可以通过创建类的方式来创建包:

输入com.dfbz.Demo02:在com/dfbz包下创建了一个Demo02类:

在这里插入图片描述

输入com.abc.Demo01:在com/abc包下创建了一个Demo01类:

在这里插入图片描述

最终目录结构:

在这里插入图片描述

3.3 声明包

在任何类的第一句代码都必须声明这个类在哪个包下的,我们之前的类都创建在根目录(src目录),因此不需要声明所在的包,除了根目录下的类都需要在类的第一行声明包;

在这里插入图片描述

1)包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;

2)包通常用小写单词,类名首字母通常大写;

3.4 import导入包

为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类

在这里插入图片描述

如果想要导入这个包下的所有类,使用*

import com.dfbz.*;

四、static关键字

4.1 static概述

static 关键字它可以用来修饰的成员变量成员方法被修饰的成员是属于类的,而不是单单是属于某个对象的。被static修饰的成员由该类的所有实例(对象)共享;

4.2 定义和使用格式

4.2.1 类变量

static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作,因为该变量属于类,而不是某个对象。

  • 类变量: 使用 static关键字修饰的成员变量。

定义格式:

static 数据类型 变量名;

举例:

static int numberID;

现在想为每一位新来报到的同学编学号(sid),从第一名同学开始,sid为1,以此类推。学号必须是唯一的,连续的,这样以便知道,要分配给下一名新同学的学号是多少。这样我们就需要一个变量,与单独的每一个学生对象无关,而是与整个班级同学数量有关。

所以,我们可以这样定义一个静态变量numberID,代码如下:

package com.dfbz.demo;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class demo01 {
    public static void main(String[] args) {

        Student s1 = new Student("张三", 23);
        Student s2 = new Student("李四", 24);
        Student s3 = new Student("王五", 25);
        Student s4 = new Student("赵六", 26);

        s1.show(); // Student : name=张三, age=23, sid=1
        s2.show(); // Student : name=李四, age=24, sid=2
        s3.show(); // Student : name=王五, age=25, sid=3
        s4.show(); // Student : name=赵六, age=26, sid=4
    }
}

class Student {
    // 存储到底创建了多少个学生
    static int numberID = 0;
    int sid;
    String name;
    int age;

    public Student() {
        // 每次创建一个学生,id都自增1
        numberID++;
        sid=numberID;
    }

    public Student(String name, int age) {
        // 每次创建一个学生,id都自增1
        numberID++;
        sid=numberID;
        this.name = name;
        this.age = age;
    }

    public void show() {
        System.out.println("sid: " + sid + "-name: " + name + "-age: " + age);
    }
}

4.2.2 静态方法

static 修饰成员方法时,该方法称为类方法。静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。

  • 类方法:使用 static关键字修饰的成员方法,习惯称为静态方法

定义格式:

修饰符 static 返回值类型 方法名 (参数列表) { 
	// 执行语句
}

举例:在Student类中定义静态方法

public static void showNum() {
    System.out.println("numberID:" + numberID);
}
  • 静态方法调用的注意事项:
    • 静态方法可以直接访问类变量(被static修饰的变量)和静态方法。(静态方法能够访问静态资源)
    • 静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
    • 静态方法中,不能使用this关键字。

tips:静态方法只能访问静态成员。

4.2.3 调用格式

被static修饰的成员可以并且建议通过类名直接访问。虽然也可以通过对象名访问静态成员,原因即多个对象均属于一个类,共享使用同一个静态成员,但是不建议,会出现警告信息。

格式:

// 访问类变量
类名.类变量名;

// 调用静态方法
类名.静态方法名(参数)

调用演示,代码如下:

public static void main(String[] args) {
    // 访问类变量
    System.out.println(Student.numberID);
    // 调用静态方法
    Student.showNum();
}

4.3 静态原理图解

static 修饰的内容:

  • 是随着类的加载而加载的,且只加载一次。
  • 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。
  • 它优先于对象存在,所以,可以被所有对象共享。

在这里插入图片描述

4.4 静态代码块

  • 静态代码块:定义在成员位置,使用static修饰的代码块{ }。
    • 位置:类中方法外。
    • 执行:随着类的加载执行,而执行且执行一次。

格式:

public class ClassName {
    static {
        //执行语句
    }
}

作用:给类变量进行初始化赋值。用法演示,代码如下:

public class Computer {
    private String name;
    private double price;

    // 类一加载,静态代码块中的代码就会被执行,而且是执行一次
    static{
        System.out.println("加载网卡驱动....");
        System.out.println("加载声卡驱动....");
        System.out.println("加载第三方软件插件....");
    }
}

tips:static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况下,去调用方法。下面将介绍两个工具类,来体现static 方法的便利。


上一篇03【流程控制、JVM内存、数组】

下一篇05【继承、抽象、权限修饰符、final】

目录【JavaSE零基础系列教程目录】


记得点赞~!!!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/131781.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!