元知识#
学习编程语言,总会有一些共性的东西,基本上所有的编程语言都会很相似甚至会相同。 给这一章起名 “元知识” 是想抽取共性的部分,然后撮合成本文。 因此,如果你学习过其他高级语言,比如 C++,即使这一小节你不阅读,也应该知道的差不多。
数据类型#
Java 不支持无符号类型,所有的数值都是有符号的。
基本类型 |
大小 |
最小值 |
最大值 |
包装器类型 |
默认值 |
---|---|---|---|---|---|
|
– |
– |
– |
|
|
|
16-bit |
\(0\) |
\(2^{16}-1\) |
|
‘u0000’( |
|
8-bit |
\(-128\) |
\(+127\) |
|
( |
|
16-bit |
\(-2^{15}\) |
\(+2^{15}-1\) |
|
( |
|
32-bit |
\(-2^{31}\) |
\(+2^{31}-1\) |
|
0 |
|
64-bit |
\(-2^{63}\) |
\(+2^{63}-1\) |
|
0L |
|
32-bit |
IEEE754 |
IEEE754 |
|
0.0f |
|
64-bit |
IEEE754 |
IEEE754 |
|
0.0d |
|
– |
– |
– |
Void |
– |
|
– |
– |
– |
– |
– |
|
– |
– |
– |
– |
– |
引用 |
– |
– |
– |
– |
|
以上就是基本类型的数据了,我们也可以通过使用 直接常量表示法,让编译器可以准确地知道要生成什么类型的数据。
直接常量就是在数值的基础上加上 前缀 或 后缀。
在 C、C++、Java 中,二进制数 没有直接的常量表示方法,可以使用 Integer
或 Long
类的静态方法 toBinaryString()
来获得。
前缀 |
含义 |
后缀 |
含义 |
---|---|---|---|
|
八进制 |
|
|
|
十六进制 |
|
|
|
长整形数据 |
除了直接常量表示法,还有 指数表示法,用 e
来代表 10 的幂次,比如 \(47e47 = 4.7 \times 10^{48}\)
在代码中,可以直接给变量赋值为指数形式,比如 a = 47e47;
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名 1传指针和传指针引用的区别/指针和引用的区别(本质) [webpage]。
指针可以在运行时改变其所指向的值,引用一旦和某个对象绑定就不再改变;
从内存上看,指针会分配内存区域,而引用不会,它仅仅是一个别名;
在参数传递时,引⽤用会做类型检查,而指针不会;
引用不能为空,指针可以为空。
在 C++ 中,一个指针的大小可能是几个字节(跟机器的位数有关系),这个大小可以通过 sizeof()
来测量。
Java 不需要 sizeof()
,因为所有数据类型在所有机器中的大小都是相同的,这是Java 天然可移植带来的优势。
Java 不会自动将 int
数值转换成布尔值,因此如果 a
是一个数值,那么 if(a)
是不对的,而
if(a != 0)
是可以的,而 if(a)
这种行为在 C++ 中是会允许的。
如果我们想要手动进行类型转换,可以使用类似 (int)value
的语法将其他类型的数据转换为整形数据。
需要注意的是,除了 boolean
以外,其他基本类型之间都可以互相转换。
运算符和优先级#
以 C 语言中的运算符和优先级为例:
运算符及优先级 |
结合性 |
---|---|
\(\text{() [] -> .}\) |
从左到右 |
\(\text{! ~ ++ -- + - * (type) sizeof}\) |
从右到左 |
\(\text{* / %}\) |
从左到右 |
\(\text{+ -}\) |
从左到右 |
\(\text{<< >>}\) |
从左到右 |
\(\text{< <= > >=}\) |
从左到右 |
\(\text{== ! =}\) |
从左到右 |
\(\text{&}\) |
从左到右 |
\(\text{^}\) |
从左到右 |
\(\text{|}\) |
从左到右 |
\(\text{&&}\) |
从左到右 |
\(\text{||}\) |
从左到右 |
\(\text{?:}\) |
从左到右 |
\(\text{= += -= *= /= %= &= ^= |= <<= >>=}\) |
从右到左 |
\(\text{,}\) |
从右到左 |
Note
一元运算符的 \(\text{+ - & *}\) 的优先级比二元运算符 \(\text{+ - & *}\) 的优先级高。
以上是的常见的运算符,但是,在 Java 中有几点需要注意:
当编译器观察到一个 String
后面紧跟一个 +
,而这个 +
的后面又紧跟一个非 String
类型的元素时,就会尝试将这个非 String
类型的元素转换为 String
,对于类来说,会自动调用
toString()
方法。
如果在应该使用 String
值的地方使用了布尔值,布尔值会自动转换为适当的文本形式,即自动转换为
True
和 False
(enum
类可以达到同样的目标)。
在为对象赋值时,真正操作的是对象的引用。实际上就是将对象的引用从一个地方复制到了另一个地方。 最典型的场景就是当对象作为函数的参数传递时。
使用比较运算符时,基本类型直接使用 ==
或 !=
即可,判断浮点数是否为 0 是非常严格的,只要比
0 大一点点,它仍然是非零的。而比较对象时,实际上比较的是对象的引用。
比较对象内容是否相等,需要使用 equals()
方法,但是当你创建自己的类时,需要重写 equals()
方法,否则,无法直接比较内容是否相等。如下代码所示:
//: operators/Equivalence.java
public class Equivalence {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1 == n2);
System.out.println(n1.equals(n2));
}
} /* Output:
false
true
*///:~
逻辑操作符在参与运算时,存在短路现象。
移位操作符只可用来处理整数类型:
左移操作:在低位补 0
有符号的右移操作:若符号为正,则在高位补 0
有符号的右移操作:若符号为负,则在高位补 1
无符号的右移操作:无论正负,在高位补 0
对 char
、byte
、short
类型的数值进行移位处理时,移位之前,编译器会将其自动转换为
int
类型。并且得到的结果也是 int
类型。如下所示:
1//: operators/URShift.java
2// Test of unsigned right shift.
3import static net.mindview.util.Print.*;
4
5public class URShift {
6 public static void main(String[] args) {
7 int i = -1;
8 print("int: " + Integer.toBinaryString(i));
9 i >>>= 10;
10 print("int: " + Integer.toBinaryString(i));
11 long l = -1;
12 print("long: " + Long.toBinaryString(l));
13 l >>>= 10;
14 print("long: " + Long.toBinaryString(l));
15 short s = -1;
16 print("Short: " + Integer.toBinaryString(s));
17 s >>>= 10;
18 print("Short: " + Integer.toBinaryString(s));
19 byte b = -1;
20 print("byte: " + Integer.toBinaryString(b));
21 b >>>= 10;
22 print("byte: " + Integer.toBinaryString(b));
23 b = -1;
24 print("byte: " + Integer.toBinaryString(b));
25 print("byte: " + Integer.toBinaryString(b>>>10));
26 }
27} /* Output:
28int: 11111111111111111111111111111111
29int: 1111111111111111111111
30long: 1111111111111111111111111111111111111111111111111111111111111111
31long: 111111111111111111111111111111111111111111111111111111
32Short: 11111111111111111111111111111111
33Short: 11111111111111111111111111111111
34byte: 11111111111111111111111111111111
35byte: 11111111111111111111111111111111
36byte: 11111111111111111111111111111111
37byte: 1111111111111111111111
38*///:~
上面代码中的 int
和 long
类型的数据表现比较正常,一个 32 位,一个 64 位,右移后,减少了 10
位。short
和 byte
类型由于在右移操作处理前和处理后的结果都会自动转换为 int
类型,所以看起来都是 32 位的,并没有发生什么变化,但这并 不是我们预期 想要的结果。
注意到第 25 行代码,没有把结果赋值给 b
,而是直接打印出来,所以其结果是正确的。
Java 中 唯一用到 逗号操作符的地方就是 for
循环的控制表达式了。
可以在 for
循环的 initializaiton 和 step 中书写多个表达式,然后用逗号分隔开。
for (initializaiton; Boolean-expression; step) {
statements;
}
foreach#
foreach
可以用于任何 Iterable
对象,因此可以用于数组和容器这种已经实现了 Iterable
接口的对象。
不必创建 int
变量去对由访问项构成的序列进行计数,foreach
将自动产生每一项。
//: control/ForEachInt.java
import static net.mindview.util.Range.*;
import static net.mindview.util.Print.*;
public class ForEachInt {
public static void main(String[] args) {
for(int i : range(10)) // 0..9
printnb(i + " ");
print();
for(int i : range(5, 10)) // 5..9
printnb(i + " ");
print();
for(int i : range(5, 20, 3)) // 5..20 step 3
printnb(i + " ");
print();
}
} /* Output:
0 1 2 3 4 5 6 7 8 9
5 6 7 8 9
5 8 11 14 17
*///:~
switch#
switch
根据 integral-selector
(整数选择因子)产生的整数值,与 case
中的情况进行比较,如果符合,执行相应的 statement
。
若 case
全都不匹配,就执行 default
语句。
switch(integral-selector) {
case integral-value1: statement; break;
case integral-value2: statement; break;
// ...
default: statement;
}