Java核心技术卷一 1. java基本语法

数据类型

java中一共有8种数据类型,其中4种整形,2种浮点类型,1种用于表示编码的字符类型char,1种用于表示真值的boolean类型。

整形

为了保证移植性,数据类型的取值范围是固定的。

  • int 4字节 -2^32 ~ 2^32-1 最常用
  • short 2字节 -2^16 ~ 2^16-1 需要控制占用存储空间量的大数组
  • long 8字节 -2^64 ~ 2^64-1 比如计算全球的人数
  • byte 1字节 -2^8 ~ 2^8-1 底层的文件处理

长整形有个后缀 L 如(40000L),十六进制前缀 0x ,八进制前缀 0 ,java 7 开始有二进制前缀 0b 如(0b111_1010_1000)下划线会自动去除。

public class DataType {
    public static void main(String[] args){
        int a = 0b10;
        int b = 010;
        int c = 10;
        int d = 0x10;
        System.out.println(a);//2
        System.out.println(b);//8
        System.out.println(c);//10
        System.out.println(d);//16
    }
}

浮点类型

表示有小数部分的数值

  • floate 4字节 符号位1bit 指数位8bit 尾数位23bit
  • double 8字节 符号位1bit 指数位11bit 尾数位52bit

大多数使用double类型,
floate后缀为F或f,没有后缀F的浮点数值默认为doublie,doublie后缀为D或d可以省略。

float e1 = 10;//默认为 int 转为 float 可以
float e2 = 10.0;//出错,默认为 double 转为 float 不行
float e3 = (float)10.0;//默认为 double 强制转换为 float 可以
float e4 = 10.0f;//加上 f ,就为 float 类型
double f = 10.0;//默认为 double

注意:JDK 5.0 中,可以使用十六进制表示浮点数。如0.125 = 0x1.0p-3,十六进制中使用 p(基数为2) 表示 e(基数为10) 。尾数采用十六进制,指数采用十进制。

用于表示溢出和出错情况的三个特殊的值:

  • 正无穷大 Double.POSITIVE_INFINITY
  • 负无穷大 Double.NEGATIVE_INFINITY
  • NaN Double.NaN

注意:所有值与 Double.NaN 比较都是不相同的

注意:浮点数值不适用禁止出现误差的金融计算中。如2.0-1.1打印出的是0.8999999999999999,而不是0.9。原因是浮点类型采用的是二进制计算,好比十进制计算无法精确表示分数1/10。如果需要无误差,就是用BigDecimal

char 类型

表示单个字符,二个字节,一个char类型可以表示 Unicode 代码单元,它可以为16进制,范围是\u0000~\uFFFF。

转义字符\u可以表示 Unicode 代码单元。

有很多特殊的转义字符,常用的有:

  • \n 换行
  • ' 单引号
  • " 双引号
  • \ 反斜杠

由于 Unicode 两个字节的代码宽度不足以包含世界上全部的字符,JDK 5.0 开始,采用代码点(指一个编码表中的某个字符对应的代码值),Unicode 分为17个代码点,第一个代码点为U+0000~U+FFFF包括了经典的 Unicode 代码;其余 16 个附加级别从U+10000~U+10FFFF

UTF-16 编码采用不同长度的编码表示所有 Unicode 代码点,每个字符用16位表示,被称为代码单元。

强烈建议不要在程序中使用 char 类型,除非需要对 UTF-16 代码单元进行操作。

boolean 类型

有两个值:true 和 false ,整形值和布尔值之间不能相互转换。

变量

每一个变量都属于一种类型。变量声明:

double salary;
int vacattion;
long earthPopulation;
boolean done;

变量必须定义类型,命名只能是字母、数字、下划线以及$,也可以是某种语言任何代表字母的字符,变量名的开头不能是数字。$不要在你的代码中使用这个字符,它只用在 java 编译器或者其他工具生成的名字中。

变量对大小写敏感。

变量初始化

  • 变量声明后,想要调用必须初始化。不能使用未初始化的变量,编译器会报错未初始化。
  • 变量的声明尽可能地靠近第一次使用的地方,这是一种良好的编程习惯。
int vacationDays;
vacationDays = 12;

int vacationDays = 12;

常量

利用关键字final指示常量。

