面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。
基本介绍
OOP: Object Oriented Programming,面向对象的程序设计。所谓“对象”在显式支持面向对象的语言中,一般是指类在内存中装载的实例,具有相关的成员变量和成员函数(也称为:方法)。面向对象的程序设计完全不同于传统的面向过程程序设计,它大大地降低了软件开发的难度,使编程就像搭积木一样简单,是当今电脑编程的一股势不可挡的潮流。
OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。OOP 主要有以下的概念和组件:
组件 - 数据和功能一起在运行着的计算机程序中形成的单元,组件在 OOP 计算机程序中是模块和结构化的基础。
抽象性 - 程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。
封装 - 也叫做信息封装:确保组件不会以不可预期的方式改变其它组件的内部状态;只有在那些提供了内部状态改变方法的组件中,才可以访问其内部状态。每类组件都提供了一个与其它组件联系的接口,并规定了其它组件进行调用的方法。
多态性 - 组件的引用和类集会涉及到其它许多不同类型的组件,而且引用组件所产生的结果依据实际调用的类型。
继承性 - 允许在现存的组件基础上创建子类组件,这统一并增强了多态性和封装性。典型地来说就是用类来对组件进行分组,而且还可以定义新类为现存的类的扩展,这样就可以将类组织成树形或网状结构,这体现了动作的通用性。
由于抽象性、封装性、重用性以及便于使用等方面的原因,以组件为基础的编程在脚本语言中已经变得特别流行。Python 和 Ruby 是最近才出现的语言,在开发时完全采用了 OOP 的思想,而流行的 Perl 脚本语言从版本5开始也慢慢地加入了新的面向对象的功能组件。用组件代替“现实”上的实体成为 JavaScript(ECMAScript) 得以流行的原因,有论证表明对组件进行适当的组合就可以在英特网上代替 HTML 和 XML 的文档对象模型(DOM)。
OOP思想
面向对象编程技术的关键性观念是它将数据及对数据的操作行为放在一起,作为一个相互依存、不可分割的整体——对象。对于相同类型的对象进行分类、抽象后,得出共同的特征而形成了类。面向对象编程就是定义这些类。类是描述相同类型的对象集合。类定义好之后将作为数据类型用于创建类的对象。程序的执行表现为一组对象之间的交互通信。对象之间通过公共接口进行通信,从而完成系统功能。类中声明的public成员组成了对象的对外公共接口。 [1] 简单来说就是以功能为解决问题的中心。
基本思想
OOP的许多原始思想都来之于Simula语言,并在Smalltalk语言的完善和标准化过程中得到更多的扩展和对以前的思想的重新注解。可以说OO思想和OOPL几乎是同步发展相互促进的。与函数式程序设计(functional-programming)和逻辑式程序设计(logic-programming)所代表的接近于机器的实际计算模型所不同的是,OOP几乎没有引入精确的数学描叙,而是倾向于建立一个对象模型,它能够近似的反映应用领域内的实体之间的关系,其本质是更接近于一种人类认知事物所采用的哲学观的计算模型。由此,导致了一个自然的话题,那就是OOP到底是什么?[D&T 1988][B.S 1991] .。在OOP中,对象作为计算主体,拥有自己的名称,状态以及接受外界消息的接口。在对象模型中,产生新对象,旧对象销毁,发送消息,响应消息就构成OOP计算模型的根本。
对象的产生有两种基本方式。一种是以原型(prototype)对象为基础产生新的对象。一种是以类(class)为基础产生新对象。原型的概念已经在认知心理学中被用来解释概念学习的递增特性,原型模型本身就是企图通过提供一个有代表性的对象为基础来产生各种新的对象,并由此继续产生更符合实际应用的对象。而原型-委托也是OOP中的对象抽象,代码共享机制中的一种。一个类提供了一个或者多个对象的通用性描述。从形式化的观点看,类与类型有关,因此一个类相当于是从该类中产生的实例的集合。而这样的观点也会带来一些矛盾,比较典型的就是在继承体系下,子集(子类)对象和父集(父类)对象之间的行为相融性可能很难达到,这也就是OOP中常被引用的---子类型(subtype)不等于子类(subclass)[Budd 2002]。而在一种所有皆对象的世界观背景下,在类模型基础上还诞生出了一种拥有元类(metaclass)的新对象模型。即类本身也是一种其他类的对象。以上三种根本不同的观点各自定义了三种基于类(class-based),基于原型(prototype-based)和基于元类(metaclass-based)的对象模型。而这三种对象模型也就导致了许多不同的程序设计语言(如果我们暂时把静态与动态的差别放在一边)。是的,我们经常接触的C++,Java都是使用基于类的对象模型,但除此之外还有很多我们所没有接触的OOPL采用了完全不一样的对象模型,他们是在用另外一种观点诠释OOP的内涵。
什么是oop的基本思想呢?把组件的实现和接口分开,并且让组件具有多态性。不过,两者还是有根本的不同。oop强调在程序构造中语言要素的语法。你必须得继承,使用类,使用对象,对象传递消息。不关心你继承或是不继承,它的开端是分析产品的分类,有些什么种类,他们的行为如何。就是说,两件东西相等意味着什么?怎样正确地定义相等操作?不单单是相等操作那么简单,你往深处分析就会发现“相等”这个一般观念意味着两个对象部分,或者至少基本部分是相等的,据此我们就可以有一个通用的相等操作。再说对象的种类。假设存在一个顺序序列和一组对于顺序序列的操作。那么这些操作的语义是什么?从复杂度权衡的角度看,我们应该向用户提供什么样的顺序序列?该种序列上存在那些操作?那种排序是我们需要的?只有对这些组件的概念型分类搞清楚了,我们才能提到实现的问题:使用模板、继承还是宏?使用什么语言和技术?gp的基本观点是把抽象的软件组件和它们的行为用标准的分类学分类,出发点就是要建造真实的、高效的和不取决于语言的算法和数据结构。当然最终的载体还是语言,没有语言没法编程。stl使用c++,你也可以用ada来实现,用其他的语言来实现也行,结果会有所不同,但基本的东西是一样的。到处都要用到二分查找和排序,而这就是人们正在做的。对于容器的语义,不同的语言会带来轻微的不同。但是基本的区别很清楚是gp所依存的语义,以及语义分解。例如,我们决定需要一个组件swap,然后指出这个组件在不同的语言中如果工作。显然重点是语义以及语义分类。而oop所强调的(我认为是过分强调的)是清楚的定义类之间的层次关系。oop告诉了你如何建立层次关系,却没有告诉你这些关系的实质。
(这段不太好理解,有一些术语可能要过一段时间才会有合适的中文翻译——译者)
面向对象的编程方法OOP是九十年代才流行的一种软件编程方法。它强调对象的“抽象”、“封装”、“继承”、“多态”。我们讲程序设计是由“数据结构”+“算法”组成的。从宏观的角度讲,OOP下的对象是以编程为中心的,是面向程序的对象。
特征
1.1面向对象程序设计的特征:
1) 封装
2) 继承
3) 多态
4)抽象
1.2类与数据封装
1.2.1什么是类?
简单的说,类就是一种用户定义的数据类型,跟结构类似;并且,类具有自己的成员变量和成员函数(方法),通过它们可以对类自身进行操作。如:汽车可以看作是发动机、车轮、座椅等诸如此类的集合。也可以从功能的角度来研究,譬如,能移动,加速,减速,刹车等。
例如:
class CMyClass1
{
protected:
CMyClass1();
public:
virtual ~ CMyClass1();
}
1.2.2 封装(encapsulation)
定义:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。封装的优点如下:
1) 封装允许类的客户不必关心类的工作机理就可以使用它。就象驾驶员不必了解发动机的工作原理就可以驾驶汽车一样,类的客户在使用一个类时也不必了解它是如何工作的,而只需了解它的功能即可。
2) 所有对数据的访问和操作都必须通过特定的方法,否则便无法使用,从而达到数据隐藏的目的。
1.2.3 对象
对象就是类的实例。类与对象的关系就如类型和变量的关系,所有对类的操作都必须通过对象来实现。当一个类定义了多个对象时,每个对象拥有各自的成员数据。
1.2.4 类的三种成员类型
1) 私有成员(private):缺省情况下,一个类中的所有成员都是私有的。私有成员只能被类本身的成员函数访问。能够被继承但是被继承的私有成员不能够使用。
2) 公有成员(public):公有成员可以被类成员函数和外部函数使用。
3) 保护成员(protected):类的保护成员能被类及其派生类的成员函数和友元函数使用,具有继承性。
1)构造函数
a. 是特殊的成员函数;在创建对象时首先由系统自动调用。它的作用是为新创建的对象分配空间,或为该对象的成员变量赋值等;
b. 构造函数名必须与其类名称完全相同,并且不允许有返回值。
2)析构函数
a. 析构函数是构造函数的逆操作;
b. 析构函数在类名之前加~来命名,它不允许有返回值,也不允许带参数,并且一个类只能有一个析构函数。
1.3继承
1.3.1 传统程序设计的缺点:
增加功能对程序所作的修改工作量非常大。
1.3.2 继承的优点:
继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
1.3.3 基类与派生类
1)一个类可以继承其它类的成员,被继承的类叫基类或父类;继承类叫派生类或子类
2)派生类不但拥有自己的成员变量和成员函数,还拥有父类的成员变量和成员函数。
1.3.4 类的保护成员(protected)
前面介绍了类的私有成员只能被类的成员函数和友员函数使用;类的保护成员能被类及其派生类的成员函数和友员函数使用。也就是说,类的保护成员具有继承性,而类的私有成员不具有继承性。
1.3.5 公用基类和私有基类
1)公用基类中的所有public成员在派生类中仍是public成员,所有protected成员在派生类中仍是protected成员。
2)私有基类中的public成员和protected成员在派生类中均变成private成员。
1.3.6 多重继承
1)多重继承的定义方法
例子:
class A
{
…
public:
int i;
void func1();
…
};
class B
{
…
public:
int i;
void func1();
…
};
class C: public A,B
{
…
void Show()
…
};
缺省情况下基类被定义为 private;因此基类B为私有基类。
2)继承的不确定性
例子:
class C:public A,B
{
…
void Show()
{
j = i*i;
func1();
}
…
};
由于基类A和B中同时拥有数据成员i和成员函数func1,类C引用基类的成员时,系统无法分辨是调用哪一基类的成员而发生错误;
3)解决多重继承的不确定性:
使用域操作符指明要调用的基类,即可解决不确定性问题。
class C:public A,B
{
…
int j;
void Show()
{
j = A::i*B::i;
A::func1();
}
…
};
1.3.7 多层继承
定义:所谓多层继承指的是从一个类派生出另一个类,然后以派生类作为基类,派生出另一个类,直到最后生成的派生类满足需要为止(见MSDN中的Hierarchy Chart)。
1.3.8 派生类的构造函数与析构函数
在继承关系下,派生类的构造函数负责调用基类的构造函数来设置基类数据成员值。
例:
class base
{//基类
…
public:
int i;
base(int j)
{//构造函数
i = j;
}
…
};
class derived:public base
{//派生类
…
public:
double f;
derived(int, double);
…
};
derived::derived(int k, double l):base(k)
{//派生类构造函数
…
f = l;
…
}
1.3.9 构造函数的调用顺序
1) 在定义派生类对象时,系统首先调用基类的构造函数,然后调用派生类的构造函数;在上例中,derived类首先调用base类的构造函数,然后调用自身的构造函数。
2) 析构函数的调用顺序与构造函数的调用顺序相反。
1.4重载
1.4.1函数重载
1)如果函数有相同的名称和返回值,而有不同的参数个数或参数类型,则这些函数就是重载函数。
2)派生类继承了基类的某一函数,并且又自定义了一个同名函数,有相同的返回值,不同的参数类型或参数个数。这种情况不属于重载。因为它们属于不同的域。
3)例:
class base
{
…
void func(int i)
{
…
}
void func(double f)
{
…
}
void func(double f, long q)
{
…
}
…
};
1.4.2 操作符重载
重载操作符的定义:返回值类型 operator op (参数表);其中,op为重载操作符,它必须是VC++中所定义的运算符。然后像定义函数一样定义重载操作符函数。
例子:
class person
{
…
int age;
void operator ++();
…
};
void person::operator++()
{
age++;
}
1.5虚拟函数与多态性
多态性是面向对象程序设计的精髓之所在,也是C++中最难理解和掌握的部分。在C++中,多态性是建立在虚拟函数基础上的,虚拟函数的使用使类的成员函数表现出多态性。
1.5.1虚拟函数
1)函数的定义:在定义类时在其成员函数前加上关键字virtual;
2)如果基类中成员函数定义为虚函数,则派生类中与其定义完全相同的成员函数,编译器自动将其视为虚函数;
3)只有类的成员函数才能定义为虚函数。
4)虚拟成员函数的存取要看首次定义它的类中,该函数是public还是private。
例:
class Insect
{
…
virtual bool CanFly();
…
};
bool Insect :: CanFly()
{
return FALSE;
}
class Butterfly:public Insect
{
…
bool CanFly();
…
};
bool Butterfly :: CanFly()
{
return TRUE;
}
1.5.2 虚函数的调用
1) 根据对象的不同而去调用不同类的虚拟函数
2) 可以使用基类对象调用派生类对象,即将派生类对象或指针赋值给基类对象或指针。
3) 反方向的赋值(将基类的对象或指针赋给派生类的对象或指针)是危险的。
例:
bool rtn;
Insect inc1,*pInc;
Butterfly btfly;
pInc = &inc1; //pInc指针指向Insect对象
rtn = pInc->CanFly(); //返回FALSE
pInc = &btfly; //pInc指针指向Butterfly对象
rtn = pInc->CanFly(); //返回TRUE
1)形式上,重载函数要求有相同的返回值类型和函数名,并有不同的参数序列;而虚拟函数要求三者完全相同。
2)重载函数可以是成员函数或非成员函数;而虚拟函数必须是成员函数。
3)调用方法上,重载函数根据所传递的参数序列的差别作为调用的依据;而虚拟函数则根据调用对象的不同而去调用不同类的函数。
4)虚拟函数在运行时表现出多态功能;而重载函数不具有这一功能。
1.5.4纯虚函数
定义:virtual type funcname(parameter)=0;
C++中有时设计基类就是为了被继承,而基类中的虚拟函数不做任何工作,这种情况下可以将基类中的虚拟函数定义为纯虚函数。包含纯虚函数的类叫抽象类。抽象类不能定义对象,但可以定义指向它的指针。
历史
面向对象技术最初是从面向对象的程序设计开始的,它的出现以60年代simula语言为标志。80年代中后期,面向对象程序设计逐渐成熟,被计算机界理解和接受,人们又开始进一步考虑面向对象的开发问题。这就是九十年代以Microsoft Visual系列OOP软件的流行的背景。
传统的结构化分析与设计开发方法是一个线性过程,因此,传统的结构化分析与设计方法要求现实系统的业务管理规范,处理数据齐全,用户能全面完整地其业务需求。
传统的软件结构和设计方法难以适应软件生产自动化的要求,因为它以过程为中心进行功能组合,软件的扩充和复用能力很差。
对象是对现实世界实体的模拟,因面能更容易地理解需求,即使用户和分析者之间具有不同的教育背景和工作特点,也可很好地沟通。
区别面向对象的开发和传统过程的开发的要素有:对象识别和抽象、封装、多态性和继承。
对象(Object)是一个现实实体的抽象,由现实实体的过程或信息牲来定义。一个对象可被认为是一个把数据(属性)和程序(方法)封装在一起的实体,这个程序产生该对象的动作或对它接受到的外界信号的反应。这些对象操作有时称为方法。对象是个动态的概念,其中的属性反映了对象当前的状态。
类(Class)用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
由上分析不难看出,尽管OOP技术更看中用户的对象模型,但其目的都是以编程为目的的,而不是以用户的信息为中心的,总想把用户的信息纳入到某个用户不感兴趣的“程序对象”中。
优缺点
· OOP 的优点:使人们的编程与实际的世界更加接近,所有的对象被赋予属性和方法,结果编程就更加富有人性化。
· OOP 的也有缺点,就 C++ 而言,由于面向更高的逻辑抽象层,使得 C++ 在实现的时候,不得不做出性能上面的牺牲,有时候甚至是致命的 ( 所有对象的属性都经过内置多重指针的间接引用是其性能损失的主要原因之一;不过,笔者的局限性在于未使用过 VC++ 外的面向对象语言,所以不是十分肯定,哈哈,有人笑出来了… )。
在计算机速度飞速发展的今天,你可能会说,一丁点的性能牺牲没什么大不了。是的,从面向对象的角度,使的编程的结构更加清晰完整,数据更加独立和易于管理,性能的牺牲可以带来这么多的好处,没有理由不做稳赚的生意吧?
不过,在某些对速度要求极高特殊场合,例如你做的是电信的交换系统,每秒钟有超过百万的人同时进行电话交换,如果,每一个数据交换过程都是一个对象,那么总的性能损失将是天文数字!!
或者这个例子不够贴身,再举个例子吧。假如你受聘于一个游戏设计公司,老板希望做出来的游戏可以更多的兼顾到更多的电脑使用者,游戏每秒钟的运行的帧可以更多,子弹和爆炸物可以更多、更华丽。那么,你会发现使用 C++ 会使你的程序变得笨拙,无法满足你的需求,除非你非得要你的游戏运行于奔腾四的机器上 ( 如果不是,而你又坚持用 C++ 的对象编程,那么请减少主角的枪的威力吧 )。
如果你是冥顽不灵的人,你说不相信 OOP 会有性能上的损失,那么,我记得曾看到在 CSDN 上关于 VB 和 VC 执行效率的讨论的文章,讲述的就是使用了 MFC 以后,执行效率甚至低于 VB 开发出来的东西。请各位验证一下:如果使用的是纯粹的 C 语言语法的话,那么一定会比在 VB 编出来的东西要快很多 ( GetTickCount 函数可以查阅 MSDN ,如果想更加精确一些,可以使用 QueryPerformanceCounter 函数 )。
未来
(撰文/Bjarne Stroustrup & Tim Lindholm 编译/孟岩)
在未来三年,程序员编写代码的方式会发生哪些变化?
Stroustrup: 在C++中,假如没有合适的库在背后支撑,完成任何重要的工作都可能是很复杂的。而一旦有了合适的库,任何东西都可以被我们操控于股掌之间。因此,构造和使用程序库的重要性与日俱增。这也暗示我们,泛型程序设计(generic programming)将会越来越多地被运用。只有通过GP,我们才能确保库的通用性和高效率。我还预期在分布式计算和“组件(components)”应用领域会出现喜人的增长。就大部分程序员而言,通过使用方便适用的程序库,这些开发工作会变得简单明了。
现在有一个趋势,编译器厂商试图把其特有的“对象模型”和图形界面(GUI)细节推销给用户。比如微软的COM和Inprise的类属性“properties”。对于用户来说,这既不必要,也不情愿。我所希望看到的程序库,应该是用标准C++打造,界面灵活,值得信赖的程序库。通常,这些界面应该是平台无关的。C++的表达能力极强,即使不使用大量的宏,也应该足以达成这一要求。就算有些地方无法百分之百的遵守这一原则,也应该将对于平台和厂家的依赖性限制起来。这个目标的完成情况,可以反映软件工具产业对于应用程序开发行业的关注程度。我怀疑目前对于那些独立的、跨平台厂商来说,并不存在相应的市场。如果能够建立这样的市场,也许能够促进厂商们为客户做出“真正有用的”产品。
Lindholm: 对于编写代码的开发者来说,主要的驱动力量仍将是两个:网络和分布式——也就是设计和开发非单机软件的需求。大部分的应用程序将不会是孤零零地运行在单一设备上,而是运用了类似EJB和JSP之类技术的,平台无关的分布式程序。程序员们将不得不面对分布式计算的重重险阻。这将对许多程序员所依赖的设计模式、技术和直觉构成严峻的挑战。这是选择编程语言之前必须认识到的,尽管不同语言的设计特性可能促进或者阻碍这一转化。
在网络应用的增长中,一个很重要的部分是小型移动设备和特殊Internet设备的爆炸性增长。这些设备各有各的操作系统,或者只在某种特定的设备领域内有共同的操作系统。我们现在还可以一一列举出这些设备——家庭接入设备、蜂窝电话、电子报纸、PDA、自动网络设备等等。但是这些设备领域的数量和深入程度将会很快变得难以估量。我们都知道这个市场大得惊人,PC的兴起与之相比不过小菜一碟。因此在这些设备的应用程序市场上,竞争将会相当残酷。获胜的重要手段之一,就是尽快进入市场。开发人员需要优秀的工具,迅速高效地撰写和调试他们的软件。平台无关性也是制胜秘诀之一,它使得程序员能够开发出支持多种设备平台的软件。
我预期的另一个变化是,我们对于代码(Java)和数据(XML)协同型应用程序的开发能力将会不断提高。这种协同是开发强大应用程序的核心目标之一。我们从XML的迅速流行和ebXML规范的进展中,已经看到了这个趋势。ebXML是一个针对电子商务和国际贸易的,基于XML的开放式基础构架,由联合国贸易促进和电子商务中心(UN/CEFACT)与结构性信息标准推进组织(OASIS)共同开发。
我们能否期望出现一个真正的面向组件(component-oriented)的语言?它的创造者会是谁呢?
Stroustrup: 我怀疑,这个领域中之所以缺乏成果,正是因为人们——主要是那些非程序员们——对“组件”这个意义含糊的字眼寄予了太多的期望。这些人士梦想,有朝一日,组件会以某种方式把程序员赶出历史舞台。以后那些称职的“设计员”只需利用预先调整好的组件,把鼠标拖一拖放一放,就把系统组合出来。对于软件工具厂商来说,这种想法还有另一层意义,他们认为,到时候只有他们才保留有必要的技术,有能力编写这样的组件。
这种想法有一个最基本的谬误:这种组件很难获得广泛欢迎。一个单独的组件或框架(framework),如果能够满足一个应用程序或者一个产业领域对所提出的大部分要求的话,对于其制造者来说就是划算的产品,而且技术上也不是很困难。可是该产业内的几个竞争者很快就会发现,如果所有人都采用这些组件,那么彼此之间的产品就会变得天下大同,没什么区别,他们将沦为简单的办事员,主要利润都将钻进那些组件/框架供应商的腰包里!
小“组件”很有用,不过产生不了预期的杠杆效应。中型的、更通用的组件非常有用,但是构造时需要非同寻常的弹性。
在C++中,我们综合运用不同共享形式的类体系(class hierarchies),以及使用templates精心打造的接口,在这方面取得了一定的进展。我期待在这个领域取得一些有趣和有用的成果,不过我认为这种成果很可能是一种新的C++程序设计风格,而不是一种新的语言。
Lindholm: 编写面向组件的应用程序,好像更多的是个投资、设计和程序员管理方面的问题,而不是一个编程语言问题。当然某些语言在这方面具有先天优势,不过如果说有什么魔术般的新语言能够大大简化组件的编写难度,那纯粹是一种误导。
微软已经将全部赌注押在C#上,其他语言何去何从?
Stroustrup: C++在下一个十年里仍然将是一种主流语言。面对新的挑战,它会奋起应对。一个创造了那么多出色系统的语言,绝不会“坐视落花流水春去也”。
我希望微软认识到,它在C++(我指的是ISO标准C++)上有着巨大的利益,C++是它与IT世界内其他人之间的一座桥梁,是构造大型系统和嵌入式系统的有效工具,也是满足高性能需求的利器。其他语言,似乎更注重那些四平八稳的商用程序。
竞争
C#会不会获得广泛的接受,并且挤掉其他的语言?
Lindholm: 通常,一种语言既不会从别的语言那里获利,也不会被挤掉。那些坚定的Fortran程序员不还用着Fortran吗?对于个人来说,语言的选择当然因时而异,但就整体而言,语言的种类只会递增,也就是说,它们之间的关系是“有你有我”而不是“有你没我”。
对于一个新语言的接受程度,往往取决于其能力所及。Java技术被迅速接受,原因是多方面的,Internet和World Wide Web接口,在其他技术面前的挫折感,对于Java技术发展方向的全面影响能力,都是原因。另一个重要的原因是Java独立于厂商,这意味着在兼容产品面前可以从容选择。
C#是否会获得广泛接受?视情况而定。总的来说,那些对于平台无关性和厂商无关性漠不关心的程序员,可能会喜欢C#。那些跟微软平台捆在一起人当然可能想要寻找VB 和VC的一个出色的替代品。但是对于程序跨平台执行能力特别关注的程序员,将会坚守Java之类的语言。这种能力对于多重访问设备(multiple access devices)和分布式计算模型至关重要,而Java语言提供了一个标准的、独立于厂商运行时环境。
Stroustrup:C#的流行程度几乎完全取决于微软投入的资金多少。看上去C#的兴起肯定会牺牲掉其他一些语言的利益,但是事实上未必如此。Java的蓬勃发展并没有给C++带来衰败。C++的应用仍然在稳定增长(当然,已经不是爆炸性的增长了)。也许其他的语言也还能获得自己的一席之地。
不过,我实在看不出有什么必要再发明一种新的专有语言。特别是微软,既生VB,何需C#?
优劣势
Stroustrup: C++的优点自始至终都是这么几条:灵活、高效,而且并非专有语言。现在ISO C++标准的出现,巩固了最后一点。
我认为C++的高效是它最基本的优点。这种高效来自于其特有的数据和计算模型,较之Java和C#,这种模型更加贴近机器。不过,哪些程序才真正地渴望这么高的效率?这是个问题。我认为这类程序非常多。人们对于计算机的期望,永远都超越硬件科技的发展速度。很显然,Java和C#的设计者的想法不同,他们认为,在很多地方效率问题无关紧要。
C++主要的缺点,归罪于糟糕的教育(是那些始终认为C++是个纯粹面向对象语言的人,和那些把C++当成C语言变体的人导致了这种情况),归罪于不同平台上的不一致性,归罪于不完整、不标准的编译器实现,归罪于平台无关的系统级程序库的缺少。
这些问题归于一点,就是缺乏一个卓越的厂商,能够满足整个C++社区的需求,勇于投入大量的资金开发必要的程序库。
Lindholm: Java技术的成功,是因为它在合适的时间,出现在合适的地点,而且合理地选择了语言和计算平台的支持目标。Java并不是在所有场合都优于其他OOP语言,但是对于出现的新问题能够解决得很出色。它面向Internet计算环境,避免了C++中晦涩的结构,成功翻越了继承机制的恼人问题。垃圾收集机制显著地提高了生产率,降低了复杂度。在网络背景下使用虚拟机,以及有关安全性和动态加载的一系列设计选择,迎合了正在出现的需求和愿望。这些特性使Java不仅成为现有程序员的新武器,而且也为新的程序员创造了繁荣的市场空间。
此外,Java拥有一个标准化的、二进制形式的类库,提供了必要的(当然并非充分的)平台与厂商无关性。平台与厂商无关性要求一项技术必须有清晰的规范,摒弃那些阻碍二进制标准实施的特性。C++虽然有一个ISO标准,但其实甚至对于相同系统与相同指令体系的各个平台,也提不出一个实用的、各版本兼容的二进制标准。
历史上很多使用虚拟机的语言饱受责难,是因为其不够出色的性能问题,而这要归过于缓慢的解释器和糟糕的垃圾收集器。Java的早期实现也因为同样的问题受到严厉的批评。但是自那时起,业界向新的虚拟机实现技术投入了大量资金,取得了显著的效果,如今在大部分场合,Java的性能跟常规的静态编译语言相比毫不逊色。这使得程序员在获得平台和厂商无关性的同时,也不必付出性能上的代价。
C++并没有强制使用面向对象方法,因此为了编写出色的面向对象代码,就要求程序员们有相当强的纪律性。很多公司就是因为这个原因放弃了C++。作为语言,Java的一个突出的优点就是强制面向对象方法,不允许非面向对象的结构。
C#介于C++和Java之间,脚踏两只船,因此既不够安全,又失之复杂。
对于公司来说,采用新的语言要付出巨大代价。雇不到好的程序员(没人熟悉这种新语言),培训费用高得惊人,学习过程中生产率和产品质量下降,多年的经验随风消逝,等等。一种语言如何克服这些障碍?
Lindholm: 说得很对,采用新东西确实常常开销巨大。不过问题是:这个新东西是否能够节省更多的开支,或者提供巨大的改进,获取合理的回报?很多公司发现,转向Java技术不论在开发的后端(尽快进入市场、快速迭代开发、维护简单性)还是前端(跨平台发布,适用范围从低端设备到高端服务器的技术,安全性),都能节省大笔的开销。
对于新事物的接纳,常常是在痛楚的压力之下。很大程度上,这正是Java所经历的。Java的产生,是对当时很多系统的缺陷所做出的反应。Java技术通过下面的手段减轻了开发者的痛楚:1) 顾及了网络计算方面的需求,是应运而生。2) 在技术能力的抉择上,保持良好的品位,顾及了大众的心理。3) 采用适度强制性策略推行设计决定。此外,Java技术已经成为大学教学中的主流,这同样保证了Java开发者队伍的不断壮大。
但是最重要的一点是,再没有另一种程序设计技术,能够像Java那样允许程序员开发基于Internet的不同平台之上的应用程序。Java平台在这方面的杰出表现,已经被大量的实例证明。Java已经成为Internet上的缺省应用程序平台,Java APIs也成为Internet应用程序开发的天然平台。
Stroustrup: 微软和Sun把大笔的金钱扔在Java、VB和C#中,并不是因为他良心发现,也不是因为他们真的相信这些语言能够带给程序员更美好的生活,而是利益使然。
有一个说法,认为软件工具厂商如果能够把应用程序开发者的专业技术任务负担起来,将获取巨大的经济利益。我对其背后的经济分析颇为怀疑,我认为这很难成为现实,特别是当应用程序开发者使用开放的、标准化的工具时,他们可以有多种选择,从而使上面的想法更加不可能。
多年以前,C++就已经具有泛型能力(也就是templates和STL),有运算符重载,有枚举类型?我们会不会在Java的未来版本中看到这些特性?Java是不是应该纳入这些特性呢?
Strousturp:从1988-89年起,C++就已经有了templates。但是我们花了不少时间来了解如何最好地运用这个工具,早期各厂家对于template的支持在品质上也有很大的差异。有些编译器厂商动作迟缓,至少有一个主要的编译器厂商(好像是指微软,微软在Visual C++4.0才开始支持template,在此之前一直声称template是过于复杂而又没什么用的技术,时至今日,Visual C++对于template的支持在主流编译器中都属于最差的一档——译者注)暗中鼓励声名狼藉的反template宣传,直到他们自己终于学会了这项技术为止。直到今天,对于template的支持在品质上仍然有待改进。
你上面提到的那些特性,我认为Java(还有C#)应该,也肯定会逐渐引入。那些对于程序员来说最有用的语言特性和概念,将会逐渐集中,成为各家主流语言的必然之选。也就是说,我认为类似析构函数和模板特殊化之类的机制,远远比枚举等机制重要得多。
Lindholm:Java技术成功的原因之一,就是很清楚哪些不该做。我们得多问几个为什么:这项特性是不是必不可少?增加它会带来哪些开销?运算符重载是C++中一项极其强大的特性,但是它也大大增加了C++语言的复杂度,很多人都难以招架。Java在各种可能的权衡之中,做出了明智的抉择,找到了能力与需求之间的完美平衡点。
当然,Java也会发展,而且最重要的是,现在是开发者们在推动发展。Java增加泛型能力这件事,很好地展示了Java是如何通过整个开发者社群的参与,在权衡中决定正确的平衡点。关于增加泛型类型(generic types)的“Java规格申请”(Java Specification Request, JSR)已经进入JCP(Java Community Process)程序,而且已经开发了很长一段时间。现在,在JCP中,有超过80个JSRs正在讨论中,这充分体现了整个体系对开发者的积极反馈和高度合作,这正是驱动Java平台不断进化的动力。