现在的位置: 首页 > 综合 > 正文

比较java和C++的面向对象语法

2013年07月31日 ⁄ 综合 ⁄ 共 5217字 ⁄ 字号 评论关闭

 

    打开《Java 核心技术》第一卷的第4章:对象和类,第5章:继承,第6章:接口和内部类。洋洋洒洒近150页,几乎集中了Java中面向对象语法的全部。不过凭借我在C++中浸淫这么多年的深厚功力,看起来还是相当轻松的:)这句话无疑是吹牛了,学习C++久了,我想人应该变得越来越谦虚才对。不过我还是提倡在枯燥的学习中多给自己一点自吹自擂的骄傲吧:)

简单的封装:

    面向对象最基本的单元就是类,类在本质上仅仅是把数据和方法结合在一起的产物,这种结合带有一定的必然性,说白了这就是局部化或者叫分治思想在软件设计中的具体应用,把一个过于复杂的东西拆成多个彼此独立的简单的东西。局部化是降低复杂性的最好武器。于是软件设计就从面向过程的数据结构+算法进化到了面向对象的类的划分和类的关系,类的划分和类的关系做得多了才有了设计模式。甚至更后来的面向组件思想(com),面向服务思想(SOA)又何尝不是另一种意义上的分治。废话多了些,直接写个最简单的类吧,就是数据+方法,在C++中它们的术语叫做成员变量和成员函数,在Java中它们的术语变成了域和方法,在Java中不用区别成员和非成员,因为它只有成员的概念。

class Person
{
    
public int getId()
    {
        
return id;
    }
    
    
public String getName()
    {
        
return name;
    }
    
    
public int getAge()
    {
        
return age;
    }

    public void setAge(int age)
    {
        
this.age = age;
    }
    
    
private String name;    //姓名
    private int age;        //年纪
    private int id;            //标识
}

与之相应的C++代码是:

class Person
{
public:
    
int getId()
    {
        
return id;
    }
    
    
string getName()
    {
        
return name;
    }
    
    
int getAge()
    {
        
return age;
    }

    void setAge(int age)
    {
        
this->age = age;
    }

private:    
    
string name;    //姓名
    int age;        //年纪
    int id;            //标识
};

可见,除了访问权限声明上Java是一对一的,而C++是分块的之外,两者几乎没有区别(类似结尾;之类的小问题不在考虑范围之内),而在使用上:

Java的代码:

        Person p = new Person();
        p.setAge(
33);

C++的代码:

    Person* p = new Person();
    p
->setAge(33);

差别也非常小,Java中去掉了指针的语法,但是指针的语义还是随处可见的,不过是少了个*和delete而已

再啰嗦一下类和对象的概念,对象是类的实例,类和对象之间是1对多的关系,类和类之间只需要类名就可以标识了(严格的说要加上所属的名称空间名字),对象和对象之间除了类型的区别以外,同样类型的对象之间还需要用标识来区别,标识的概念也是面向对象中的重要概念。那么如何实现从类到对象,说白了就是多了一个this指针,这在Java中应该叫this引用吧

加上了继承:

类的继承是面向对象的重要概念,这是面向对象可以提高软件复用度的重要方法,可以构造一个从父类到子类不限层数的大大的派生体系树,特别是象Java这样的单基类的派生体系,基本上就是一棵树。在C++中是不限制基类的,所以可能是多棵树。

Java的代码:

class Employee extends Person
{
}

C++的代码:

class Employee : public Person
{
};

又仅仅是关键字的不同,在Java中也没有对基类可见性的限制,总的来说Java中的可见性控制比C++中的要宽松的多。而且Java中根本就不支持多派生,这也是比C++简单的重要方面。

如何实现继承,在C++中,基本上就是把基类原样复制到子类的内存布局中,在Java中的实现就不是很清楚了

加上了多态:

方法的多态性是面向对象的重要概念,这是面向对象可以提高软件可扩展性的重要方法。语法上其实太简单了,同样标识的方法在基类和子类中就构成了重写,只不过在C++还需要在基类的方法前面加个virtual关键字而已。

在C++中,多态的实现要靠虚函数表,而在Java中就根本不需要这么麻烦了,因为它有完善的类型标识体系,做个反射都玩似的。不过话说回来了,既然虚函数表都可以做,也做个完善的类型标识体系很难吗?但是C++注定要对内存和效率斤斤计较的,它把问题留给了程序员,要做可以,自己来。于是MFC搞了一堆表格,又是宏又是虚函数的,只为了实现isA和isKindOf函数。于是Qt搞出了元数据,我自己解析,自己动态生成一个新文件。ATL更是搞出来一个thunk技术。真是八仙过海,各显神通。就是晕你没商量,如果还不晕,我们还有其它的各种奇招,怪招。。。

方法的重载:

方法是可以重载的,道理其实非常简单,因为方法的标识是函数名+参数类型,所以同名的方法可以重载。这在C++中也是很普通的概念,而且C++甚至支持运算符的重载,而Java就不支持,导致Java在做一些数学运算的时候,那代码写得真是惨不忍睹。 