final bouble CM_PER_INCH = 2.54;
  • 只能被赋值一次
  • 赋值后不能更改
  • 常量名尽量用大写

经常希望一个常量在类中的多个方法使用,这些常量称为类常量,可以使用关键字static final设置。

static 和 final 结合使得这个常量只有一个,不会跟随新的实例生成,可以通过类名直接访问。

类常量的定义位于main方法的外部。因此,在同一个类的其他方法中也可以使用这个常量。而且,如果一个常量被声明为public,那么其他类的方法也可以使用这个常量。

public static final double CM_PER_INCH = 2.45;
public static void main(String[] args)

Class.CM_PER_INCH //其他类调用

运算符

特性:

  • 两个整数相除表示整数除法,否则是浮点除法
  • 比较运算符,一旦左边能确定结果,右边就不执行,也就是短路
  • 位运算符,不存在短路,两边都要执行完

常用运算符

自增运算符 ++ 、自减运算符 -- :改变变量的值,操作数不能是数值,不要再其他运算符内使用这个,防止出现 bug

关系运算符a==b a!=b a<b a>b a<=b a>=b ! a&&b a||b a?b:c

&& 与 || 按照短路方式求值,第一个数确定了结果就不会指定第二个数。

位运算符

&:按位与。只有两个操作数对应位同为1时,结果为1,其余全为0
|:按位或。只有两个操作数对应位同为0时,结果为0,其余全为1
~:按位非。一元操作符,每位取反
^:按位异或。两边的结果不同(true 和 false),结果为真
<<:左位移运算符。符号位不变,低位补0
>>:右位移运算符。符号位不变,并用符号位补溢出的高位
>>>:无符号右移运算符。符号位不变,并用0补溢出的高位

适用于 整形 与 布尔型
整形会转为二进制进行位运算
布尔型直接进行位运算

左位移相当于乘以2^n,n为位移的个数
右位移相当于除以2^n,n为位移的个数

数学函数

Math.sqrt(4);//平方根
Math.abs(-4);//绝对值
Math.pow(2,3);//幂运算
Math.PI;//帕尔常量近似值
Math.E;//e常量近似值
Math.ceil(12.5);//向上取整
Math.floor(12.5);//向下取整
Math.round(12.5);//四舍五入
Math.random();//0 <= x < 1 的double类型的数

如果在源文件顶部加上一下代码,就可以省略前缀Math.

import static java.lang.Math.*;
a = sqrt(PI);

数值类型之间的转换

转换,存在无信息丢失的转换以及有精度损失的转换。

无信息丢失的转换:

  • byte -> short 1位到2位
  • short -> int 2位到4位
  • char -> int 2位到2位
  • int -> long 4位到8位
  • int -> double 4位到尾数52bit(6.5位)

损失进度的转换:

  • int -> float 如果int的取值超过了float的尾数23位,就会损失精度,如123456789会准换为1.23456792E8
  • long -> float 如果long的取值超过了float的尾数23位,就会损失精度,如123456789会准换为1.23456792E8
  • long -> double 如果long的取值超过了float的尾数52位,就会损失精度,如123456789123456789L会转换为1.23456789123456784E17

在进行二元操作时:

  • 如果两个操作符有一个是double类型,另一个转换为double类型
  • 否则,如果其中有一个是float类型,另一个转换为float类型
  • 否则,如果其中有一个是long类型,另一个转换为long类型
  • 否则,两个操作符都转换为int类型

精度低的自动提升为精度高的。
注意,如果 int 类型的数据在 byte 内,可以赋值给 byte。

//byte 范围 -128~127
byte b1 = 3;//正确,在范围内
byte b2 = 300;//错误,超过范围
byte b3 = 3 + 7;//正确,在范围内

byte b4 = 3;
byte b5 = 7;
byte b6 = b4 + b5;//错误,变量无法确定范围

final byte b7 = 3;
final byte b8 = 7;
byte b9 = b7 + b8;//正确,常量可以确定范围

赋值语句会自动转换

short s1 = 10;
s1 = s1 + 10;//错误,10 为 int 类型
s1 += 10;//正确,赋值语句会自动强制类型转换
s1 += 10.9;//正确,赋值语句会自动强制类型转换

强制类型转换

在不能自动转换类型的情况下,可以使用强制类型转换。
当然,会存在丢失一些信息的可能。

double x = 9.997;
int nx = (int) x;
//结果 nx = 9;

double类型数据截断小数部分转换为int类型数据,如果想要四舍五入可以使用Math.round(x)数学函数。
如果试图将一个类型强制类型转换为另一个类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。

运算符级别

运算符 结合性
[].()(方法调用) 从左向右
! ~ ++ -- +(一元运算正号)- (一元运算负号)()(强制类型转换)new 从右向左
*/% 从左向右
+- 从左向右
<< >> >> 从左向右
< <= > >= instanceof 从左向右
== != 从左向右
& 从左向右
^ 从左向右
| 从左向右
&& 从左向右
|| 从左向右
?= 从右向左
= += -= *= /= %= &= |= ^= <<= >>= >>>= 从右向左

枚举类型

变量取值在一个有限的集合内,如服装的尺寸。

enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE};

声明这种类型的变量:

Size s = Size.MEDIUM;

所以,他是一个类,详细介绍在第5章。

字符串

java字符就是Unicode字符,java没有内置的字符串类型,java类库中提供了一个预定一类String,每个用双引号括起来的字符串都是String类的一个实例。

String a = "b";

子串

复制较大字符串中的一段字符串,参数一为起始位置,参数二为结束位置

String greeting = "Hello";
String s = greeting.substring(0,3);
//结果 s = "Hel";

拼接

java语言允许使用+来连接字符串。

String a = "a";
String b = "b";
String ab = a + b;//"ab"
String c = a + "c";//"ac"

字符串和非字符串连接时,非字符串会转换为字符串

String a = "a";
String b = 1 + "a";//"1a"

不可变字符串

String类没有提供修改字符的方法,只能重复赋值。

String greeting = "hello";
greeting = greeting.substring(0,3) + "p!";
//结果 greeting = "help!";

java中的字符串存放在公共的储存池中,如果复制一个字符串,原始字符串与赋值字符串共享相同的字符。

很少需要修改字符串,而是往往需要对字符串进行比较。

检测字符串是否相等

对于字符串s.equals(t),如果字符串s与t相等返回true,否则返回false,s与t可以是字符串变量也可以是字符串常量。

"Hello".equals(t);
"Hello".equalsIgnoreCase("hello");//不区分大小写

不能使用==判断字符串是否相等,它的作用是确定两个字符串是否放置在同一个位置上,基本没有这个场景。

如果虚拟机将相同的字符串共享,就可以使用==运算符检测是否相等。实际上只有字符串常量是共享的,而+substring等操作产生的结果并不是共享的。

String greeting = "Hello";
System.out.println(greeting == "Hello");//true
System.out.println(greeting.substring(0, 3) == "hel");//false

空串与Null串

空串""是长度为0的字符串,以下用来检察字符串是否为空。

if(str.length() == 0)
if(str.equals(""))

检察一个字符串是否为null

if(str == null)

既不是null也不是空串

if(str != null && str.length() != 0)

代码点与代码单元

java 字符串由 char 序列组成,char 类型数据是一个采用 UTF-16 编码表示 Unicode 代码点的代码单元。

常用 Unicode 字符一个代码单元即可表示,而辅助字符需要一对代码单元表示。

获取代码单元数量:

String greeting = "中国";
int n = greeting.length();

获取代码点数量:

int cpCount = greeting.codePointCount(0, greeting.length());

获取某个位置上的代码单元:

char first = greeting.charAt(0);

获取某个位置上的代码点:

int i = 0;
int index = greeting.offsetByCodePoints(0,i);
int cp = greeting.codePointAt(index);

遍历字符串查看每一个代码点:

int i = 0;
while (i<greeting.length()) {
    int index = greeting.offsetByCodePoints(0, i);
    int cp = greeting.codePointAt(index);
    if (Character.isSupplementaryCodePoint(cp)) {
        i += 2;
        System.out.println(cp);
     } else {
        i++;
        System.out.println(cp);
     }
}

