Java编程入门(2.5):表达式

这一节会更深入地探讨表达式。回忆一下,表达式是一段用来表示或计算值的代码。表达式可以是文字、变量、函数调用或者这些的组合,由 +、> 这样的运算符连接到一起。表达式的值可以赋给变量,在子程序调用中用作参数,或者与其它的值组合成为更复杂的表达式。(这些值在某些情况下甚至可以被忽略掉,如果你想要这么干;这比你想象中更加常见)。表达式是编程的基础。到目前为止,本书都只是顺带提到了表达式。这一节会告诉你一个完整的故事(这里会忽略一些不常见的运算符)。

表达式的基础由文字(比如674、3.14、true 和 ‘X’)、变量和函数调用组成。还记得函数是一个带返回值的子程序。你可能已经看到过一些函数的例子,比如 TextIO 类的输入程序以及 Math 类的数学计算函数。

Math 类还包含了一组数学常量,在数学表达式中非常有用:Math.PI 表示 π (圆周率)、 Math.E 表示 e(自然对数的基数)。这些“常量”实际上都是 Math 类中 double 类型的成员变量。它们是数学常量的近似值,实际的精确值要求无限长度的数字。标准的 Integer 类包含了一组与 int 数据类型相关的常量:Integer.MAX_VALUE 是最大的 int,2147483647;Integer.MIN_VALUE 是最小的 int,-2147483648。类似地,Double 类包含了一些与 double 类型相关的常量。Double.MAX_VALUE 是最大的 double 值,而 Double.MIN_VALUE 是最小的值。Double 还包含了表示无限的数值,Double.POSITIVE_INFINITY 和 Double.NEGATIVE_INFINITY。而特殊的 Double.NaN 表示未定义值。例如,Math.sqrt(-1)  的结果就是 Double.NaN。

文字、变量和函数调用都是简单表达式。通过运算符可以将简单表达式组合成复杂表达式。运算符包括 + 将两个数值相加,> 比较两个值大小,等等。当表达式中包含了若干运算符时,就会出现优先顺序问题,它决定了运算符在计算式如何分组。例如,在表达式 “A + B * C” 中,B*C 会先计算,然后结果再与 A 相加。我们说,乘法  (*) 的优先级比加法  (+) 高。如果默认的优先级顺序不是你想要的,那么可以使用括号明确指定你期望的分组。例如,你可以使用”(A + B) * C” 表明希望先将 A 与 B 相加再乘以 C。

这一节的后面会对Java中的运算符细节进行详细地解释。Java提供了很多运算符,我不会每个都进行介绍,但是这里会给出大多数最重要的运算符说明。

2.5.1 算数运算符

算数运算符包括加法、减法、乘法和除法。它们的符号分别是 +、-、* 和  /。这些操作可以用于任意类型的数值:byte、short、int、long、float或double。(它们还可以用在 char 类型上,在这种情况下 char 被当做 integer 使用;char 会被转换成它的 Unicode 代码,并代入算数运算符操作。)当计算机实际计算时,计算中的两个值必须是相同类型。如果你的程序告诉计算机输入了两个不同类型的值,那么计算机会将其中一个转换成另一个的类型。例如,计算 37.4 + 10 这个表达式时,计算机会将整数 10 转换成实数 10.0,然后计算 37.4 + 10.0。这被称为类型转换。通常,你不必关心表达式中的类型转换,因为计算机会替你自动完成。

两个数值(如果必要,会对其中一个进行类型转换)计算后的结果与其类型一致。如果两个 int 相乘,结果是 int;两个 double 相乘,结果是 double,这是可预见的结果。但是,当你使用 / 时必须非常小心。如果两个 int 相除,结果是 int;如果商是分数,会被舍去。例如,7/2 结果是 3,而不是 3.5。假设N是整型变量,那么 N/100 结果不是整数,并且当 N 大于 1 时 1/N 等于 0!这是很多常见编程错误的根源。可以把其中一个运算符改为实数,强迫计算机输出实数:比如,当计算机处理 1.0/N 时,首先把N转成实数,从而与 1.0 的类型匹配,这样得到的结果就是实数。

