面向对象程序设计作为一种重要的程序设计思想,在近些年来得到广泛的推崇。而Java和C++更是面向对象程序设计语言中的翘楚,要彻底摸清面向对象程序设计的精髓和在实际应用中更加高效的进行代码设计,我们有必要对比一下Java和C++的在实现面向对象程序设计上的异同之处,对两种语言的细节之处做一个详尽的了解。
面向对象程序设计概念:
面向对象编程技术并不是简单的替代了结构化编程技术,而是在结构化编程的基础上做了一个很大的提升。很多面向对象技术仍然是建立在结构化编程技术之上。这里有一个很明显的是函数指针和面向对象中的多态之间的关系。并且今天所谓鼓吹java等语言是完全的面向对象语言者,其实完全忽略了其面向过程的一面。一个很显然的反例就是java中依然存在非对象的基本数据类型。面向对象编程为程序员提供了良好的封装形式,使得用户只需关心对象对外提供的数据和功能而不用去关心对象的具体实现。
结构化程序设计中,核心考虑的是处理流程,然后是在流程中考虑如何保存与访问所需处理的数据,将数据和处理分开来看。这也就是Pascal语言的发明者Niklaus Wirth 在他的一本书中提到的公式Algorithms + Data Structures = Programs.而在面向对象的程序设计中,核心考虑的是对象,对象包含了数据并提供了处理功能,实现了数据和处理的更好的结合。当数据集合很小的时候,用面向过程语言来处理并不是一个很困难的问题。但当面临大规模的程序设计问题时,面向过程的实现方式显然就力不从心了。这时,面向对象编程就显示了它强大的威力。在面向对象程序设计时,数据和处理按照相关性被分解到各个对象中,每个对象只访问和处理本对象所包含的数据,这样就大大降低了程序设计的复杂度。下面我们用一句话来总结面向对象程序设计和面向过程的程序设计的特点:面向过程/结构化编程是大面积紧耦合的,而面向对象编程是小面积紧耦合(对象内部)和大面积松耦合的(对象之间).
2. 类和对象的概念:
一个形象的比喻是类是模板或蓝图而对象是实际物体。相当与在建筑设计中类是图纸而对象就是高楼大厦,所以说对象是类的一个实例!java提供了良好的封装机制,我们只有正确的使用好这些封装机制才能正确的发挥其作用。避免通过外部方法来直接访问类的字段,而是用类的方法来间接访问类的字段,这是实现数据封装的必有之路,而java严格的实现了这一点,但C++可以通过友员函数等机制来破坏这个性质,所以java是比C++更面向对象化的语言。
面向对象的程序设计中需要把握一个对象的下面三个特征:
a). 对象的行为(behavior): 可以使用这个对象来做些什么?或用这个对象可以调用哪些方法?
b). 对象的状态(state): 当执行上述的方法时对象会作何反应?
c). 对象的身份(identity): 当遇到和这个对象具有相同行为和相同状态的对象时,如何进行对象身份的识别?
一个类的所有实例对象共享相同的一组行为特征,即这些对象实现了相同的方法。上面这三个特征是相互作用和相互影响的。
类之间的关系:
a). 相关(dependence) ("uses-a"): 如果一个类中有成员函数的参数使用了另外一个类的对象,我们就说这两个类是相关的。在实际的编程中应当尽量减少这种类之间的相关关系,一个重要的理由是当类A中使用了类B的对象时,当B类做了改变时A类必须做出相应的改变。一个哲学意义上的说法是如果A意识不到B的存在,那么B的任何变化对A来说都是毫无意义的。
b). 聚合(aggregation) ("has-a"): 以后详述;
c). 继承(inheritance) ("is-a"):以后详述;
3. Java中的对象变量和C++中对象指针的关系:
Java中的对象变量实际上类似与C++中的对象指针,Java中的对象变量实质上就是指向对象的对象指针,在对象变量变量中保存的是对象在内存中的实际地址.
因此,Java中两个对象的赋值不能用=,因为=号只是将两个变量指向同一个对象在内存中的地址。因此在这两个对象之间进行==比较时也只是比较这两个变量是否指向的是内存中的同一个对象,并不是比较两个对象的值是否相等!要比较两个对象是否相等,则需要使用equals方法!
因此在Java的类中的类访问器中不能直接返回一个可改变对象的引用,下面的代码说明了这一点:
import java.util.*;
publicclassEmployeeTest
{
public static void main(String[] args)
{
Employee harry = new Employee("Harry Brown",231124,2010,2,3);
Date d = harry.getHireDay();
System.out.println(d);
double tenYearsInMilliSeconds = 10*365.35*24*60*60*1000;
d.setTime(d.getTime()-(long)tenYearsInMilliSeconds);
System.out.println(harry.getHireDay());
}
}
class Employee
{
public Employee(String n, double s,
int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar
= new GregorianCalendar(year, month - 1, day);
//GregorianCalendar uses 0 for January hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
private String name;
private double salary;
private Date hireDay;
}
结果:
Wed Feb 03 00:00:00 CST 2010
Wed Feb 02 12:00:00 CST 2000
这段代码的main函数中返回了一个可改变对象的引用,那么这个引用和这个可改变对象指向了同一块的内存对象,那么通过这个引用就可以修改这个内存对象!因此,这种所谓的“访问器”实际上破坏了封装性,解决方法是在返回可改变对象时先clone一个这个对象的副本,然后返回这个副本的引用。这样所有对这个返回引用的操作都执行在这个副本上,原来的对象并没改变:
publicDate getHireDay()
{
return(Date)hireDay.clone();
}
结果:
Wed Feb 03 00:00:00 CST 2010
Wed Feb 03 00:00:00 CST 2010
4 . Java 和 C++ 在对象构造方面的差异:
Java中的实例域可以进行显示初始化,而C++中的数据域是不能显示初始化的!但C++在构造函数中可以提供一个数据域初始化列表!
Java代码:
public class TestConstruct
{
public static void main(String[] args)
{
TestConstruct tc = new TestConstruct();
System.out.println(tc.GetName());
}
public String GetName()
{
return name;
}
private String name = "Jim Green";
}
输出结果:
Jim Green
C++ 代码:
#include
#include
using namespace std;
class Person
{
public:
Person(String& aname)
{
name = aname;
}
private:
string name = "Miracle Jiang";
}
int main(void)
{
return 0;
}
输出结果:
TestConstruct.cc:14:17: warning: non-static data member initializers only available with -std=c++11 or -std=gnu++11 [enabled by default]
string name = "Miracle Jiang";
上述结果C++11之前是不支持“显示实例域初始化”的,C++11之后可以使用了。但为了代码兼容性,最好不要使用这个性质。
Java 类中的构造函数之间可以相互调用,可以将公共构造部分放到一个构造函数中,其他特殊构造函数共同调用这个公共构造函数,使用this(...),C++中的构造函数之间不可以相互调用,但可以将所有公共构造部分放在一个成员函数init中,所有的构造函数调用这个构造函数。
下面这段代码来自《Core Java》一书,描述了不同的类的初始化方式:
1 /**2 @version1.00 2000-01-273 @authorCay Horstmann4 */
5
6 import java.util.*;
7
8 public class ConstructorTest
9 {
10 public static void main(String[] args)
11 {
12 //fill the staff array with three Employee objects13 Employee[] staff = new Employee[3];
14
15 staff[0] = new Employee("Harry", 40000);
16 staff[1] = new Employee(60000);
17 staff[2] = new Employee();
18
19 //print out information about all Employee objects20 for (int i = 0; i
21 {
22 Employee e = staff[i];
23 System.out.println("name=" + e.getName()
24 + ",id=" + e.getId()
25 + ",salary=" + e.getSalary());
26 }
27 }
28 }
29
30 class Employee
31 {
32 //three overloaded constructors33 public Employee(String n, double s)
34 {
35 name = n;
36 salary = s;
37 }
38
39 public Employee(double s)
40 {
41 //calls the Employee(String, double) constructor42 this("Employee #" + nextId, s);
43 }
44
45 //the default constructor46 public Employee()
47 {
48 //name initialized to ""--see below49 //salary not explicitly set--initialized to 050 //id initialized in initialization block51 }
52
53 public String getName()
54 {
55 return name;
56 }
57
58 public double getSalary()
59 {
60 return salary;
61 }
62
63 public int getId()
64 {
65 return id;
66 }
67
68 private int id;
69 private static int nextId;
70
71 //object initialization block72 {
73 id = nextId;
74 nextId++;
75 }
76
77 //static initialization block78 static
79 {
80 Random generator = new Random();
81 //set nextId to a random number between 0 and 999982 nextId = generator.nextInt(10000);
83 }
84
85 private String name = ""; //instance variable initialization86 private double salary;
87 }
5. Java 和 C++ 析构过程的差异
不要因为Java的垃圾回收机制而忽视了资源的手动回收!
Java有自带的垃圾回收机制,手动的内内存回收是不必要的,所以Java没有显示的支持构造函数。但是有时有些对象中的资源并不一定是内存资源,而有可能是
一个文件或者其他系统资源的句柄!在这些情况下,当这些资源不再被使用的时候,对其的释放就显得很重要了。
Java 中可以为每个类提供一个finalize函数,当垃圾回收器清除对象之前,finalize函数会被调用。但实际中不能依赖这个finalize函数来实现资源的回收,因为
finalize函数会出现一些未定义的行为。建议定义一个手工的方法来释放支援,而不是完全依赖Java语言的垃圾回收机制。例如,可以在类的定义中定义一个叫
close的方法,当使用完对象之后,调用close方法以释放占用的资源.