字符串 API

        String greeting = "Hello World!";
        char a = greeting.charAt(0);//返回给定位置的代码单元
        int b = greeting.codePointAt(2);//返回给定位置开始或结束的代码点
        int c = greeting.offsetByCodePoints(0,5)//从0开始,位移5后的代码点索引
        int d = greeting.compareTo("g");//按照字典排序,位于g前返回负数,后返回整数,否则返回0
        boolean e = greeting.endsWith("ld!");//以"ld!"结尾,返回true
        boolean f = greeting.equals("H");//与"H"相等返回true
        boolean g = greeting.equalsIgnoreCase("H");//与"H"相等(忽略大小写)返回true
        int h1 = greeting.indexOf("Hel");
        int h2 = greeting.indexOf("Hel",5);
        int h3 = greeting.indexOf(1);
        int h4 = greeting.indexOf(1,5);//返回 与字符串"H"或代码点1匹配 的第一个字串的开始位置。这个位置从索引0或5开始计算。如果不存在"H"返回-1
        int i1 = greeting.lastIndexOf("!");
        int i2 = greeting.lastIndexOf("!",4);
        int i3 = greeting.lastIndexOf(3);
        int i4 = greeting.lastIndexOf(3,8);//返回 与字符串"H"或代码点3匹配 的最后一个字串的开始位置。这个位置从原始串尾端或8开始计算。如果不存在"H"返回-1
        int j = greeting.length();//返回字符串长度
        int k = greeting.codePointCount(2,5);//返回2-4的代码点数量
        String l = greeting.replace("new","old");//用new替换源字符串所有的old
        boolean m = greeting.startsWith("H");//以H开始返回true
        String n1 = greeting.substring(2);
        String n2 = greeting.substring(2,5);//返回一个新的字符串,从2到末尾或5
        String o = greeting.toLowerCase();//大写改小写
        String p = greeting.toUpperCase();//小写改大写
        String q = greeting.trim();//去掉前后空格
        String r = String.join("+", "a", "b", "c"));//a+b+c,使用指定定界符连接所有元素

构建字符串

当用到大量短字符串,可以使用StringBuilder实现追加和插入,避免老是构建新的String对象,耗时又浪费空间,操作完成后调用.toString()方法变为String对象。

StringBuilder builder = new StringBuilder();
builder.append(ch);
builder.append(str);//追加
builder.setCharAt(2,'s');//第2个单元设置为s
builder.insert(2,'g'/"ggg");//在第2个单元插入
builder.delete(2,3);//删除2-2,返回this
String com = builder.toString();//转换为String类型

输入输出

利用控制台的输入输出,熟悉java程序设计语言。

读取输入

首先构造一个Scanner对象,与标准输入流System.in关联。
然后通过方法接受用户的输入。

Scanner 类在包 java.util 中

import java.util.*;

//java.util.Scanner 5.0
Scanner in = new Scanner(System.in);
String name = in.nextLine();//读取一行
String name2 = in.next();//读取一个单词(空格分隔)
String name3 = in.nextInt();//读取一个整数
String name4 = in.nextDouble();//读取一个浮点数
boolean page1 = in.hasNext();//检测输入中是否有其他单词
boolean page2 = in.hasNextInt();//检测下一个字符序列是否是整数
boolean page3 = in.hasNextDouble();//检测下一个字符序列是否是浮点数
//java.lang.System 1.0
Console console = System.console();
//返回与当前 Java 虚拟机关联的唯一 Console 对象(如果有),否则返回null。以Javaw所执行的应用程式(eclipse)没有主控制台(console),所以取不到console物件,System.console()只能是null了。
//java.io.Console 6
Console console = System.console();
console.readPassword();//从控制台读取密码,禁用回显。
console.readLine();//从控制台读取单行文本。

格式化输出

java的格式化输出延续了C。

System.out.print(x);//输出x不换行
System.out.println(x);//输出x换行
System.out.printf("%8.2f\n",x);//格式化输出,不换行,可以用转义字符

格式化输出printf有转换符指示被每一个以%字符开始的格式化说明。

