java(一)入门基础


一. java概念

1.1 Java的特性和优势

  • 安全性

    Java适合于网络/分布式环境,为了达到这个目标,在安全性方面投入了很大的精力,使Java可以很容易构建防病毒,防篡改的系统

  • 面向对象

    面向对象是一种程序设计技术,非常适合大型软件的设计和开发。由于C++为了照顾大量C语言使用者而兼容了C,使得自身仅仅成为了带类的C语言,多少影响了其面向对象的彻底性!Java则是完全的面向对象语言。

  • 简单性

    Java就是C++语法的简化版,我们也可以将Java称之为“C++-”。“C加加减”,指的就是将C++的一些内容去掉;比如:头文件,指针运算,结构,联合,操作符重载,虚基类等等。同时,由于语法基于C语言,因此学习起来完全不费力。

  • 高性能

    Java最初发展阶段,总是被人诟病“性能低”;客观上,高级语言运行效率总是低于低级语言的,这个无法避免。Java语言本身发展中通过虚拟机的优化提升了几十倍运行效率。比如,通过JIT(JUST IN TIME)即时编译技术提高运行效率。 将一些“热点”字节码编译成本地机器码,并将结果缓存起来,在需要的时候重新调用。这样的话,使Java程序的执行效率大大提高,某些代码甚至接待C++的效率。

    ​ 因此,Java低性能的短腿,已经被完全解决了。业界发展上,我们也看到很多C++应用转到Java开发,很多C++程序员转型为Java程序员。

  • 分布式

    Java是为Internet的分布式环境设计的,因为它能够处理TCP/IP协议。事实上,通过URL访问一个网络资源和访问本地文件是一样简单的。Java还支持远程方法调用(RMI,Remote Method Invocation),使程序能够通过网络调用方法。

  • 多线程

    多线程的使用可以带来更好的交互响应和实时行为。 Java多线程的简单性是Java成为主流服务器端开发语言的主要原因之一。

  • 健壮性

    ​ Java是一种健壮的语言,吸收了C/C++ 语言的优点,但去掉了其影响程序健壮性的部分(如:指针、内存的申请与释放等)。Java程序不可能造成计算机崩溃。即使Java程序也可能有错误。如果出现某种出乎意料之事,程序也不会崩溃,而是把该异常抛出,再通过异常处理机制加以处理。

1.2 Java应用程序的运行机制

计算机高级语言的类型主要有编译型和解释型两种,而Java 语言是两种类型的结合。

​ Java首先利用文本编辑器编写 Java源程序,源文件的后缀名为.java;再利用编译器(javac)将源程序编译成字节码文件,字节码文件的后缀名为.class; 最后利用虚拟机(解释器,java)解释执行。

1.3 JVM、JRE和JDK

(1) JVM

  • JVM(Java Virtual Machine)就是一个虚拟的用于执行bytecode字节码的”虚拟计算机”。他也定义了指令集、寄存器集、结构栈、垃圾收集堆、内存区域。JVM负责将Java字节码解释运行,边解释边运行,这样,速度就会受到一定的影响。

  • 不同的操作系统有不同的虚拟机。Java 虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,随处运行”。 Java虚拟机是实现跨平台的核心机制。

(2) JRE

Java Runtime Environment (JRE) 包含:Java虚拟机、库函数、运行Java应用程序所必须的文件。

(3) JDK

Java Development Kit (JDK)包含:包含JRE,以及增加编译器和调试器等用于程序开发的文件。

JDK、JRE和JVM的关系:

  • 如果只是要运行Java程序,只需要JRE就可以。JRE通常非常小,其中包含了JVM。

  • 如果要开发Java程序,就需要安装JDK。

二. 数据类型和运算符

2.1 注释

  • 单行注释: 使用“//”开头,“//”后面的单行内容均为注释。
  • 多行注释: 以“/**”开头以“/”结尾,在“/”和“*/”之间的内容为注释,我们也可以使用多行注释作为行内注释。但是在使用时要注意,多行注释不能嵌套使用。
  • 文档注释: 以“/**”开头以“ */”结尾,注释中包含一些说明性的文字及一些JavaDoc标签(后期写项目时,可以生成项目的API)

2.2 标识符

标识符是用来给变量、类、方法以及包进行命名的,如Welcome、main、System、age、name、gender等。标识符需要遵守一定的规则:

  • 标识符必须以字母、下划线_、美元符号$开头。
  • 标识符其它部分可以是字母、下划线“_”、美元符“$”和数字的任意组合。
  • Java 标识符大小写敏感,且长度无限制。
  • 标识符不可以是Java的关键字。

标识符的使用规范:

  • 表示类名的标识符:每个单词的首字母大写,如Man, GoodMan
  • 表示方法和变量的标识符:第一个单词小写,从第二个单词开始首字母大写,我们称之为“驼峰原则”,如eat(), eatFood()

2.3 Java中的关键字/保留字

Java关键字是Java语言保留供内部使用的,如class用于定义类。 关键字也可以称为保留字,它们的意思是一样的,不能使用关键字作为变量名或方法名

关键字列表
abstractassertbooleanbreakbytecase
catchcharclassconstcontinuedefault
dodoubleelseextendsfinalfinally
floatforgotoifimplementsimport
instanceofintinterfacelongnativenew
nullpackageprivateprotectedpublicreturn
shortstaticstrictfpsuperswitchsynchronized
thisthrowthrowstransienttryvoid
volatilewhile
  • final关键字
    1. 修饰变量: 被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋值。 final int a = 120; a不能再被赋值了
    2. 修饰方法:该方法不可被子类重写。但是可以被重载! final void study(){}
    3. 修饰类: 修饰的类不能被继承。比如:Math、String等。 final class A {}

2.4 变量和常量

  • 变量

    (1)变量的本质

    变量本质上就是代表一个”可操作的存储空间”,空间位置是确定的,但是里面放置什么值不确定。我们可通过变量名来访问“对应的存储空间”,从而操纵这个“存储空间”存储的值。

    (2)变量的声明

    Java是一种强类型语言,每个变量都必须声明其数据类型。变量的数据类型决定了变量占据存储空间的大小。 比如,int a=3; 表示a变量的空间大小为4个字节。

    int:4个字节;long和double:8个字节

    每个变量都有类型,类型可以是基本类型,也可以是引用类型。

    变量名必须是合法的标识符,且变量声明是一条完整的语句,因此每一个声明都必须以分号结束

    (3)变量的分类

    类型声明位置从属于生命周期
    局部变量方法或语句块内部方法/语句块从声明位置开始,直到方法或语句块执行完毕,局部变量消失
    成员变量(实例变量)类内部,方法外部对象对象创建,成员变量也跟着创建。对象消失,成员变量也跟着消失;
    静态变量(类变量)类内部,static修饰类被加载,静态变量就有效;类被卸载,静态变量消失。
  • 常量

    常量通常指的是一个固定的值,例如:1、2、3、’a’、’b’、true、false、”helloWorld”等。

    在Java语言中,主要是利用关键字final来定义一个常量。 常量一旦被初始化后不能再更改其值。

    常量的声明及使用:

public class TestConstants {
    public static void main(String[] args) {
        final double PI = 3.14;
        // PI = 3.15; //重新赋值会编译错误,被final修饰不能再被赋值! 
        double r = 4;
        double area = PI * r * r;
        double circle = 2 * PI * r;
        System.out.println("area = " + area);
        System.out.println("circle = " + circle);
    }
}
  • 变量和常量命名规范(驼峰命名法)

    所有变量、方法、类名:见名知意

  1. 类成员变量,局部变量,方法名:首字母小写和驼峰原则: monthSalary, run(), runRun( )
  2. 常量:大写字母和下划线:MAX_VALUE
  3. 类名:首字母大写和驼峰原则: Man, GoodMan,TestPlay

2.5 数据类型

Java是一种强类型语言,每个变量都必须声明其数据类型。 Java的数据类型可分为两大类:基本数据类型(primitive data type)和引用数据类型(reference data type)。

Java中定义了3类8种基本数据类型:

  • 数值型- byte、 short、int、 long、float、 double
  • 字符型- char
  • 布尔型-boolean

引用数据类型的大小统一为4个字节,记录的是其引用对象的地址!

(1)整型

类型占用存储空间表数范围
byte1字节-27 ~ 27-1(-128~127)
short2字节-215 ~ 215-1(-32768~32767)
int4字节-231 ~ 231-1 (-2147483648~2147483647)约21亿
long8字节-263 ~ 263-1

长整型常数的声明:

long a = 55555555;  //编译成功,在int表示的范围内(21亿内)。
long b = 55555555555;//不加L编译错误,已经超过int表示的范围。
long c = 55555555555L;//在最后增加L声明当前常数是Long类型,可正常编译

(2)浮点型

带小数的数据在Java中称为浮点型。浮点型可分为float类型和double类型。

类型占用存储空间表数范围
float4字节-3.403E38~3.403E38
double8字节-1.798E308~1.798E308

float类型又被称作单精度类型,尾数可以精确到7位有效数字,在很多情况下,float类型的精度很难满足需求。而double表示这种类型的数值精度约是float类型的两倍,又被称作双精度类型,绝大部分应用程序都采用double类型。浮点型常量默认类型也是double。

float类型的数值有一个后缀F或者f ,没有后缀F/f的浮点数值默认为double类型。也可以在浮点数值后添加后缀D或者d, 以明确其为double类型。

float  f1 = 3.14F;//float类型赋值时需要添加后缀F/f
double d1  = 3.14;//double类型赋值时后面可加d,也可不加
double d2 = 3.14D;
  • 浮点类型float,double的数据不适合在不容许舍入误差的金融计算领域。如果需要进行不产生舍入误差的精确数字计算,需要使用BigDecimal类。

java.math包下面的两个有用的类:BigInteger和BigDecimal,这两个类可以处理任意长度的数值。BigInteger实现了任意精度的整数运算。BigDecimal实现了任意精度的浮点运算。

(3) 字符型

字符型在内存中占2个字节,在Java中使用单引号来表示字符常量。

例如’A’是一个字符,它与”A”是不同的,”A”表示含有一个字符的字符串。

char 类型用来表示在Unicode编码表中的字符。Unicode编码被设计用来处理各种语言的文字,它占2个字节,可允许有65536个字符。

char eChar = 'a'; 
char cChar ='中';

转义字符:

转义符含义Unicode****值
\b退格(backspace)\u0008
\n换行\u000a
\r回车\u000d
\t制表符(tab)\u0009
\“双引号\u0022
\‘单引号\u0027
\反斜杠\u005c

String类,其实是字符序列(char sequence)。

String  test = "abc";//字符串

(4) boolean类型

boolean类型有两个常量值,true和false,在内存中占一位(不是一个字节),不可以使用 0 或非 0 的整数替代 true 和 false 。

boolean 类型用来判断逻辑条件,一般用于程序流程控制 。

boolean flag ;
flag = true;   //或者flag=false;
if(flag) {
         // true分支
} else {
         //  false分支
}

请不要这样写:if ( flag == true ),只有新手才那么写。关键也很容易写错成if(flag=true),这样就变成赋值flag 为true而不是判断!老鸟的写法是if ( flag )或者if ( !flag)

2.6 运算符

计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操作变量。

类型内容
算术运算符(一元)++,--
算术运算符(二元)+,-,*,/,%
赋值运算符=
扩展运算符+=,-=,*=,/=
关系运算符>,<,>=,<=,==,!= instanceof
逻辑运算符&&,||,!,^
位运算符&,|,^,~ , >>,<<,>>>
条件运算符? :
字符串连接符+

算术运算符:

一元运算符:

public class test1 {
    public static void main(String[] args) {
        int a =3;
        int b=a++;//先给b赋值,a再自增。
        System.out.println("a="+a+"\nb="+b);//a=4 b=3
        
        a =3;
        b=++a;//a先自增,再给b赋值
        System.out.println ("a="+a+"\nb="+b);//a=4 b=4
    }
}

二元运算符:

二元运算符指的是需要两个操作数才能完成运算的运算符。其中的%是取模运算符,就是我们常说的求余数操作。

运算规则:

  1. 如果两个操作数有一个为Long, 则结果也为long。

  2. 没有long时,结果为int。即使操作数全为short,byte,结果也是int。

  浮点运算:

  3. 如果两个操作数有一个为double,则结果为double。

  4. 只有两个操作数都是float,则结果才为float。

赋值及其扩展赋值运算符:

运算符用法举例等效的表达式
+=a += ba = a+b
-=a -= ba = a-b
*=a *= ba = a*b
/=a /= ba = a/b
%=a %= ba = a%b

关系运算符:

运算符含义示例
==等于a==b
!=不等于a!=b
>大于a>b
<小于a<b
>=大于或等于a>=b
<=小于或等于a<=b
  • =是赋值运算符,而真正的判断两个操作数是否相等的运算符是==。
  • ==、!= 是所有(基本和引用)数据类型都可以使用
  • > 、>=、 <、 <= 仅针对数值类型(byte/short/int/long, float/double。以及char)

逻辑运算符:

逻辑运算的操作数和运算结果都是boolean值。

运算符说明
逻辑与&( 与)两个操作数为true,结果才是true,否则是false
逻辑或|(或)两个操作数有一个是true,结果就是true
短路与&&( 与)只要有一个为false,则直接返回false
短路或||(或)只要有一个为true, 则直接返回true
逻辑非!(非)取反:!false为true,!true为false
逻辑异或^(异或)相同为false,不同为true

位运算符:

位运算符说明
~取反
&按位与
|按位或
^按位异或
<<左移运算符,左移1位相当于乘2
>>右移运算符,右移1位相当于除2取商

字符串连接符:

“+”运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接。

int a=12;
System.out.println("a="+a);//输出结果: a=12

条件运算符:

x ? y : z

其中 x 为 boolean 类型表达式,先计算 x 的值,若为true,则整个运算的结果为表达式 y 的值,否则整个运算结果为表达式 z 的值。

2.7 运算符优先级的问题

优先级运算符结合性
1()括号运算符由左至右
2!、+(正号)、-(负号)一元运算符由左至右
2~位逻辑运算符由右至左
2++、--递增与递减运算符由右至左
3*、/、%算术运算符由左至右
4+、-算术运算符由左至右
5<<、>>位左移、右移运算符由左至右
6>、>=、<、<=关系运算符由左至右
7==、!=关系运算符由左至右
8&位运算符、逻辑运算符由左至右
9^位运算符、逻辑运算符由左至右
10|位运算符、逻辑运算符由左至右
11&&逻辑运算符由左至右
12||逻辑运算符由左至右
13? :条件运算符由右至左
14=、+=、-=、*=、/=、%=赋值运算符、扩展运算符由右至左

2.8 类型转换

(1)自动类型转换

自动类型转换指的是容量小的数据类型可以自动转换为容量大的数据类型。

可以将整型常量直接赋值给byte、 short、 char等类型变量,而不需要进行强制类型转换,只要不超出其表数范围即可。

实线表示无数据丢失的自动类型转换,而虚线表示在转换时可能会有精度的损失。

short  b = 12;  //合法
short  b = 1234567;//非法,1234567超出了short的表数范围

(2)强制类型转换

强制类型转换,又被称为造型,用于显式的转换一个数值的类型。在有可能丢失信息的情况下进行的转换是通过造型来完成的,但可能造成精度降低或溢出。

(type)var
//运算符“()”中的type表示将值var想要转换成的type类型。

例子:

double x  = 3.14; 
int nx = (int)x;   //值为3
char c = 'a';
int d = c+1;
System.out.println(nx);//3
System.out.println(d);//98
System.out.println((char)d);//b,将数字98转为char型

当将一种类型强制转换成另一种类型,而又超出了目标类型的表数范围,就会被截断成为一个完全不同的值。

int x = 300;
byte bx = (byte)x;    //值为44

不能在布尔类型和任何数值类型之间做强制类型转换

(3)范围溢出

操作比较大的数时,要留意是否溢出,尤其是整数操作时;

public class test1 {
    public static void main(String[] args) {
        int money = 1000000000; //10亿
        int years = 20;
        
        //返回的total是负数,因为超过了int的范围,数据溢出
        int total = money*years;
        System.out.println(total);//-1474836480
        
        /**
         * 返回的total仍然是负数。
         * 因为money和years都是int类型,相乘后仍然是int,此时已经发生了数据丢失,
         * 再自动转成long后已经是错误的值
         */
        long total1 = money*years;
        System.out.println(total1);//-1474836480
        
        //提前转变数据类型,先将一个参数变成long,整个表达式发生提升,全部用long来计算。
        long total2 = money*((long)years); 
        System.out.println(total2);//20000000000
   }
}

