作者dasea (植栽鸡肉饭)
看板ncyu_phyedu
标题[讨论] java
时间Sat Jan 30 14:48:03 2010
Java Virtual Machine
C语言的开发模式, 是编写.c的Source Code, 再经由Compiler编译成Object Code。所谓
Object Code指的是和硬体相关的机器指令, 也就是说当我们想要把C程式移植到不同的硬
体时, 必须要重新Compile,以产生新的执行档。除了需要重新编译外,新系统是否具备应
用程式所需的程式库,include的档案是否相容, 也是程式能否在新机器上顺利编译和执行
的条件之一。
在实务上,为了让C程式能在不同的UNIX版本上都能顺利编译,原作者往往必须使用前置处
理器的#ifdef指令,判断不同环境的适当写法。如果想把在UNIX上开发的C程式移植到
Windows上,则有用到专属程式库的部分(如UNIX的使用者介面可能用到X Window的
API,Windows就没有支援,必须一台一台灌程式库才行,很可能还要花钱买),就必须重写才
行。
解决此类问题的方法之一,是定义一种Virtual Machine(虚拟机器),让程式语言编译时不
要翻成实体机器的指令,而是翻成Virtual Machine的目的码。Virtual Machine一般是以
软体来模拟的,只要新的平台有Virtual Machine,则原始程式不用Compile,执行旧机器上
已有的Virtual Machine目的码,就可以了。当然要达到完全不用重新Compile就能执行的
理想,还要配合标准的程式库才行。
Java语言基於上述理念,定义了Java Virtual Machine,它所用的指令称为byte code。使
用Virtual Machine的缺点之一,是执行的速度较慢,代价是开发的速度变快了。以现在的
硬体来说,大部分应用程式的执行速度已经没有那麽重要,反倒是软体的开发速度和品质越
来越值得重视。
此外JVM的技术不断进步, 诸如Just In Time(JIT) Compiler, 或HotSpot等技术都可以让
Java程式以非常接近原生码(Native Code)的速度执行。因此不要因为某些偏颇的报告或
直觉, 就不使用Java了。
开发Java应用程式的工具中,最常见的是由Java的原创公司Sun Micro所出版的JDK(Java
Development Kit)。JDK可以免费下载。以Text Editor写好的Hello.java原始档:
public class Hello {
public static int gvar;
public static void say(String s) {
int x = 10;
System.out.print(s+x);
}
public static void main(String[] argv) {
float y = 0;
say("Hello, world\n");
}
}
这程式的C版本如下
#include <stdio.h>
int gvar;
void say(char[] s) {
int x = 10;
printf("%s%d", s, x);
}
int main(int argc, char** argv) {
float y = 0;
say("Hello, world\n");
}
经过:
javac Hello.java
编译完成後会产生byte code格式的Hello.class,然後
java Hello
就可以利用Java Virtual Machine(此处是java这个执行档)来执行了。
上述过程中几个比较会发生的问题是
javac找不到: 请设定path这个环境变数。
javac抱怨class Hello找不到: 请确定你的档名是大写Hello.java,程式内的public
class Hello有没有大小写的问题。
java抱怨找不到main: 请确定public static void main(String[] argv)毫无错误。
Java是物件导向(Object-Oriented)程式语言
Java是由C++简化来的。由於C++要和C完全相容,又很注重效能问题,因此C++算是很复杂的
程式语言。Java在设计之初,考量的重点之一就是简单,因此和C++比起来,不仅更为物件导
向,而且比C++容易学习。
Java许多运算符号和叙述语法都是来自C语言,假设各位已经对C语言有所了解,本章後面的
部分只将Java和C在运算符号和叙述语法上的差异点出来,相同的部分请参见C语言的课程
内容。
资料型别
Java语言所定义的基本资料型别有
型别名称 位元长度 范围
boolean 1 true或false
byte 8 -128 ~ 127
short 16 -32768 ~ 32767
char 16 Unicode characters
int 32 -2147483648 ~ 2147483647
long 64 -9223372036854775808 ~ 9223372036854775807
float 32 +-3.4028237*10+38 ~ +-1.30239846*10-45
double 64 +-1.76769313486231570*10+308 ~ 4.94065645841246544*10-324
Java的资料型态里没有unsigned。
Java对数值型态的转换比C稍微严格一点,下列左边的部分都可以指定(assignment)给右边
的型别:
byte --> short --> int --> long --> float --> double
除上述外,其他型别间的转换都必须下达型别转换(Type Casting)命令来处理,其形式为圆
括弧里写上型别名称,如(double)
由於Java在char的型态部分采用Unicode,因此字元常数的表示法,除因循C的规则外,也可
以直接指定16bits Unicode编码给char型别的变数。Java的变数也可以用Unicode来命名,
换句话说,你可以用中文取变数名称。
除了这些基本资料型别外,Java还有一个称为Reference(参考)的型别。Reference用来存
取Object(物件),其功能和C语言的pointer用来存取记忆体有点像,但没有pointer的&+-等
运算符号,而且Reference只能存取型态相符合的类别。宣告Reference的语法是
ClassName varName,例如
String s;
宣告s是一个型态为reference的变数,这表示我们可透过s来存取属於String类别的物件
(s is a reference to String object)。
要特别强调的是, s并不是物件, 而是用来指向String物件的reference。打个比方,
动物 手指头; // java 因字元编码使用unicode, 所以可用中文当变数名称
变数 "手指头" 宣告为reference, 可指向属於 class "动物" 的物件, 手指头不是动物
, 而是用手指头指向某只动物。
java.lang.Float f;
java.lang.Double d;
java.lang.Integer i;
以上变数的型态都是reference
运算符号(Operator)
Java语言在运算式的部分,和C语言极为类似, 除了没有sizeof, pointer和struct相关的
运算符号外, 另外新增了>>>向右无号shift, 以及用来判断物件型态的instanceof。Java
的常数的表示法也和C相同,而Java里的新资料型态boolean的合法值为true和false两个常
数。
算术(Arithmetic)运算符号
运算符号 功能叙述
+ 加
* 乘
- 减
/ 除
% 余数
++ 加一
-- 减一
逻辑(logic)运算符号
运算符号 功能叙述
> 大於
< 小於
>= 大於等於
<= 小於等於
== 等於
!= 不等於
&& logic AND
|| logic OR
! logic NOT
instanceof reference instanceof ClassName
判断reference所指到的物件其型态是否和ClassName相容
Java语言和C语言有关逻辑运算最大的不同,在於Java以boolean资料型态(只有true和
false两种值)判断条件是否成立,而C语言只能使用0或非0。
位元(Bit)运算符号
运算符号 功能叙述
& bit AND
<< left bit shift
| bit OR
>> right bit shift with sign
^ bit XOR
~ 1补数
>>> 同>>但左边一律补零
其他运算符号
运算元 功能叙述
= 将右边的值复制到左边的变数
(type) 将右边的数值或reference转换成type型别
+= 将右边的数值加上左边的数值然後指定给左边的变数
?: 若?左边成立则做:左边否则做:右边
, 合并两个运算视为一个叙述
(运算式) 表示()内优先运算
. Reference.ObjectMember或ClassName.ClassName
存取物件或类别成员
new 产生物件
优先权
种类 运算符号 结合顺序
group (op) left to right
postfix [] . (params) op++ op-- right to left
prefix ++op --op +op -op ~ ! right to left
creation or casting new (type)op right to left
multiplicative * / % left to right
additive + - left to right
shift << >> >>> left to right
relational < > <= >= instanceof == left to right
equality == != left to right
bitwise and & left to right
bitwise exclusive or ^ left to right
bitwise inclusive or | left to right
logical and && left to right
logical or || left to right
conditional ? : right to left
assignment = += -= *= /= %= &= ^= |= <<= >>= >>>= right to left
seperator , left to right
流程控制叙述
Java的流程控制叙述和C语言极为类似,不同处在於break和continue两个指令。Java的
break和continue指令後面可以加上标签,以指示要跳出或继续的范围。
public class BreakContinueExample {
public static void main(String[] argv) {
int i, j;
outerLoop:
for (i = 0; i < 100; i++) {
innerLoop:
for (j = 0; j < 100; j++) {
if (j == 50 && i == 50) {
break outerLoop;
}
}
}
System.out.println("Loop have been terminated.");
}
}
在上面的例子中,当j==50且i==50时,break指令会跳出最外面的回圈,直接印出回圈终止讯
息。如果break後面没有outerLoop的话, 只会跳出里面的回圈,然後i从51继续做下去。
字串
C语言定义以0结尾的字元阵列就是字串。但对Java来说, 字串是由String类别来表达, 也
就是说String是物件而不是阵列。由於我们经常使用字串, 为了写作程式方便起见,
Java Compiler碰到+符号某一边的型态是String时, 就会把+翻译成StringBuffer类别里
相对应的append Method。例如:
public class StringTest {
public static void main(String[] argv) {
int x = 5;
float y = 1.5;
System.out.println("x = " + x + ", y = " + y);
}
}
会翻译成:
public class StringTest {
public static void main(String[] argv) {
int x = 5;
float y = 1.5;
System.out.println((new StringBuffer("x = ")).append(x).append(", y =
").append(y).toString());
}
}
如果你会C++, 看到Java字串+符号的语法, 千万不要以为Java支援operator overloading
。Java只是透过Compiler来做特别的转换, 称这种技术为Compiler Sugar比较适合。
Java语言的写作风格
写作Java程式时,请注意下列几种风格
Class Name请首字大写
Variable Name和Method Name请首字小写
如果名称由数个英文字组成,第二个英文字以後首字大写
内缩四个空格
注解部分如要变成说明文件,请遵照javadoc这个工具的写作规则
/**
* 第一行的两个**用来告诉javadoc此部份注解要变成HTML文件的一部份
* 这段注解里的所有文字都会变成此类别一开头的说明
*/
public class Hello { // Class Name首字大写
/**
* 此段注解会变成描述main方法的一部分
* @param argv 使用@param注记会产生参数(parameter)argv的相关说明
* @return 传回值的意义说明
*/
public static void main(String[] argv) { // Method Name首字小写
// argv: array of references to String object
int myVariable; // 变数宣告
int i, sum;
for (i = 1, sum = 0; i <= 100; i++) {
sum += i;
}
System.out.println("summation from 1 to 100 is "+sum);
}
}
运算符号范例
摄氏温度转华氏温度
public class Example {
public static void main(String[] argv) {
float degree = 100.0;
System.out.println("100C=" + (degree * 9.0 / 5.0 + 32.0));
}
}
华氏温度转摄氏温度
怎麽写呢?
1 + 2 + ... + n的总合
public class Example {
public static void main(String[] argv) {
int n = 100;
System.out.println("1+2+...+"+n+" = " + ( n * (n + 1) / 2));
}
}
特别注意上述的运算式里/2要放到最後面,如果写成n/2*(n+1),从数学式子的角度看好像
没问题,但别忘了,binary operator的两边必须是同样型别的资料,而且计算的结果也是同
样的型别。因此n/2*(n+1)会先计算n/2,如果n不能被2整除的话,那麽为了符合计算结果必
须是整数的限制,则小数点的部份就会无条件舍去,使得计算的结果错误。下面的范例一样
要注意相同的问题。
12 + 22 + ... + n2的总合
怎麽写?
把浮点数四舍五入为整数
Java语言规定浮点数转整数时,小数点部分无条件舍去。如果要达到浮点数四舍五入为整
数的效果,可以使用下面的小技巧
public class Example {
public static void main(String[] argv) {
double x = 20.6;
System.out.println(x + " 四舍五入成为 " + (int)(x+0.5));
System.out.println(x + " 四舍五入成为 " + round(x));
}
static int round(double y) {
return (int)(y + 0.5);
}
}
回圈范例
写一程式输入5个整数数字,计算其总合和平均。解析:
需要1个变数纪录到目前为止所有inputNum的总和,称此变数为sum,其初始值为0
以回圈执行5次,每次输入数字加总到sum,回圈执行的次数以变数i来代表
平均数为sum/5
如何读入资料?
import java.util.Scanner;
public class Example {
public static void main(String[] argv) {
int sum = 0, i = 0;
Scanner in = new Scanner(System.in);
while (i < 5 && in.hasNextInt()) {
sum = sum + in.nextInt();
i++;
}
System.out.println("sum is "+sum", average is "+(sum/5.0));
}
}
写一函数输入参数int n,传回1 + 2 + 3 ... + n的总合。解析:
要想办法拜访1,2,3...n的每一个数字一次
可用for(i=1; i <= n; i++)的形式达成上述目标
拜访到这些数字时,就把它们加起来
public class Example {
public static int sum(int n) {
int total = 0; // 纪录到目前为止的总和
for (int i = 1; i <= n; i++) {
total += i;
}
return total;
}
public static void main(String[] argv) {
System.out.println(sum(100));
}
}
写一函数输入参数int n,传回1 + 3 + 5 ... + n的总合。解析:
要想办法拜访1,3,5...n的每一个数字一次,也就是从1开始每次加2
可用for(i = 1; i <= n; i += 2)的形式达成上述目标
拜访到这些数字时,就把它们加起来
怎麽写?
写一函数於萤幕上画出九九乘法表。解析:
总共有i = 1..9 列, j = 1..9 行, 对第i列第j行元素来说, 其数值为i*j
public class Example {
public static void main(String[] argv) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= 9; j++) {
System.out.print(" " + (i * j));
}
System.out.println();
}
}
}
输入参数int size,并在萤幕上印出正方形,size=3的样子如下
***
***
***
解析
萤幕上的游标只能由上而下,由左而右,无法回头。
此图形共有1..size列,每列有size个*,因此可用两层回圈来做。
要让一个叙述执行size次,可用for(i = 1; i <= size; i++)的形式来达成
public class Example {
public static void print(int size) {
int i, j; // 第i列,第j行
for (i = 1; i <= size; i++) { // 印出第i列
for (j = 1; j <= size; j++) { // 第i列有size个*
System.out.print("*");
}
System.out.println();
}
}
public static void main(String[] argv) throws Exception {
BufferedReader in = new BufferedReader(new
InputStreamReader(System.in));
print(Integer.parseInt(in.readLine()));
}
}
输入参数int size,并在萤幕上印出直角三角形,size=3的样子如下
*
**
***
解析
萤幕上的游标只能由上而下,由左而右,无法回头。
此图形共有1..size列,第i列有个*,因此可用两层回圈来做。
怎麽写?
撰写一函数输入int size,并在萤幕上印出等腰的三角形,size=3的样子如下
*
***
*****
解析
总共有1..size列,对第i列而言,有size-i个空格,以及(2 * i- 1)个*
怎麽写?
写一函数求两个整数的最大公因数,解析:
此函数需要两个参数x,y
当y不能整除x时,将x设成为y,y设为x%y, 重复此步骤直到x%y为0
此时y就是这两个数的最大公因数
public class Example {
public static void main(String[] argv) {
System.out.println(gcd(12,18));
}
public static int gcd(int x, int y) {
int tmp;
// 如果x < y 则下面的回圈执行第一次时就会交换x,y了
while (x % y != 0) {
tmp = y;
y = x % y;
x = tmp;
}
return y;
}
}
写一函数求两个整数的最小公倍数
怎麽写?
写一函数求费氏数,解析:
F(n)=n, if n<=1;
F(n)=F(n-1)+F(n-2), otherwise
可定义两变数fn_1,fn_2表示最近两个找出的费氏数
下一个费氏数依定义为fn_1 + fn_2
找到最新的费氏数後,最近的两个费氏数就变成了fn_1+fn_2以及fn_1
以变数i纪录目前要求的是哪一个费氏数
以变数tmp作为更新最新两个费氏数所需的记忆体空间
public class Example {
public static void main(String[] argv) {
System.out.println(fab(5));
}
public static int fab(int n) {
int fn_1 = 1, fn_2 = 0; // 纪录最近找到的两个费氏数
int i, tmp; // i表示目前要找F(i)
if (n <= 1) return n;
for (i = 2; i <= n; i++) {
tmp = fn_1; // 先把fn_1纪录在tmp
fn_1 += fn_2; // 最新的费氏数是前面两个相加
fn_2 = tmp; // 第二新的就是原先的fn_1
}
return fn_1;
}
}
递回(recursion)范例
求1+2+3+...+n
解析
边际条件是n=1时,总合为1
该函数可定成int sum(int n)
sum(n) = n + sum(n - 1)
public class Example {
public static void main(String[] argv) {
System.out.println(sum(100));
}
public static int sum(int n) {
if (n == 1) {
return 1;
}
return n + sum(n - 1);
}
}
以递回计算1*2+2*3+3*4+…+(n-1)*n之和
怎麽写?
利用递回求得A的B次方
public class Example {
public static void main(String[] argv) {
System.out.println(power(2, 6));
}
public static int power(int a, int b) {
switch(b) {
case 0: return 1;
case 1: return a;
default: return (a * power(a, b - 1));
}
}
以递回求两个整数m,n的最大公因数
解析
如果n==0,则最大公因数为m
如果n不等於0,则最大公因数为gcd(m,n)==gcd(n, m%n)
怎麽写?
费式数列
解析
费氏数列的定义为F(n)=n, if n<= 1
F(n) = F(n-1)+F(n-2), if n > 1。
public class Example {
public static void main(String[] argv) {
System.out.println(fab(5));
}
public static int fab(int num) {
if (num <= 1) {
return num;
}
return fab(num - 1) + fab(num - 2);
}
}
Ackerman函数
A(m, n)定义为
n + 1, if m = 0
A(m - 1, 1), if n = 0
A(m - 1, A(m, n - 1)), otherwise
怎麽写?
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 61.58.22.74