Java和C++在细节上的差异(一)

  一、基本程序设计结构

  Java的基本程序结构、关键字、操作符都和C/C++非常相似,以下为主要的几点区别:

  1、Java的原始数值型数据类型中不包含无符号类型,如c中的unsigned int。

  2、在进行移位运算时,当向左边移动时,如1 << 35, 对于int类型,由于其占有4个bytes(32bits), 因此在Java中,大于32的移位将对32取模,即1 << 35的结果等于1 << 3,以此类推,long将会对64取模。对于int类型而言,如果确实需要获取32位以上的移位,需要将返回值的类型提升到long即可。

  3、在c语言中,可以通过判断一个数值是否为0来代替布尔中的false,其他的数值均表示为true。该写法可应用于if和while等子句中,如 if (i) {….}, 当i的值不为0时,该条件可为真,或者是在判断指针对象是否为NULL时,也可作为if和while的条件,因此很容易出现将if (i == 9) {…}写成if (i = 9) {…}的低级错误,在Java中禁止了该类转换,既if和while中条件必须是布尔类型,如果在Java中写成 if (i = 9) {…}将会直接导致编译错误,从而更好的避免了该类问题的发生。

  4、Java中去除了goto字句,但是仍然视为保留字。然而Java中的break字句,新增了带标签的break [label],可以使break语句直接跳出指定的循环,而不仅仅是缺省的最内层循环。注:标签必须放在希望跳出的最外层循环之前,并且紧跟一个冒号。如:

  1publicvoidtest() {2intn;3read_data:4while(…) {5for(…) {6System.out.print(“Enter a number >= 0:”);7n=in.nextInt();8if(n<0)9breakread_data;10}11}12//下面的代码将会被立即执行,当break跳出最外层的循环之后。13if(n<0) {14…15}else{16…17}18}

  5、Java中支持0长度的数组定义,如int et = new int[0]; 在C/C++中,该写法将会导致编译错误。

  6、多维数组的两种常用访问方式。

  1publicstaticvoidmain(String[] args) {2int[][] magicSquare=3{4{16,3,2,13},5{5,10,11,8},6{9,6,7,12},7{4,15,14,1}8};9//通过普通的for循环访问10for(inti=0; i<magicSquare.length;++i) {11for(intj=0; j<magicSquare[i].length;++j) {12System.out.printf(“%s”,magicSquare[i][j]);13}14System.out.println();15}16//通过普通的for each循环访问17for(int[] row : magicSquare) {18for(intcol : row) {19System.out.printf(“%s”,col);20}21System.out.println();22}23}24/*两次输出结果均为:2516 3 2 13265 10 11 8279 6 7 12284 15 14 1*/

  7、Java中的不规则二维数组。

  1 public void foo() {2 int[][] odds = new int[NMAX+1][];3 for (int n = 0; n <= NMAX; ++n)4 odds[n] = new int[n + 1];5 6 for (int n = 0; n < odds.length; ++n) {7 for (int k = 0; k < odds[n].length; ++k)8 odds[n][k] = n * k;9 }10 }

  C/C++中对应于Java的不规则二维数组的表示方式。

  1 void foo() {2 int** odds = new int*[10];3 for (int n = 0; n < 10; ++n) {4 if (n == 0)5 odds[n] = new int;6 else7 odds[n] = new int[n + 1];8 }9 10 for (int n = 0; n < 10; ++n) {11 for (int k = 0; k < n + 1; ++k)12 odds[n][k] = n * k;13 }14 //注:C/C++代码部分需要自行释放分配的内存。 15 for (int n = 0; n < 10; ++n) {16 if (n == 0)17 delete odds[n];18 else19 delete [] odds[n];20 }21 delete [] odds;22 }

  二、对象与类

  1、Java对象实例的存储方式:

  所有的Java对象实例都是通过new的方式创建的,如Employee employee = new Employee()。而此时创建的employee对象实例,实际是指向Employee对象的一个实例的引用,主要体现为实例之间基于等号的赋值,如:employee = employee2; 赋值后两个变量将指向同一个Employee对象实例。Java处理对象变量的方式和C++中的引用比较类似,但是还是存在一定的差异,首先C++不存在空引用,既引用变量定义时也必须被同时声明其所引用的对象实例,再者就是引用一旦定义时初始化后就不能再被重新赋值了。因此这里可以将Java的对象变量看做C++中的对象指针,如:BirthdayDate d; /*Java*/ 等同于 BirthdayDate* d; /*C++*/。

  与Java对象实例声明的方式相同,C++中的对象指针也是通过new的方式进行初始化的,如BirthdayDate* d = new BirthdayDate. 同样可以将C++中的对象指针赋值为NULL,也可以将其重新赋值指向另外一个对象实例。与Java相同,通过new操作符创建的对象实例是存储在堆中的,不同的是,Java的对象在创建后,无需开发人员在去关注该对象实例需要合适被释放,所有的操作均有Java虚拟机中提供的垃圾回收机制自动完成。而C++中的该类对象,则需要开发人员通过调用delete操作符来自行完成释放,如果忘记释放将会产生内存泄露。在C++中,不仅可以将对象存储在堆上,同样也可以定义并存储的栈上,如BrithdayDate d; 该对象实例不需要手工释放,在栈退出时将自动释放该对象的存储空间,同时也会调用该对象的析构函数。

  2、Java对象方法的显式参数和隐式参数:

  1 public class Employee {2 public void raiseSalary(double byPercent) {3 double raise = salary + byPercent / 100;4 salary += raise;5 }6 private double salary;7 }

  raiseSalary是Employee类的一个成员方法,该方法是由两个参数构成,一个是显式参数byPercent,另一个则是隐式参数this,既raiseSalary方法是实现体可以改为:

  1 public void raiseSalary(double byPercent) {2 double raise = this.salary + byPercent / 100;3 this.salary += raise;4 }

  这里的隐式参数this表示当前调用raiseSalary方法的对象实例的自身,该机制和C++基本相同。

  注:静态方法中不存在该特征。

  3、Java对象中定义的final实例域,如:public class Employee { … private final String name; }, 该类型的field必须在对象构造函数中进行初始化,之后该变量将不能再被重新赋值。和final字段相似,C++对象中的const成员变量也必须在对象构造函数的初始化列表中完成赋值任务,在之后的使用中该字段将不会再被修改,否则会产生编译错误。对于Java的final域而言,以便应用于基本数据类型,如int,double等,或者不可变类型,如String。对于可变类型而言,final修饰符可能会造成某些预料之外的混乱,如 private final Date hiredate; 当该field作为某个get方法的返回值返回给调用者之后,final的修饰作用只能保证返回后的date对象不能再被重新赋值并指向新的对象实例引用,但是可以通过直接修改返回值对象的自身数据来破坏对象的封装性,从而可能造成数据的非法性,或者状态的不一致性。

  4、函数参数传递的方式:传值和传引用。

  在Java中调用函数是,参数都是通过传值的方式传递到函数内部,然而根据参数类型的不同,其表现仍然存在一定的差异。主要总结为以下3点:

  被调用方法不能修改一个基本数据类型的参数,如:int,double,boolean等,见如下代码:

  1 private static void tripleValue(double x) {2 x *= 3;3 System.out.println(“End of method: x = ” + x);4 }5 6 public static void testTripleValue() {7 System.out.println(“Test tripleValue”);8 double percent = 10;9 System.out.println(“Before: percent = ” + percent);10 tripleValue(percent);11 System.out.println(“After: percent = ” + percent);12 }13 /* 结果如下:14 Test tripleValue15 Before: percent = 10.016 End of method: x = 30.017 After: percent = 10.0 */

  被调用方法可以改变一个对象参数的状态,见如下代码:

  1 private static void tripleSalary(Employee x) {2 x.raiseSalary(200);3 System.out.println(“End of method: salary = ” + x.getSalary());4 }5 6 public static void testTripleSalary() {7 System.out.println(“Test tripleSalary”);8 Employee harry = new Employee(“Harry”,50000);9 System.out.println(“Before: salary = ” + harry.getSalary());10 tripleSalary(harry);11 System.out.println(“After: salary = ” + harry.getSalary());12 }13 /* 结果如下:14 Test tripleSalary15 Before: salary = 50000.016 End of method: x = 150000.017 After: salary = 150000.0 */

  被调用方法不能实现让对象参数引用一个新的对象,见如下代码:

  1 private static void swap(Employee a,Employee b) {2 Employee temp = x;3 x = y;4 y = temp;5 System.out.println(“End of method: x = ” + x.getName());6 System.out.println(“End of method: y = ” + y.getName());7 } 8 public static void testSwap() {9 System.out.println(“Test Swap”);10 Employee a = new Employee(“Alice”,70000);11 Employee b = new Employee(“Bob”,60000);12 System.out.println(“Before: a = ” + a.getName());13 System.out.println(“Before: b = ” + b.getName());14 swap(a,b);15 System.out.println(“After: a = ” + a.getName());16 System.out.println(“After: b = ” + b.getName());17 }18 /* 结果如下:19 Test swap20 Before: a = Alice21 Before: b = Bob22 End of method: x = Bob23 End of method: y = Alice24 After: a = Alice25 After: b = Bob */

  C++有值调用和引用调用,引用参数标有&符号。如:void tripleValue(double& x)或void swap(Employee& x,Employee& y)方法实现修改他们引用参数的目的,既该方法执行完成后,调用函数的参数变量的值将发生改变。

  5、对象的构造和构造函数:

  在Java中如果一个class没有定义任何构造函数,Java编译器将自动生成一个缺省的构造函数,没有任何参数,其行为只是按照Java默认的方式初始化该类的所有域变量,如数值型为0,布尔为false,对象则为null。但是如果该class定义了自己的构造函数,那么缺省构造函数将不会被自动生成,再试图调用自动生成的缺省构造函数将会导致编译错误。该行为和C++完全一致。但是Java提供了另外一种域变量初始化方式,如下:

  1 public class Employee {2 …3 private String name = “”; //直接赋值4 private int id = assignId();//通过调用域方法完成初始化。5 }

  在C++中不能直接在类的定义中以任何形式直接初始化成员变量。但是C++提供了在构造函数中以初始化列表的方式完成成员变量对象的初始化,特别是const成员,必须在这里赋值。

  通过一个构造器调用另一个构造器从而完成域变量的初始化和部分代码复用。通过this关键字(或称隐式参数)作为函数名,然后传入参数调用你期望的另一个构造函数,注:this被调用之前不能执行任何其他的code。

  1 public Employee(double s) {2 //calls Employee(String,double)3 this(“Employee #” + nextId,s);4 ++nextId;5 }

  在C++中如果打算完成此功能,必须将构造函数的部分逻辑抽取出来,以便让多个构造函数去调用,然后不同的构造函数之间不能直接调用。

  在Java定义的子类中,如果子类的构造函数不是调用父类的缺省构造函数,则需要在子类构造函数的第一行代码中指定欲调用的父类构造函数,该调用需要通过super关键字来完成。见如下代码:

  1 public class MyFirst {2 public static void main(String[] args) {3 BaseClass bc1 = new SonClass();4 BaseClass bc2 = new SonClass(5);5 }6 }7 8 class BaseClass {9 public BaseClass() {10 System.out.println(“This is BaseClass”);11 }12 13 public BaseClass(int i) {14 System.out.println(“This is BaseClass with i.”);15 }16 }17 18 class SonClass extends BaseClass {19 public SonClass() {20 System.out.println(“This is SonClass”);21 }22 23 public SonClass(int i) {24 super(5);25 System.out.println(“This is SonClass with i”);26 }27 }28 /* 结果如下:29 This is BaseClass30 This is SonClass31 This is BaseClass with i.32 This is SonClass with i */

  在C++中也可以完成该种类型的指定,但是必须在子类构造函数的初始化列表中完成对父类指定构造函数的调用。

  1 class BaseClass {2 public:3 BaseClass() {4 printf(“This is BaseClass\n”);5 }6 7 BaseClass(int i) {8 printf(“This is BaseClass with i\n”);9 }10 };11 12 class SonClass : public BaseClass {13 public:14 SonClass() {15 printf(“This is SonClass\n”);16 }17 18 SonClass(int i) : BaseClass(i) {19 printf(“This is SonClass with i\n”);20 }21 };22 23 int main()24 {25 BaseClass* bc1 = new SonClass;26 BaseClass* bc2 = new SonClass(5);27 delete bc1;28 delete bc2;29 return 0;30 }31 /* 结果如下:32 This is BaseClass33 This is SonClass34 This is BaseClass with i.35 This is SonClass with i */

  在Java的域变量初始化方法中存在初始化块的方式,既除声明即初始化、构造函数初始化之外的第三种域变量初始化方式。在一个类的声明中可以存在多个代码块,只要构造类的对象,这些块就会被执行,然后再运行类的构造函数。静态域变量可以在静态初始化块中完成初始化的工作,但是该初始化块只是在类第一次加载时被执行一次,之后都将不再被执行。见如下代码:

  1 class Employee {2 public Employee(String n,double s) {3 name = n;4 salary = s;5 }6 7 …8 9 private static int nextId;10 private int id;11 private String name;12 private double salary;13 14 //object initialization block.15 {16 id = nextId;17 nextId++;18 }19 20 //static initialization block.21 static22 {23 Random generator = new Random();24 nextId = generator.nextInt();25 }26 }

  6、C++的对象析构和Java对象的finalize方法:

  C++是有显式的析构方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构函数中,最常见的操作时回收分配给对象的存储空间,系统资源等。有Java有自动的垃圾回收器,不需要人工回收内存,所以Java并不支持析构函数。如果打算在Java的代码中完成类似的工作,可以通过为该类添加finalize方法,该方法将会在垃圾收集器清除对象之前调用,在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能调用。如果某个资源确实需要在使用完毕后立刻关闭,那么就需要由人工来管理。可以应用一个类似dispose或close的方法完成相应的清理操作。特别需要说明,如果一个类使用了这样的方法,当对象不再被使用时一定要调用它。

  7、Java的包 vs C++的名字空间

  他们具有极为相同的只能,即防止名字污染。当一个应用程序中存在多个第三方组件,那么不同组件中命名了相同名称的类将是极为可能发生的,如Java中的Date类,在java.util和java.sql中均存在该名称的类的声明。为了有效的防止名字污染,C++中采用了namespace和using namespace的指令来明确定义某个类具体所位于的具体位置,Java中则采用了package和import语句。

  Java在Java SE5.0 开始,import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。如import static java.lang.System.*。在完成该静态导入之后,就可以在剩下的代码中直接使用System类的静态方法和静态域了,如out.println();exit(0)。该技巧主要用于带有较长名称的常量,如if (d.get(DAY_OF_WEEK) == MONDAY) …,看起来比if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) …要容易的多。

没有了爱的语言,所有的文字都是乏味的

Java和C++在细节上的差异(一)

相关文章:

你感兴趣的文章:

标签云: