四、运算符(3)

news2024/11/24 12:01:05

本章概要

  • 移位运算符
  • 三员运算符
  • 字符串运算符
  • 常见陷阱
  • 类型转换
    • 截断和舍入
    • 类型提升
  • Java 没有 sizeof
  • 总结

移位运算符

移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 << 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 >> 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。

如果移动 charbyteshort,则会在移动发生之前将其提升为 int,结果为 int。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 int 范围的位数。若对一个 long 值进行处理,最后得到的结果也是 long

移位可以与等号 <<=>>=>>>= 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 byteshort 一起使用的话,则结果错误。取而代之的是,它们被提升为 int 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:

// operators/URShift.java
// 测试无符号右移
public class URShift {
    public static void main(String[] args) {
        int i = -1;
        System.out.println(Integer.toBinaryString(i));
        i >>>= 10;
        System.out.println(Integer.toBinaryString(i));
        long l = -1;
        System.out.println(Long.toBinaryString(l));
        l >>>= 10;
        System.out.println(Long.toBinaryString(l));
        short s = -1;
        System.out.println(Integer.toBinaryString(s));
        s >>>= 10;
        System.out.println(Integer.toBinaryString(s));
        byte b = -1;
        System.out.println(Integer.toBinaryString(b));
        b >>>= 10;
        System.out.println(Integer.toBinaryString(b));
        b = -1;
        System.out.println(Integer.toBinaryString(b));
        System.out.println(Integer.toBinaryString(b>>>10));
    }
}

输出结果:

11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111

在上例中,结果并未重新赋值给变量 b ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:

// operators/BitManipulation.java
// 使用位运算符
import java.util.*;
public class BitManipulation {
    public static void main(String[] args) {
        Random rand = new Random(47);
        int i = rand.nextInt();
        int j = rand.nextInt();
        printBinaryInt("-1", -1);
        printBinaryInt("+1", +1);
        int maxpos = 2147483647;
        printBinaryInt("maxpos", maxpos);
        int maxneg = -2147483648;
        printBinaryInt("maxneg", maxneg);
        printBinaryInt("i", i);
        printBinaryInt("~i", ~i);
        printBinaryInt("-i", -i);
        printBinaryInt("j", j);
        printBinaryInt("i & j", i & j);
        printBinaryInt("i | j", i | j);
        printBinaryInt("i ^ j", i ^ j);
        printBinaryInt("i << 5", i << 5);
        printBinaryInt("i >> 5", i >> 5);
        printBinaryInt("(~i) >> 5", (~i) >> 5);
        printBinaryInt("i >>> 5", i >>> 5);
        printBinaryInt("(~i) >>> 5", (~i) >>> 5);
        long l = rand.nextLong();
        long m = rand.nextLong();
        printBinaryLong("-1L", -1L);
        printBinaryLong("+1L", +1L);
        long ll = 9223372036854775807L;
        printBinaryLong("maxpos", ll);
        long lln = -9223372036854775808L;
        printBinaryLong("maxneg", lln);
        printBinaryLong("l", l);
        printBinaryLong("~l", ~l);
        printBinaryLong("-l", -l);
        printBinaryLong("m", m);
        printBinaryLong("l & m", l & m);
        printBinaryLong("l | m", l | m);
        printBinaryLong("l ^ m", l ^ m);
        printBinaryLong("l << 5", l << 5);
        printBinaryLong("l >> 5", l >> 5);
        printBinaryLong("(~l) >> 5", (~l) >> 5);
        printBinaryLong("l >>> 5", l >>> 5);
        printBinaryLong("(~l) >>> 5", (~l) >>> 5);
    }

    static void printBinaryInt(String s, int i) {
        System.out.println(
        s + ", int: " + i + ", binary:\n " +
        Integer.toBinaryString(i));
    }

    static void printBinaryLong(String s, long l) {
        System.out.println(
        s + ", long: " + l + ", binary:\n " +
        Long.toBinaryString(l));
    }
}

输出结果(前 32 行):

-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
1
maxpos, int: 2147483647, binary:
1111111111111111111111111111111
maxneg, int: -2147483648, binary:
10000000000000000000000000000000
i, int: -1172028779, binary:
10111010001001000100001010010101
~i, int: 1172028778, binary:
 1000101110110111011110101101010
-i, int: 1172028779, binary:
1000101110110111011110101101011
j, int: 1717241110, binary:
1100110010110110000010100010110
i & j, int: 570425364, binary:
100010000000000000000000010100
i | j, int: -25213033, binary:
11111110011111110100011110010111
i ^ j, int: -595638397, binary:
11011100011111110100011110000011
i << 5, int: 1149784736, binary:
1000100100010000101001010100000
i >> 5, int: -36625900, binary:
11111101110100010010001000010100
(~i) >> 5, int: 36625899, binary:
10001011101101110111101011
i >>> 5, int: 97591828, binary:
101110100010010001000010100
(~i) >>> 5, int: 36625899, binary:
10001011101101110111101011
    ...

结尾的两个方法 printBinaryInt()printBinaryLong() 分别操作一个 intlong 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 intlong 的所有位运算符的效果之外,本示例还显示 intlong 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 int 部分的输出。以上数字的二进制表示形式是带符号的补码(2’s complement)。

三元运算符

三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 if-else 语句是不同的。下面是它的表达式格式:

布尔表达式 ? 值 1 : 值 2

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

当然,也可以换用普通的 if-else 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 if-else 不同的是,三元运算符是有返回结果的。请看下面的代码示例:

// operators/TernaryIfElse.java
public class TernaryIfElse {

    static int ternary(int i) {
        return i < 10 ? i * 100 : i * 10;
    }

    static int standardIfElse(int i) {
        if (i < 10) {
            return i * 100;
        } else {
            return i * 10;
        }
    }

    public static void main(String[] args) {
        System.out.println(ternary(9));
        System.out.println(ternary(10));
        System.out.println(standardIfElse(9));
        System.out.println(standardIfElse(10));
    }
}

输出结果:

900
100
900
100

可以看出,ternary() 中的代码更简短。然而,standardIfElse() 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。

字符串运算符

这个运算符在 Java 里有一项特殊用途:连接字符串。这点已在前面展示过了。尽管与 + 的传统意义不符,但如此使用也还是比较自然的。这一功能看起来还不错,于是在 C++ 里引入了“运算符重载”机制,以便 C++ 程序员为几乎所有运算符增加特殊的含义。但遗憾得是,与 C++ 的一些限制结合以后,它变得复杂。这要求程序员在设计自己的类时必须对此有周全的考虑。虽然在 Java 中实现运算符重载机制并非难事(如 C# 所展示的,它具有简单的运算符重载),但因该特性过于复杂,因此 Java 并未实现它。

我们注意到运用 String + 时有一些有趣的现象。若表达式以一个 String 类型开头(编译器会自动将双引号 "" 标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。代码示例:

// operators/StringOperators.java
public class StringOperators {
    public static void main(String[] args) {
        int x = 0, y = 1, z = 2;
        String s = "x, y, z ";
        System.out.println(s + x + y + z);
        // 将 x 转换为字符串
        System.out.println(x + " " + s);
        s += "(summed) = "; 
        // 级联操作
        System.out.println(s + (x + y + z));
        // Integer.toString()方法的简写:
        System.out.println("" + x);
    }
}

输出结果:

在这里插入图片描述

注意:上例中第 1 输出语句的执行结果是 012 而并非 3,这是因为编译器将其分别转换为其字符串形式然后与字符串变量 s 连接。在第 2 条输出语句中,编译器将开头的变量转换为了字符串,由此可以看出,这种转换与数据的位置无关,只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接。最后一条输出语句,我们可以看出 += 运算符可以拼接其右侧的字符串连接结果并重赋值给自身变量 s。括号 () 可以控制表达式的计算顺序,以便在显示 int 之前对其进行实际求和。

请注意主方法中的最后一个例子:我们经常会看到一个空字符串 "" 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 Integer.toString())。

常见陷阱

使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 ()。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:

while(x = y) {
// ...
}

显然,程序员原意是测试等价性 ==,而非赋值 =。若变量 y 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 true。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 int 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。

唯一有种情况例外:当变量 xy 都是布尔值,例如 x=y 是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。

在 C/C++ 里,类似的一个问题还有使用按位“与” & 和“或” | 运算,而非逻辑“与” && 和“或” ||。就象 === 一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。

类型转换

“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为 float 变量赋值一个整数值,计算机会将 int 自动转换成 float。我们可以在程序未自动转换时显式、强制地使此类型发生转换。

要执行强制转换,需要将所需的数据类型放在任何值左侧的括号内,如下所示:

// operators/Casting.java
public class Casting {
    public static void main(String[] args) {
        int i = 200;
        long lng = (long) i;
        lng = i; // 没有必要的类型提升
        long lng2 = (long) 200;
        lng2 = 200;
        // 类型收缩
        i = (int) lng2; // Cast required
    }
}

诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 int 型数据为 long 型。

当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(Narrowing Conversion)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(Widening conversion),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。

除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。

截断和舍入

在执行“向下转换”时,必须注意数据的截断和舍入问题。若从浮点值转换为整型值,Java 会做什么呢?例如:浮点数 29.7 被转换为整型值,结果会是 29 还是 30 呢?下面是代码示例:

// operators/CastingNumbers.java
// 尝试转换 float 和 double 型数据为整型数据
public class CastingNumbers {
    public static void main(String[] args) {
        double above = 0.7, below = 0.4;
        float fabove = 0.7f, fbelow = 0.4f;
        System.out.println("(int)above: " + (int) above);
        System.out.println("(int)below: " + (int) below);
        System.out.println("(int)fabove: " + (int) fabove);
        System.out.println("(int)fbelow: " + (int) fbelow);
    }
}

输出结果:

在这里插入图片描述

因此,答案是,从 floatdouble 转换为整数值时,小数位将被截断。若你想对结果进行四舍五入,可以使用 java.lang.Mathround() 方法:

// operators/RoundingNumbers.java
// float 和 double 类型数据的四舍五入
public class RoundingNumbers {
    public static void main(String[] args) {
        double above = 0.7, below = 0.4;
        float fabove = 0.7f, fbelow = 0.4f;
        System.out.println("Math.round(above): " + Math.round(above));
        System.out.println("Math.round(below): " + Math.round(below));
        System.out.println("Math.round(fabove): " + Math.round(fabove));
        System.out.println("Math.round(fbelow): " + Math.round(fbelow));
    }
}

输出结果:

在这里插入图片描述

因为 round() 方法是 java.lang 的一部分,所以我们无需通过 import 就可以使用。

类型提升

你会发现,如果我们对小于 int 的基本数据类型(即 charbyteshort)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 int,并且结果值的类型为 int。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。float 型和 double 型相乘,结果是 double 型的;intlong 相加,结果是 long 型。

Java没有sizeof

在 C/C++ 中,经常需要用到 sizeof() 方法来获取数据项被分配的字节大小。C/C++ 中使用 sizeof() 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。

Java 不需要 sizeof() 方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。

总结

上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 // 过滤了。代码示例:

// operators/AllOps.java
// 测试所有基本类型的运算符操作
// 看看哪些是能被 Java 编译器接受的
public class AllOps {
    // 布尔值的接收测试:
    void f(boolean b) {
    }

    void boolTest(boolean x, boolean y) {
        // 算数运算符:
        //- x = x * y;
        //- x = x / y;
        //- x = x % y;
        //- x = x + y;
        //- x = x - y;
        //- x++;
        //- x--;
        //- x = +y;
        //- x = -y;
        // 关系运算符和逻辑运算符:
        //- f(x > y);
        //- f(x >= y);
        //- f(x < y);
        //- f(x <= y);
        f(x == y);
        f(x != y);
        f(!y);
        x = x && y;
        x = x || y;
        // 按位运算符:
        //- x = ~y;
        x = x & y;
        x = x | y;
        x = x ^ y;
        //- x = x << 1;
        //- x = x >> 1;
        //- x = x >>> 1;
        // 联合赋值:
        //- x += y;
        //- x -= y;
        //- x *= y;
        //- x /= y;
        //- x %= y;
        //- x <<= 1;
        //- x >>= 1;
        //- x >>>= 1;
        x &= y;
        x ^= y;
        x |= y;
        // 类型转换:
        //- char c = (char)x;
        //- byte b = (byte)x;
        //- short s = (short)x;
        //- int i = (int)x;
        //- long l = (long)x;
        //- float f = (float)x;
        //- double d = (double)x;
    }

    void charTest(char x, char y) {
        // 算数运算符:
        x = (char) (x * y);
        x = (char) (x / y);
        x = (char) (x % y);
        x = (char) (x + y);
        x = (char) (x - y);
        x++;
        x--;
        x = (char) +y;
        x = (char) -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        x = (char) ~y;
        x = (char) (x & y);
        x = (char) (x | y);
        x = (char) (x ^ y);
        x = (char) (x << 1);
        x = (char) (x >> 1);
        x = (char) (x >>> 1);
        // 联合赋值:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        x <<= 1;
        x >>= 1;
        x >>>= 1;
        x &= y;
        x ^= y;
        x |= y;
        // 类型转换
        //- boolean bl = (boolean)x;
        byte b = (byte) x;
        short s = (short) x;
        int i = (int) x;
        long l = (long) x;
        float f = (float) x;
        double d = (double) x;
    }

    void byteTest(byte x, byte y) {
        // 算数运算符:
        x = (byte) (x * y);
        x = (byte) (x / y);
        x = (byte) (x % y);
        x = (byte) (x + y);
        x = (byte) (x - y);
        x++;
        x--;
        x = (byte) +y;
        x = (byte) -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        x = (byte) ~y;
        x = (byte) (x & y);
        x = (byte) (x | y);
        x = (byte) (x ^ y);
        x = (byte) (x << 1);
        x = (byte) (x >> 1);
        x = (byte) (x >>> 1);
        // 联合赋值:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        x <<= 1;
        x >>= 1;
        x >>>= 1;
        x &= y;
        x ^= y;
        x |= y;
        // 类型转换:
        //- boolean bl = (boolean)x;
        char c = (char) x;
        short s = (short) x;
        int i = (int) x;
        long l = (long) x;
        float f = (float) x;
        double d = (double) x;
    }

    void shortTest(short x, short y) {
        // 算术运算符:
        x = (short) (x * y);
        x = (short) (x / y);
        x = (short) (x % y);
        x = (short) (x + y);
        x = (short) (x - y);
        x++;
        x--;
        x = (short) +y;
        x = (short) -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        x = (short) ~y;
        x = (short) (x & y);
        x = (short) (x | y);
        x = (short) (x ^ y);
        x = (short) (x << 1);
        x = (short) (x >> 1);
        x = (short) (x >>> 1);
        // Compound assignment:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        x <<= 1;
        x >>= 1;
        x >>>= 1;
        x &= y;
        x ^= y;
        x |= y;
        // 类型转换:
        //- boolean bl = (boolean)x;
        char c = (char) x;
        byte b = (byte) x;
        int i = (int) x;
        long l = (long) x;
        float f = (float) x;
        double d = (double) x;
    }

    void intTest(int x, int y) {
        // 算术运算符:
        x = x * y;
        x = x / y;
        x = x % y;
        x = x + y;
        x = x - y;
        x++;
        x--;
        x = +y;
        x = -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        x = ~y;
        x = x & y;
        x = x | y;
        x = x ^ y;
        x = x << 1;
        x = x >> 1;
        x = x >>> 1;
        // 联合赋值:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        x <<= 1;
        x >>= 1;
        x >>>= 1;
        x &= y;
        x ^= y;
        x |= y;
        // 类型转换:
        //- boolean bl = (boolean)x;
        char c = (char) x;
        byte b = (byte) x;
        short s = (short) x;
        long l = (long) x;
        float f = (float) x;
        double d = (double) x;
    }

    void longTest(long x, long y) {
        // 算数运算符:
        x = x * y;
        x = x / y;
        x = x % y;
        x = x + y;
        x = x - y;
        x++;
        x--;
        x = +y;
        x = -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        x = ~y;
        x = x & y;
        x = x | y;
        x = x ^ y;
        x = x << 1;
        x = x >> 1;
        x = x >>> 1;
        // 联合赋值:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        x <<= 1;
        x >>= 1;
        x >>>= 1;
        x &= y;
        x ^= y;
        x |= y;
        // 类型转换:
        //- boolean bl = (boolean)x;
        char c = (char) x;
        byte b = (byte) x;
        short s = (short) x;
        int i = (int) x;
        float f = (float) x;
        double d = (double) x;
    }

    void floatTest(float x, float y) {
        // 算数运算符:
        x = x * y;
        x = x / y;
        x = x % y;
        x = x + y;
        x = x - y;
        x++;
        x--;
        x = +y;
        x = -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        //- x = ~y;
        //- x = x & y;
        //- x = x | y;
        //- x = x ^ y;
        //- x = x << 1;
        //- x = x >> 1;
        //- x = x >>> 1;
        // 联合赋值:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        //- x <<= 1;
        //- x >>= 1;
        //- x >>>= 1;
        //- x &= y;
        //- x ^= y;
        //- x |= y;
        // 类型转换:
        //- boolean bl = (boolean)x;
        char c = (char) x;
        byte b = (byte) x;
        short s = (short) x;
        int i = (int) x;
        long l = (long) x;
        double d = (double) x;
    }

    void doubleTest(double x, double y) {
        // 算术运算符:
        x = x * y;
        x = x / y;
        x = x % y;
        x = x + y;
        x = x - y;
        x++;
        x--;
        x = +y;
        x = -y;
        // 关系和逻辑运算符:
        f(x > y);
        f(x >= y);
        f(x < y);
        f(x <= y);
        f(x == y);
        f(x != y);
        //- f(!x);
        //- f(x && y);
        //- f(x || y);
        // 按位运算符:
        //- x = ~y;
        //- x = x & y;
        //- x = x | y;
        //- x = x ^ y;
        //- x = x << 1;
        //- x = x >> 1;
        //- x = x >>> 1;
        // 联合赋值:
        x += y;
        x -= y;
        x *= y;
        x /= y;
        x %= y;
        //- x <<= 1;
        //- x >>= 1;
        //- x >>>= 1;
        //- x &= y;
        //- x ^= y;
        //- x |= y;
        // 类型转换:
        //- boolean bl = (boolean)x;
        char c = (char) x;
        byte b = (byte) x;
        short s = (short) x;
        int i = (int) x;
        long l = (long) x;
        float f = (float) x;
    }
}

注意boolean 类型的运算是受限的。你能为其赋值 truefalse,也可测试它的值是否是 truefalse。但你不能对其作加减等其他运算。

charbyteshort 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 int 类型的运算则不用转换,因为默认就是 int 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 int 型的范围,这种情况下结果会发生溢出。下面的代码示例:

// operators/Overflow.java
// 厉害了!内存溢出
public class Overflow {
    public static void main(String[] args) {
        int big = Integer.MAX_VALUE;
        System.out.println("big = " + big);
        int bigger = big * 4;
        System.out.println("bigger = " + bigger);
    }
}

输出结果:

big = 2147483647
bigger = -4

编译器没有报错或警告,运行时一切看起来都无异常。诚然,Java 是优秀的,但是还不足够优秀。

对于 charbyte 或者 short,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 boolean 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/796150.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python_day15_闭包

闭包 def outer(logo):def inner(msg):print(f"<{logo}>{msg}<{logo}>")return innerfunc_1 outer("####") print(type(func_1)) func_1("jsy")func_2 outer("$$$$$") func_2(hello)nonlocal关键字&#xff0c;在闭包中…

C#|无法打开cs文件设计窗口

报错信息&#xff1a;To prevent possible data loss before loading the designer, the following errors must be resolved: 解决方案&#xff1a;实不相瞒我把项目解决方案名称改短了就可以了。。有其他原因或者解决方案望不吝赐教。。

【Huawei】WLAN实验(三层发现)

拓扑图如上&#xff0c;AP与S1在同一VLAN,S1与AC在同一VLAN&#xff0c;AP采用三层发现AC&#xff0c;AP与客户的DHCP由S1提供。 S1配置 vlan batch 10 20 30 dhcp enable ip pool apgateway-list 192.168.20.1network 192.168.20.0 mask 255.255.255.0option 43 sub-option …

详细介绍Webpack5中的Loader

loader用于帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。 优先级分类 pre&#xff1a; 前置 loader normal&#xff1a; 普通 loader&#xff0c;没有用enforce指定就是normal inline&#xff1a; 内联 loader post&#xff1a; 后置 loader loader执行顺序…

Vue实现柱状图横向自动滚动

Vue实现柱状图横向自动滚动 1. 前言2. 代码3、实现效果图 1. 前言 原理&#xff1a;通过定时器修改Echarts的配置&#xff08;options&#xff09;达到我们想要的效果。 此外&#xff0c;我们还需要了解Echarts中dataZoom这个组件&#xff0c;这个组件用于&#xff1a;用于区域…

几张图教你电商入门:如何做数据分析

电商API和电商数据分析是相辅相成的&#xff0c;它们一起为电商平台的发展和运营提供了重要支持。 电商API&#xff08;Application Programming Interface&#xff09;提供了丰富的功能和数据接口&#xff0c;使得开发者能够在电商平台上进行二次开发和定制化操作。比如&…

C++多线程编程(第二章 多线程通信和同步)

1、多线程状态 1.1线程状态说明 初始化&#xff08;Init&#xff09;:该线程正在被创建&#xff1b; 就绪&#xff08;Ready&#xff09;:该线程在就绪列表中&#xff0c;等待CPU调度&#xff1b; 运行&#xff08;Running&#xff09;:该线程正在运行&#xff1b; 阻塞&…

idea下tomcat运行乱码问题解决方法

idea虚拟机选项添加-Dfile.encodingUTF-8

yaml-cpp YAML格式处理库的介绍和使用(面向业务编程-文件格式处理)

YAML格式介绍 YAML的格式介绍&#xff0c;有关ini、json和xml或许很多人已经很了解了&#xff0c;但是关于YAML&#xff0c;还有许多人不了解。YAML被设计成更适合人类阅读&#xff08;我想正因为如此&#xff0c;所以相对来说更灵活&#xff0c;就导致到使用的时候很多人会觉…

PostMan+Jmeter+QTP工具介绍及安装

目录 一、PostMan介绍​编辑 二、下载安装 三、Postman与Jmeter的区别 一、开发语言区别&#xff1a; 二、使用范围区别&#xff1a; 三、使用区别&#xff1a; 四、Jmeter安装 附一个详细的Jmeter按照新手使用教程&#xff0c;感谢作者&#xff0c;亲测有效。 五、Jme…

python_day14_综合案例

文件内容 导包配置 import jsonfrom pyspark import SparkContext, SparkConf import osos.environ["PYSPARK_PYTHON"] "D:/dev/python/python3.10.4/python.exe" os.environ["HADOOP_HOME"] "D:/dev/hadoop-3.0.0" conf SparkC…

Megatron-LM、NVIDIA NeMo、model_optim_rng.pt 文件是什么?

本文涉及以下几个概念&#xff0c;分别是&#xff1a; Megatron和Megatron-LM-v1.1.5-3D_parallelism NVIDIA NeMo Megatron和Megatron-LM-v1.1.5-3D_parallelism是什么&#xff1f; Megatron是由NVIDIA开发的一种用于训练大规模语言模型的开源框架。它旨在提供高效的分布式…

鸿蒙4.0重大官宣!六大功能呼之欲出!

鸿蒙系统是当年华为为应对美国封杀而开发的操作系统,当时只是权宜之计&#xff0c;但没想到它的发展壮大远远超出了人们的预期,2021年其用户就突破了1亿,去年7月搭载鸿蒙系统的设备超过了3亿。近日,华为官宣将在今年8月4号的开发者大会上发布全新的鸿蒙4.0系统,系统包含多项让人…

THM-被动侦察和主动侦察

被动与主动侦察# 在计算机系统和网络出现之前&#xff0c;孙子兵法在孙子兵法中教导说&#xff1a;“知己知彼&#xff0c;必胜不疑。” 如果您扮演攻击者的角色&#xff0c;则需要收集有关目标系统的信息。如果你扮演防御者的角色&#xff0c;你需要知道你的对手会发现你的系…

加利福尼亚大学|3D-LLM:将3D世界于大规模语言模型结合

来自加利福尼亚大学的3D-LLM项目团队提到&#xff1a;大型语言模型 (LLM) 和视觉语言模型 (VLM) 已被证明在多项任务上表现出色&#xff0c;例如常识推理。尽管这些模型非常强大&#xff0c;但它们并不以 3D 物理世界为基础&#xff0c;而 3D 物理世界涉及更丰富的概念&#xf…

视频监控管理平台EasyCVR录像的3种方式

视频监控综合管理平台EasyCVR可以实现海量资源的接入、汇聚、计算、存储、处理等&#xff0c;平台具备轻量化接入能力&#xff0c;可支持多协议方式接入&#xff0c;包括主流标准协议GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Eho…

深入理解 python 虚拟机:字节码灵魂——Code obejct

Code Object 数据结构 typedef struct {PyObject_HEADint co_argcount; /* #arguments, except *args */int co_kwonlyargcount; /* #keyword only arguments */int co_nlocals; /* #local variables */int co_stacksize; /* #entries needed for evaluation stack */int co_f…

Flutter 异步编程指南作

1 Dart 中的事件循环模型 在 App 开发中&#xff0c;经常会遇到处理异步任务的场景&#xff0c;如网络请求、读写文件等。Android、iOS 使用的是多线程&#xff0c;而在 Flutter 中为单线程事件循环&#xff0c;如下图所示 Dart 中有两个任务队列&#xff0c;分别为 microtask…

Windows 11的最新人工智能应用Windows Copilot面世!

Windows Copilot是Windows 11预览版中的一项AI辅助功能。 Windows 11还包括设置应用程序的更改&#xff0c;更广泛的支持压缩文件格式。 上个月&#xff0c;微软宣布将继续其将ChatGPT应用于所有产品的冒险之旅&#xff0c;推出了名为Copilot的新Windows 11功能。几个月前&…

难评的Worldcoin,正在登月中

7月24日&#xff0c;前段时间搅混加密舆论界一汪春水的Worldcoin正式上线代币WLD。 由Openai创始人Sam Altman牵头的Worldcoin项目在其试点之初就备受关注&#xff0c;而该种关注在其融资近一亿美金后更是空前&#xff0c;币种上架的呼声不断。 但当代币真的狼来了时&#xff0…