上一篇文章为大家介绍了《Java和C++的差异第二部分》,在这篇文章中我们继续解开其中的奥妙。
六、接口与内部类:
1. 接口和抽象类:Java通过interface关键字来表示接口,接口中不能包含非静态域字段,所有的域成员均是公有的抽象方法,如Comparable接口,如果希望利用Arrays.sort方法,数组的成员必须实现该接口。抽象类中包含抽象方法,和接口一样抽象类也不能被实例化。
1) 接口不能被实例化,但是可以声明接口的变量指向其实现类的对象。
2) 每个类只能有一个超类,但是可以实现多个接口。
以下为Java的接口和抽象类的定义方式:
2 int compareTo(Object other);
3 }
4
5 public interface Comparable<T> {
6 int compareTo(T other);
7 }
8
9 abstract class Employee implements Comparable {
10 public abstract int compareTo(Object other);
11 }
在C++中同样存在接口和抽象类的概念,也和Java一样不能被实例化,但是并没有相应的关键字存在,而是以一种潜在规则的方式存在,见如下代码:
2 class Comparable {
3 public:
4 virtual ~Comparable() {}
5 //compareTo为纯虚方法
6 virtual int compareTo(Comparable& other) = 0;
7 }
8
9 //Employee对象中存在部分纯虚方法,且可以有成员变量存在。
10 class Employee {
11 public:
12 virtual int compareTo(Comparable& other) { return 0; }
13 virtual int backgroud() = 0;
14
15 private:
16 int _age;
17 }
在C++的实现中,基于接口编程,同时导出C接口的工厂方法对于跨编译器极为重要,该方式比较类似于Windows中的COM技术。
C++支持多重继承,因此也存在虚基类(菱形结构)等问题带来的负面影响,既子类的两个父类中同时存在相同签名的虚方法。见如下代码:
2 public:
3 virtual void run() {}
4 }
5
6 class SentObjectTask {
7 public:
8 virtual void run() {}
9 }
10
11 class TcpServerSentTask : public TcpServerTask, public SentObjectTask { }
2. 对象克隆: Object对象中存在protected类型的clone方法,该方法将会完成子类对象clone的缺省操作,既对象域字段的浅拷贝,如果该对象的成员均为原始类型,如int、float等,或者为不可变类型,如String。这样的浅拷贝将能够达到对象clone的预期。换言之,如果对象内部存在可变对象的引用,浅拷贝将会带来原始对象和cloned对象引用相同对象引用的问题。如果希望避免该问题的发生,子类需要实现Cloneable接口。这里需要指出的是Cloneable接口并未提供clone方法,只是提供了一种契约签名。子类真正做的还是重载Object方法中的clone方法,由于Object中该方法为protected方法,所以caller不能直接调用它,只能将子类的clone方法声明为共有类型,caller才能调用。
2 public class implements Cloneable {
3 //这里已经提升了clone方法的级别为public。
4 public Employee clone() throws CloneNotSupportedException {
5 return (Employee)super.clone();
6 }
7 }
8 //深拷贝clone方法,必须clone对象内部所有可变的实例域,其中这些可变类
9 //必须全部都实现了自己的clone方法,否则将会跑出异常。
10 public class Employee implements Cloneable {
11 public Employee clone() throws CloneNotSupportedException {
12 //缺省clone完成了域字段的按位浅拷贝。
13 Employee cloned = (Employee)super.clone();
14 cloned.hireday = (Date)hireday.clone();
15 }
16 private Date hireday;
17 }
注:数组对象可以通过Array的clone(public)方法完成元素的拷贝。
在C++中由于并不存在Object这样的单根结构的框架,因此C++是以另外一种方式表现该问题的,既缺省拷贝构造和缺省等于操作符重载。和Java类似,这两个方法也是member bitwise拷贝的,但这是由编译器在生成对象模型时自动完成的缺省行为,如果该类重载了拷贝构造函数和等于操作符,在需要copy的时候则会调用重载后的方法,类的实现者应该在这两个方法中完成深拷贝。C++中还可以通过将这两个方法显示的声明为private类型的方法来禁用这种对象之间的copy行为,一旦出现,编译器将会在在编译器报错。在C++中还存在一个explicit的关键字,可以有效的防止编译器通过自行推演隐式的调用对象的拷贝构造函数和等于操作符函数,见如下代码:
2 //引用相同_name变量地址的问题。
3 class Employee {
4 private:
5 char* _name;
6 };
7 //该类由于将这两个方法私有化,一旦出现对象的隐式拷贝构造,
8 //将会导致编译错误。
9 class Employee {
10 private:
11 Employee(Employee& other);
12 const Employee& operator= (Employee& other);
13 private:
14 char* _name;
15 };
16 //将会调用重载后的这两个函数
17 class Employee {
18 Employee(Employee& other);
19 const Employee& operator= (Employee& other);
20 private:
21 char* _name;
22 };
注:C++中有一种被称为引用计数的技术,经常会用在这个地方,以便提高对象copy的效率。
3. 接口与回调:严格意义上讲,回调这个属于更多的应用于C/C++这些支持基于过程编程的语言,Java中的回调是通过接口的方式来实现的,由于在接口的实现类中可以附带更多的信息,因此其表达能力要由于C/C++中的函数指针,见如下代码:
2 public Thread(Runnable r) {}
3 }
4
5 public class MyTask implements Runnable {
6 public MyTask(int taskID) {
7 _taskID = taskID;
8 }
9
10 public void setOk(bool ok) {
11 _ok = ok;
12 }
13
14 public void run() {}
15 }
16
17 public static void main(String[] args){
18 MyTask t = new MyTask(5);
19 Thread thrd = new Thread(t);
20 t.setOk(true);
21 thrd.start();
22 }
这里的Runnable参数既为接口,Thread对象在启动的时候会调用该接口实现对象的run方法,但是在调用之前可以给该实现类传入更多的状态等相关数据,以便在线程类调用run方法时可以得到更多的信息。
以下为回调函数在C/C++中的实现:
2 int testCaller(TestCallback cb,int a,int b) {
3 return cb(a,b);
4 }
5
6 int testCallback(int a,int b) {
7 return a * b;
8 }
9
10 int main() {
11 TestCallback cb = testCallback;
12 return testCall(cb,5,6);
13 }
在C++中还可以通过模板以更加松散的方式完成类似Java的基于接口的回调(Java的回调方式,C++完全可以做到),见如下代码:
2 class Thread {
3 public:
4 Thread(T* r) _r = r {}
5 void start() { if (_r) _r->run(); }
6 private:
7 T* _r;
8 }
在以上的实现中,T无需是某个接口的实现类,只要保证该类型包含run()方法即可,注意:C++中的模板是引用才编译的方式,如果没有任何Thread