需要特别注意的是,Java中其实没有C++中常用的覆盖概念:

class Person
{
    
void testOverload()
    {
        System.out.println(
"testOverload()");
    }
}

class Employee extends Person
{
    
public void testOverload(int i)
    {
        System.out.println(
"testOverload(int)");
    }
    
public void testOverload(double r)
    {
        System.out.println(
"testOverload(double)");
    }
}
       
        Employee e 
= new Employee();
        e.testOverload();
        e.testOverload(
1);
        e.testOverload(
1.0);

同样的代码在C++中会因为子类的testOverload函数覆盖了基类中同名的函数,而导致e.testOverload()根本无法通过编译。在C++中重载是不能跨越作用域的,C++这样设计的目的是为了保证重载规则的简单,只搜索到子类这个层次就足够了,不必要再去父类中搜索,因为C++的重载匹配规则是非常复杂的。而这条基本的原则在Java中形同废纸,它就是要继续到父类中去搜索,它或许也有资格这样做,因为C++还要支持用户自定义的类型转换,而Java全然没有那么多的负担,因为它的基本原则就是足够的简单。

再多说一句,在C++中要想实现跨作用域的重载可以用using声明,而Java中就好像默认已经全部这样声明了。

特殊的方法:

为了使每个类都有点必要的人样,一些特殊的方法是必须考虑的:

Java中的构造方法,和C++中的构造函数类似,不过Java中的构造函数之间可以相互调用,这其实更象个噱头,在C++中搞个公共的初始化函数就ok了

Java中的finalize方法,和C++中的析构函数类型,不过Java中因为有垃圾回收机制,到底这个函数什么时候执行是不可知的,这可能是最令C++程序员不习惯的地方,那么多资源管理的奇技淫巧统统不能用了,真是失落啊,不过这到也是代码新手的最大福音了

Java中的equals方法,和C++中operater==类似,就是如何比较两个对象是否具有相同的内容

Java中的clone方法,和C++中的operater=类似,不过在Java中对象基本上都是引用的语义,并不存在C++中operater=的语义,所以在C++中非常重要的拷贝构造函数和operater=基本上就没有了,Java中只能通过clone方法生成一个对象了,总算是保留了一个可以继续争论深克隆和浅克隆的地方

嵌套类和局部类:

面向对象中的类真是无处不在,类可以另一个类里面,这就是嵌套类,类也可以在方法里面,这就是局部类。使用嵌套类和局部类的主要目的是为了名称控制和权限控制。在C++也有同样的概念。

在Java中已经没有提前声明的语法了,于是它可以很自然的写:

class Nest
{
    
class AA 
    {
        BB b;
    }
    
    
class BB
    {
    }
}

而在C++中,这段代码错误百出,你必须这样来写:

class Nest
{
public:
    
class BB;

    class AA
    {
        BB
* b;
    };

    class BB
    {

    };
};

C++受制它的名称解析机制和内存布局机制,必须提前声明class BB,而且在class AA也必须只能有BB的指针或引用。Java中的b根本就是引用,当然没有那么麻烦了。

上面的还是小问题,对于嵌套类和局部类,Java表现的真是超乎寻常的激进啊

this指针的传递:

class Nest
{
    
private void funNest()
    {
    }
    
    
class AA 
    {
        
private void funAA()
        {
        }
    
        
class BB
        {
            
private void funBB()
            {
                funNest();
                funAA();
            }
        }
    }
    
    
public void test()
    {
        AA.BB b 
= new AA().new BB();
        b.funBB();
    }
}

注意代码AA.BB b = new AA().new BB();这里Java的语义是把Nest.this,和AA.this指针自动传递给BB了,所以BB中可以自由的引用外层类的域和方法。C++可从来不允许编译器这么自以为是,如果要实现相同的功能,还是让程序员自己老老实实的把父类的指针通过构造函数传递进去吧。但是Java的这种简化也是没有任何问题:

class CC extends Nest.AA.BB
{
}

在Java中居然无法编译,因为当用new CC执行CC的默认构造函数时,基类BB同时也是嵌套类,它需要自动传递的Nest.this,和AA.this指针找不到了,在Java中必须把CC的构造函数这样写:

class CC extends Nest.AA.BB
{
    
public CC(Nest.AA o)
    {
        o.
super();
    }
}
    
// 创建对象
    CC c = new CC(new Nest().new AA());

说实话,这样的代码确实有些丑陋,或许以后看惯了能好点吧:)

匿名类:

把内部类的名称省略了就是一个匿名类,因为没有了名称,也就无从谈起构造函数了,这又是一个为了少写点代码的语法:

        Employee d = new Employee()
        {
            
public void setAge(int age)
            {
            }
        };
        d.setAge(
10);

这里其实构造了一个匿名的Employee的派生类,并且重写了Employee中的setAge方法

不是结尾的结尾:

就先写到这里吧,本来想继续讨论Java的权限控制,static,final关键字和接口概念的,但是忽然发现,还是最好吧这些东西留在下一般文章吧

抱歉!评论已关闭.