还可以给控制格式化输出各种标志。如0 - ( ,

String.format("%s",a)可以用格式化创建字符串。

还提供了日期和时间的格式化选择。

格式化的字符串可以指出要被格式化的参数索引。索引必须在%后面,并以$终止。

System.out.println("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date())

结果

Due date: May 11, 2018

索引值从1开始,1代表第一个参数,2代表第二个参数。

文件输入输出

文件的读取

Scanner in = new Scanner(Paths.get("myfile.txt");
Scanner in = new Scanner(Paths.get("c:\\mydir\\myfile.txt");//含反斜杠需要额外加反斜杠

文件的写入

PrintWriter out = new PrintWriter("myfile.txt");
//若文件不存在自动创建,位置为Java虚拟机启动路径的相对位置。
//如果使用的是集成开发环境,启动路径由IDE控制
//位置为 String dir = ystem.getPropery("user.dir");

文件的路径

  • 相对路径:启动路径就是命令解释器的当前路径,列如,"mydirectory/myfile.txt"
  • 绝对路径:c:/mydirectory/myfile.txt

如果用一个不存在的文件构建 Scanner 或用一个不能被创建的文件名构造一个 PrintWriter 抛出异常。

如果知道这个异常可以在 main 方法后面用 throws 字句:

public static void main(String[] args) throws FileNotFoundException{
  Scanner in = new Scanner(Paths.get("myfile.txt"));
}

控制流程

java用条件语句和循环结构确定控制流程。

块作用域

块(即复合语句)是指由一对花括号括起来的若干条简单的java语句。

  • 块确定了变量的作用域

  • 块可以嵌套在另一个块中

  • 不能在嵌套的两个块中声明同名的变量,会导致无法通过编译。这与 C++ 和 js 不同

java public static void main(String[] args){ int n; { int k; int n;// ERROR -- can`t redefine n in inner block } }

条件语句

if (c != 3) {
    a = 1;
} else {
    b = 2;
}

循环语句

while (a < b) {
    a += b;
}
//至少执行一次循环
do {
    a += b;
} while (a < b);

确定循环

for 循环是 while 的简化形式

for (int i = 0; i < args.length; i++) {
    ;       
}

ifor循环内定义,在外部就无法访问i的最终结果,需要访问最终结果需要在外部定义。

如果循环不嵌套,i就可以无限使用:

for (int i = 0; i < args.length; i++) {
    ;       
}
for (int i = 0; i < args.length; i++) {
    ;       
}

警告:再循环中,检测两个浮点数是否相等需要格外小心。

for(double x = 0;x != 10;x += 0.1) ...

由于0.1无法用精确的二进制表示,由于舍入的误差,最红会导致 x 不会等于 10 导致循环一直进行下去。

多重选择

switch (key) {
case value:
    //语句
    break;
default:
    break;
}

value 可以为:

  • char byte short 或 int
  • 枚举常量
  • 字符串字面量 java se 7

执行流程:

  • 从选项值相匹配处开始执行知道遇到 break 语句或者执行到语句结束处。
  • 没有相匹配的 case 就执行 default

中断控制流程语句

break:退出当前循环语句
continue:中断每次循环进入下次循环
他们是可选的可以不使用。

中断标号

可以给语句块加标号赋予它们名称,标号位于语句之前。标号只能被continue和break引用。

label:statement

语句前只允许加一个标号,标号后面不能跟大括号。通过用break后加标号对处于标号中的语句进行控制。往往标号后是for.while.do-while等循环。

通过用标号,我们可以对外层循环进行控制

flag1:for (int i = 0;i < 3;i++){
    flag2:for (int j = 0;j <4;j++){
        System.out.println("x=" + j);
        continue flag1;
    }
}

x=0
x=0
x=0

大数值

BigInteger 和 BigDecimal 可以处理包含任意长度数字序列的数值,实现了任意精度的整数运算和任意精度的浮点数运算。

注意:大数值不能使用运算符,要使用自带的方法进行算术运算。

BigInteger a = BigInteger.valueOf(1000);//将普通数值转为大数值
BigInteger c = a.add(b);//和
BigInteger c = a.subtract(b);//差
BigInteger c = a.multiply(b);//积
BigInteger c = a.divide(b);//商
BigInteger c = a.mod(b);//余数
int c = a.compareTo(b);//对比,相等返回0,小于b返回负数,大于b返回正数

数组

java中的数组是一组数据结构,用来存储同一类型的集合。通过一个整形下标可以访问数组中的每一个值。
声明及初始化:

int[] a;
int[] a = new int[100];//创建了一个长度为100的数组

初始化后的数组值为 null 。

获取某个数组的长度使用 length :

int len = a.length;

调用 toString() 方法可以按格式输出所有元素。

Arrays.toString(smallPrimes)

数组一旦创建就不能改变它的大小,如果要在运行中扩展数据,使用另一种数据结构——数组列表(Arraylist)

for each 循环

格式:

for (variable : collection ){
    statement;
}

int arr[] = {2, 3, 1};
for (int x : arr) { 
    System.out.println(x); //逐个输出数组元素的值 
}

不必为集合的初始值和终止值操心。

数组初始化以及匿名数组

初始化简化方式:

int[] smallPrimes = { 2, 3, 4, 5 };
new int[] { 2, 3, 4, 5 };//匿名数组
smallPrimes = new int[] { 2, 3, 4, 5 };//重新初始化

简化初始化数组可以省略 new ,数组重新初始化要用匿名数组。

数据长度为 0 的方法:

int[] a1 = new int[0];
int[] a2 = {};
System.out.println(a1.length);
System.out.println(a2.length);

数组拷贝 copyOf

讲一个数组变量拷贝到另一个数组变量,这时,两个变量将引用同一个数组。
其中一个改变值时,另一个也改变对应的值。

int[] a = {1,2,3};
int[] b = a;
b[0] = 4;//此时a[0]也变为4

提供一个方法让数组拷贝后不会一起改变,可以拷贝数组并且变大或变小,变大时,整数类型变为0,布尔类型变为false,变小时,只拷贝最前面的数据元素。

int[] smallPrimes = {1,2,3,4,5};
smallPrimes = Arrays.copyOf(smallPrimes, 8);
System.out.println(Arrays.toString(smallPrimes));
// [1, 2, 3, 4, 5, 0, 0, 0]

命令行参数

每一个 java 应用程序都有一个带有 String arg[] 参数的 main 方法。

public static void main(String[] args)...

我们通过命令行参数执行 java 代码时,main 方法将接收一个字符串数组,也就是命令行参数。

如以这种形式运行程序:

java Message -g cruel world

此时,args 数组将包含下列内容:

arg[0]: "-g"
arg[1]: "cruel"
arg[2]: "world"

注意,args 数组不包含程序名。

数组排序和常用 API

可以使用自己的算法,或者自带优化的快速排序算法。

Arrays.sort(smallPrimes);

常用的数组 API:

//java.util.Arrays 1.2
int[] a = {1, 2, 3, 4, 5};

String name1 = Arrays.toString(a);
System.out.println(name1);//[1, 2, 3, 4, 5]

int[] name2 = Arrays.copyOf(a, 4);
System.out.println(Arrays.toString(name2));//[1, 2, 3, 4],拷贝函数

Arrays.sort(a);//无返回值,采用优化的快速排序算法对数组进行排序

int name3 = Arrays.binarySearch(a,3);//返回查找到的下标
System.out.println(name3);//2

int name4 = Arrays.binarySearch(a, 1, 3, 2);//查找起点、末尾、查找的值
System.out.println(name4);//1

Arrays.fill(a, 1);//所有值设置为1
System.out.println(Arrays.toString(a));//[1, 1, 1, 1, 1]

多维数组

跟一维数组差不多:

int[][] smallPrimes = new int[4][4];

int[][] smallPrimes = {
    {1, 2, 3, 4},
    {1, 2, 3, 4},
    {1, 2, 3, 4},
    {1, 2, 3, 4}
};

快速打印二维数组的方法:

String a = Arrays.deepToString(smallPrimes);
System.out.println(a);
//[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]

不规则数组

Java 实际上没有多维数组只有一维数组。多维数组解释为数组的数组

数组可以单独地存取数组的某一行,可以让两行交换,当然他们的类型要一样。

double[] temp = balances[i];
balances[i] = balances[i+1];
balances[i+1] = temp;

不规则数组,即第二维的数组长度不一样

首先分配数组:

int[][] odds = new int[NMAX + 1][];//先确定一维
for(int n = 0;n <= NMAX;n++){
    odds[n] = new int[n+1]//在确定二维
}

只能单独的创建不规则的数组。

总结:

  • 多维数组的类型要一致
  • 多维数组的长度可以不同
  • 多维数组的行可以交换
  • 只能单独创建不规则的数组

猜你喜欢

转载自www.cnblogs.com/lovezyu/p/9127447.html