2.9 键盘输入

使用Scanner获取键盘输入:

import java.util.Scanner;

public class test2 {
    public static void main(String[] args) {
        //创建一个Scanner键盘输入对象
        Scanner scanner =  new Scanner(System.in);
        System.out.println("请输入名字:");
        String   name =  scanner.nextLine();
        System.out.println("请输入你的爱好:");
        String  favor = scanner.nextLine();
        System.out.println("请输入你的年龄:");
        int   age = scanner.nextInt();

        System.out.println("###############");
        System.out.println(name);
        System.out.println(favor);
        System.out.println("来到地球的天数:"+age*365);
        System.out.println("离开地球的天数:"+(100-age)*365);

    }
}

2.10 Math类的使用

Math.random()该方法用于产生一个0到1区间的double类型的随机数,但是不包括1。

int i = (int) (6 * Math.random()); //产生:[0,5]之间的随机整数
double a = Math.random();
System.out.println(a);

三. 控制语句

流程控制语句是用来控制程序中各语句执行顺序的语句,可以把语句组合成能完成一定功能的小逻辑模块。控制语句分为三类:顺序、选择和循环

3.1 选择结构

选择结构用于判断给定的条件,然后根据判断的结果来控制程序的流程。

主要的选择结构有:if选择结构和switch多选择结构。有如下结构:

  1. if单选择结构
  2. if-else双选择结构
  3. if-else if-else多选择结构
  4. switch结构

3.2 if语句

当布尔表达式为真,执行括号内的语句,否则执行else内的语句

(1)if单选择结构

if(布尔表达式){
    执行语句
}

(2) if-else双选择结构

if(布尔表达式){
 	执行语句1
}else{
    执行语句2
}

(3) if-else if-else多选择结构

if(布尔表达式1) {
	执行语句1
} else if(布尔表达式2) {
	执行语句2
}……
else if(布尔表达式n){
	执行语句n
} else {
	执行语句n+1
}

3.3 switch语句

switch (表达式) {
    case 值1: 
    	语句序列1;
    break;
    case 值2:
     	语句序列2;
    break;
 .....
    default:
     默认语句;
}
  • 当表达式为值1时,执行语句序列1;以此类推,当都不满足case值时,执行默认语句
  • break表示中断switch,不加会陷入死循环;

3.4 循环结构

循环结构分两大类,一类是当型,一类是直到型。

当型:

​ 当布尔表达式条件为true时,反复执行某语句,当布尔表达式的值为false时才停止循环,比如:while与for循环。

直到型:

​ 先执行某语句, 再判断布尔表达式,如果为true,再执行某语句,如此反复,直到布尔表达式条件为false时才停止循环,比如do-while循环。

3.5 while和do…while

(1) while:先判断后执行

while (布尔表达式) {
    循环体;
}

在循环刚开始时,会计算一次“布尔表达式”的值,若条件为真,执行循环体。而对于后来每一次额外的循环,都会在开始前重新计算一次。

语句中应有使循环趋向于结束的语句,否则会出现无限循环–––"死"循环。

求1到100的和:

public class test {
    public static void main(String[] args) {
        int  i = 0;
        int  sum = 0;
        // 1+2+3+…+100=?
        while (i <= 100) {
            sum += i;//相当于sum = sum+i;
            i++;
        }
        System.out.println("Sum= " + sum);
    }
}

(2) do…while :先执行后判断

do {
        循环体;
    } while(布尔表达式) ;

do-while循环结构会先执行循环体,然后再判断布尔表达式的值,若条件为真,执行循环体,当条件为假时结束循环。do-while循环的循环体至少执行一次。

求1到100的和:

public class test {
    public static void main(String[] args) {
        int i = 0;
        int sum = 0;
        do {
            sum += i; // sum = sum + i
            i++;
        } while (i <= 100);//此处的;不能省略
        System.out.println("Sum= " + sum);
    }
}

3.6 for循环

for (初始表达式; 布尔表达式; 迭代因子) {
      循环体;
}

求1到100的和:

public class test1 {
    public static void main(String[] args){
        int sum=0;
        int i;
        for(i=1;i<=100;i++){
            sum+=i;
        }
        System.out.println(sum);
    }
}