Java还提供了计算除法操作的余数。计算余数的运算符为 %。如果 A 和 B 都是整数,那么 A % B 表示 A 除以 B 的余数。(然而,对于负数Java中的 % 与数学计算中的“取模”操作不同。在Java中,如果 A 或 B 为负数,那么 A % B 的结果也是负数)。例如,7 % 2 等于 1,34577 % 100 等于 77,50 % 8 等于 2。% 常被用来测试给定整数是奇数还是偶数:如果 N % 2 等于0,那么N是偶数;如果 N % 2 等于 1,那么N是奇数。一般来说,你可以通过 N % M 结果是否为 0,判断整数N是否可以被M取模。

% 运算符也适用于实数。通常,A % B 表示从 A 中移除多个 B 后遗留的数值。例如,7.52 % 0.5 等于 0.02。

最后,你还可能需要一元减法运算符,得到一个数的负数。例如,-X 等价于 (-1)*X。出于完备性考虑,Java还提供了一元加法运算符,例如 +X。景观在实际中没有任何作用。

顺便说一下,+ 操作还可以用来向 String 字符串连接任意类型的值。当你使用 + 连接字符串时,这是另一种形式的类型转换,任意的对象都会自动转换为 String。

2.5.2 增加和减少

你会发现,为变量加 1 是编程中极其常见的操作,为变量减 1 也一样。可以像下面这样赋值为变量加 1:

counter  =  counter + 1;
goalsScored  =  goalsScored + 1;

x = x + 1 语句的结果是,用变量 x 原来的值加 1 后再赋值给变量 x。也可以用 x++ 得到相同的效果(或者你可能会喜欢写成 ++x)。实际上,这么写会改变 x 的值,得到的效果与 “x = x + 1″ 一样。上面的两个语句可以改为:

counter++;
goalsScored++;

类似地,你也可以写 x–(或 –x)从 x 中减1。也就是说,x– 与 x = x - 1 执行了相同的计算。向变量加 1 称为变量递增,从变量减 1 称为变量递减。运算符 ++ 和 — 分别被称为递增运算符和递减运算符。这些运算符可以用于任何数值类型的变量,以及char类型的变量( ‘A’++  结果是 ‘B’)。

通常,运算符 ++ 和 — 用在语句中,比如 “x++;”  或 “x–;”。这些语句是改变 x 值的指令。然而,将x++、++x、x–或–x作为表达式或表达式的一部分也是合法的。也就是说,你可写出下面的代码:

y = x++;
y = ++x;
TextIO.putln(--x);
z = (++x) * (y--);

“y = x++;”的效果是 x 变量加1,然后把某个值赋给 y。赋给 y 的值是表达式 x 加 1 之前的值。因此,假设 x 等于 6,那么 “y = x++;” 执行后,会将 x 变为 7,但是 y 的值被赋为 6。因为赋给 y 的是 x 加 1 前的旧值。而另一种写法,++x 会得到加1后的新值。所以,还是假设 x 等于 6, ”y = ++x;” 会把 x 和 y 都变为 7。运算符 — 也是类似的用法。

特别要注意,x = x++; 这个语句没有改变 x 的值!这是因为赋给 x 的是 x 的旧值,即在语句执行前 x 的值。最终结果是,x 增加了 1,但是马上被改回了原来的值!你还需要记住,x++ 不等同 于 x + 1。表达式 x++ 改变了 x 的值,而 x + 1 没有改变。

这里会让你感到困惑,我从学生程序中看到很多由此造成的bug。我的建议是:不要写这种让人困惑的代码。++ 和 — 只在单独的语句使用,不要用成表达式。在接下来的示例中,我会遵循这条建议。

2.5.3 关系运算符

Java提供了布尔比例和布尔表达式表示条件,条件结果可以为 true 或 false。组织布尔表达式可以通过关系运算符比较两个值。

A == B       Is A "equal to" B?
A != B       Is A "not equal to" B?
A < B        Is A "less than" B?
A > B        Is A "greater than" B?
A <= B       Is A "less than or equal to" B?
A >= B       Is A "greater than or equal to" B?
A == B       A“等于”B?
A != B       A“不等于”B?
A < B        A“小于”B?
A > B        A“大于”B?
A <= B       A“小于等于”B?
A >= B       A“大于等于”B?

这些运算符可以比较任意数值类型的数值。还可以用来比较char类型的值。对字符串来说,< 和 >被定义为根据字符的 Unicode值 进行比较(结果并不是完全按照字母顺序比较,这可能不是你想要的。所有大写字母小于小写字母。)

使用布尔表达式时你应当记住,对计算机而言,布尔值并没有什么特殊的地方。在下一章中,你会看到如何在循环和分支中使用它们。你可以像赋值给数字变量一样,给布尔变量赋布尔值。函数会返回布尔值。

顺带说一下,运算符 == 和 != 也可以用来比较布尔值。在某些情况下这是非常有用的。例如,你可以看下面这段代码:

boolean sameSign;
sameSign = ((x > 0) == (y > 0));

关系运算符 <、>、<= 和 >= 不能比较String值。你可以合法地使用 == 和 != 来比较字符串,但是由于对象行为的差别,可能不会得到你期望的结果。(== 运算符可以检查两个对象的内存地址是否相同,而不是判断对象中值是否相等。对某些对象,在某种情况下,你可能想要做类似的检查——但字符串不行。下一章我会再讨论这个话题。)相反地,你要使用 equals()、equalsIgnoreCase() 和 compareTo(),这些在2.3.3章节中进行了讨论,如何比较两个字符串。

另一个 == 和 != 不起作用的地方是与 Double.NaN 比较。这个常量表示 double 类型的未定义值。无论 x 的值是否为 Double.NaN,x == Double.NaN 和 x != Double.NaN 都会返回 false!要检测实数类型的值 x 是否为 Double.NaN,可以使用函数 Double.isNaN(x) 返回判断结果。

2.5.4  布尔运算符

在英语中,复杂条件通过 and、or 和 not 组合在一起。例如,“If there is a test and you did not study for it…”,and、or 和 not 都是布尔运算符,在Java中也同样存在。

在Java中,布尔运算符“and”表示为 &&。&& 运算符用来结合 2 个布尔值。结果还是 1 个布尔值。如果两个值都是 true,那么结果为 true;如果其中一个为 false,那么结果为 false。例如,如果 x 等于 0 并且 y 等于 0,“(x == 0) && (y == 0)” 结果为true。

布尔运算符“or”在Java中表示为 ||。(由两个垂直的行字符 | 组成。)如果 A 或 B 其中一个为 true 或者都为 true,那么表达式 “A || B” 结果为 true。只有 A 和 B 同时为 false 时,“A || B” 结果为 false。

运算符 && 和 || 被称为短路版本的布尔运算符。也就是说,&& 或 || 的第二个操作符不一定会计算。考虑下面这个测试

(x != 0) && (y/x > 1)

假设 x 的值实际为 0,在这种情况下,y/x 是未定义的触发运算(除0)。然而,计算机永远不会执行这个触发,因为当计算机对 (x != 0) 计算结果时,发现结果为 false。这时计算机知道  ((x != 0) && 任意表达式) 一定会为 false。因此,它不会再计算对第二个运算符求值。运算被短路,从而避免了除0的情况。(这个听起来有点偏技术性,事实也是如此。但有时候,会让编程生活更轻松些。)

布尔运算符“not”是一元运算符。在Java中用 ! 表示,写在单个运算对象的前面。例如,假设 test 是一个布尔变量,那么

test = ! test;

将会对 test 的值取反,从 true 变为 false 或者从 false 变为 true。

2.5.5 条件运算符

任何优秀的编程语言都有一些漂亮的小功能。虽然不是必须的功能,但可以让你在使用时感觉很酷。Java也有,条件运算符中的三元运算符。它有 3 个操作数,有 2 个部分:,? 和 : 组合在一起。三元运算符形式如下:

boolean-expression ? expression1 : expression2

计算机会检测布尔表达式的值。如果值为 true,会计算 expression1,否则计算 expression2。例如:

next = (N % 2 == 0) ? (N/2) : (3*N+1);

如果N是偶数,会把 N/2 赋给 next(即 N % 2 == 0 为 true);如果N是基数,会把 (3*N+1) 赋给 next(这里的括号不是必须的,但是会让表达式更容易理解)。

2.5.6  赋值运算符和类型转换

你可能已经对赋值表达式非常熟悉,使用 “=” 将表达式赋值给变量。实际上,在某种意义上 = 也是运算符,可以将它用作表达式或者作为复杂表达式一部分。表达式 A=B 与向 A 赋值的语句作用相同。因此,如果你想要把 B 的值赋给 A,同时判断值是否为 0,可以这么写:

if ( (A=B) == 0 )...

通常,我会强调不要那么做!

通常,表达式中右边的类型必须和左边一致。然而,在某些情况下,计算机会对表达式的值自动转换,以匹配变量的类型。比如在数值类型 byte、short、int、ong、float、double 中,列表中靠前的类型数值可以自动转换为列表中靠后的类型。

int A;
double X;
short B;
A = 17;
X = A;    // OK; A is converted to a double //OK;A被自动转换为double类型
B = A;    // illegal; no automatic conversion //非法;不能从int自动转换为short
          //       from int to short

在不影响语义的情况下,转换应当自动进行。任何int应当可以被转换为数值相同的 double 类型。然而,int 值中有一些超过了short 类型的合法范围。比如不能将 100000 转为 short,因为 short 的最大值是 32767.

在某些情况下,比如在不能自动转换的情况下你可能想要进行强制转换。这里,你需要使用类型转换。类型转换可以把类型名放在括号里,放在你需要转换的数值前面。例如:

int A;
short B;
A = 17;
B = (short)A;  // OK; A is explicitly type cast // OK;A可以显示地把数值转换为short类型
               //      to a value of type short

你可以将任何数值类型转换为其他数值类型。然而,你应当注意,转换过程中可能会改变数值。例如,(short)100000 等于 -31072。(-31072 是通过 100000 丢掉2个字节,保留 4 个字节得到的 short 值——转换中会丢失 2 个字节的信息。)

当你将实数转为整型时,小数部分被丢掉了。例如,(int)7.9453 等于 7。另一个类型转换的例子,从 1 到 6 范围中得到随机整数。函数 Math.random() 会返回 0.0 到 0.9999… 之间的实数,因此 6*Math.random() 的结果在 0.0 和 5.999… 之间。类型转换操作符 (int) 可以用来将结果转为整形:(int)(6*Math.random())。因此,(int)(6*Math.random()) 结果会得到 0、1、2、3、4、5 之间的某个整数。要得到 1 到 6 之间的随机数,可以加 1:”(int)(6*Math.random()) + 1″。(6*Math.random() 周围的括号是必须的,因为用括号来保证运算的优先顺序;如果没有括号,类型转换运算符只能对 6 起效)。

char 类型与整型几乎等价。你可以将 char 类型赋值给任意整型变量。你还可以将 0 到 65535 内的常量赋值给 char 变量。你还可以显示地在 char 和数值型之间进行类型转换,比如 (char)97 得到 ‘a’,(int)’+’ 是 43,(char)(‘A’ + 2) 等于 ‘C’。

String 和其它类型之间的不能进行类型转换。任意类型转换成字符串的一种方法,可以将他们与一个空字符串连接。例如,”" + 42 可以得到字符串 “42″。但是还有更好的办法,使用 String 类中的静态方法 String.valueOf(x)。String.valueOf(x) 返回输入 x 转换得到的 String。例如,String.valueOf(42) 返回字符串 “42″。而且,如果 ch 是一个 char 变量,那么 String.valueOf(ch) 返回长度为 1 的字符串,字符串中内容是 ch 变量中的唯一一个字符。

也可以将特定字符串转换为其它类型的值。例如,字符串”10″ 可以被转转换为整型值 10,而字符串 “17.42e-2″ 可以转换为 double 值 0.1742。在Java中,这些转换可以由内建方法完成。

标准的 Integer类 提供了静态成员函数,可以将 String 转为 int。特别地,如果 str 是任意的 String 表达式,Integer.parseInt(str) 会试着将str的值转为int类型。例如 Integer.parseInt(“10″) 得到 int 值 10。如果 Integer.parseInt 传入的参数是非法的 int 值,那么会返回错误。

类似地,标准的 Double 类提供了 Double.parseDouble方法。如果 str 是 String 类型,调用 Double.parseDouble(str) 方法会试图将 str 转为 double 类型的值。当 str 表示的是非法 double 值,就会返回错误。

让我们会到赋值语句。Java有许多赋值运算符变种,用来保存类型。例如,”A += B” 等价于 “A = A + B”。除了关系运算符,每个Java运算符都可以处理 2 个操作数,这样就得到了 1 个类似的赋值运算符。例如:

x -= y;     // same as:   x = x - y; 等价于:x = x - y;
x *= y;     // same as:   x = x * y; 等价于:x = x * y;
x /= y;     // same as:   x = x / y; 等价于:x = x / y;
x %= y;     // same as:   x = x % y; 等价于:x = x % y;
q &&= p;    // same as:   q = q && p;  (for booleans q and p) 等价于:q = q && p; (q和p都是布尔型)

组合式赋值运算符 += 甚至可以用于String。回忆一下,+ 运算符可以将 string 作为其中的一个操作数,表示连接操作。既然 str += x 等价于 str = str + x,那么当 += 将 string 作为操作数时,这个操作就把右边的操作数附到字符串的结尾。例如,如果 str 的值是 “tire”,那么语句 str += ’d'; 就会返回 str 值为 “tired”。

2.5.7  优先规则

如果在表达式中使用了多个运算符,并且没有使用括号来显示地指定计算顺序,那么你就需要考虑优先规则来确定实际的计算顺序。(建议:不要让你自己和程序的阅读者产生困惑,大方地使用括号吧。)

下面是本章讨论过的运算符列表,按照优先级从高(第一个计算)到低(最后计算)顺序排列:

Unary operators: 一元运算符:             ++, --, !, unary -, unary +, type-cast
Multiplication and division: 乘法和除法:  *,  /,  %
Addition and subtraction: 加法和减法:     +,  -
Relational operators: 关系运算符:        <,  >,  <=,  >=
Equality and inequality: 相等和不等:     ==,  !=
Boolean and: 布尔与:                 &&
Boolean or: 布尔或:                  ||
Conditional operator: 条件运算符:        ?:
Assignment operators: 赋值运算符:        =,  +=,  -=,  *=,  /=,  %=

同一行的运算符优先级相等。在没有括号的情况下,优先级相同的运算符串在一起,一元运算符和赋值运算符的计算顺序是从右到左,而剩下的其它运算符计算顺序是从左到右。例如 A*B/C 表示 (A*B)/C,而 A=B=C 表示 A=(B=C)。(考虑到 B=C 作为表达式的同时也进行了给 B 赋值的运算,你能看出A=B=C表达式是如何计算的吗?)

原文链接: math.hws.edu 翻译: ImportNew.com - 唐尤华
译文链接: http://www.importnew.com/17127.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 唐尤华

我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其实我是一个程序员。(新浪微博:@唐尤华

查看唐尤华的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部