初始化部分、条件判断部分和迭代因子可以为空语句,但必须以“;”分开

for ( ; ; ) {    // 无限循环: 相当于 while(true)
    System.out.println("无限循环");
}

初始化变量的作用域:

public class test1 {
    public static void main(String[] args){
        for(int i=1;i<=10;i++){
            System.out.println(i);
        }
        System.out.println(i);//编译错误,无法访问在for循环中定义的变量
    }
}

嵌套循环:

打印99乘法表

public class test1 {
    public static void main(String[] args){
        int i,j;
        for(i=1;i<=9;i++){
            for(j=1;j<=i;j++){
                System.out.print(j+"*"+i+"="+i*j);
                System.out.print('\t');
            }
            System.out.println();
        }
    }
}

输出:

1*1=1	
1*2=2	2*2=4	
1*3=3	2*3=6	3*3=9	
1*4=4	2*4=8	3*4=12	4*4=16	
1*5=5	2*5=10	3*5=15	4*5=20	5*5=25	
1*6=6	2*6=12	3*6=18	4*6=24	5*6=30	6*6=36	
1*7=7	2*7=14	3*7=21	4*7=28	5*7=35	6*7=42	7*7=49	
1*8=8	2*8=16	3*8=24	4*8=32	5*8=40	6*8=48	7*8=56	8*8=64	
1*9=9	2*9=18	3*9=27	4*9=36	5*9=45	6*9=54	7*9=63	8*9=72	9*9=81

3.7 break和continue

在任何循环语句的主体部分,均可用break控制循环的流程。break用于强行退出循环,不执行循环中剩余的语句。

continue 语句用在循环语句体中,用于终止某次循环过程,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定。

3.8 方法

方法就是一段用来完成特定功能的代码片段,类似于其它语言的函数。

[修饰符1  修饰符2  …]  返回值类型 方法名(形式参数列表){
    Java语句;… … …
 }

方法的调用方式: 对象名.方法名(实参列表)

  • 形式参数:在方法声明时用于接收外界传入的数据所定义的。

  • 实参:调用方法时实际传给方法的数据。

  • 返回值:方法在执行完毕后返还给调用它的环境的数据。

  • 返回值类型:事先约定的返回值的数据类型,如无返回值,必须显示指定为为void。

如果方法前不加static则内存会放在堆,因此需要new一个对象才能使用,而使用static才会放在方法区,不需要new直接使用。

3.9 方法的重载(overload)

方法的重载是指一个类中可以定义多个方法名相同,但参数不同的方法。 调用时,会根据不同的参数自动匹配对应的方法。

重载的方法,实际是完全不同的方法,只是名称相同而已。

构成方法重载的条件:(1)形参类型、(2)形参个数、(3)形参顺序不同

不构成方法的重载:(1)只有返回值不同不构成方法的重载 (2)只有形参的名称不同,不构成方法的重载

3.10 递归(自己调用自己)

递归结构包括两个部分:

1.定义递归头。定义什么时候不调用自身方法。如果没有头,将陷入死循环,也就是递归的结束条件。

2.递归体。定义什么时候需要调用自身方法。

public class test1 {
    public static void main(String[] args){
        int b =test(10);
        System.out.println(b);
    }
    static int test(int n){
        if(n==1){//递归头
            return 1;
        }else{//递归体
            return n*test(n-1);
        }
    }
}

任何能用递归解决的问题也能使用迭代解决。当递归方法可以更加自然地反映问题,并且易于理解和调试,并且不强调效率问题时,可以采用递归;

在要求高性能的情况下尽量避免使用递归,递归调用既花时间又耗内存。

3.11 this最常的用法

  1. 在程序中产生二义性之处,应使用this来指明当前对象;普通方法中,this总是指向调用该方法的对象。构造方法中,this总是指向正要初始化的对象。
  2. 使用this关键字调用重载的构造方法,避免相同的初始化代码。但只能在构造方法中用,并且必须位于构造方法的第一句。
  3. this不能用于static方法中。
基础知识
  • 作者:管理员(联系作者)
  • 发表时间:2018-10-01 18:46
  • 版权声明:自由转载-非商用-非衍生-保持署名(null)
  • undefined
  • 评论