C#和java,C#和C++各有什么不同
C#(C-Sharp)是Microsoft的新编程语言,被誉为"C/C++家族中第一种面向组件的语言".然而,不管它自己宣称的是什么,许多人认为C#更像是Java的一种克隆,或者是Microsoft用来替代Java的产品.事实是否是这样的呢?
本文的比较结果表明,C#不止是Java的同胞那么简单.如果你是一个Java开发者,想要学习C#或者了解更多有关C#的知识,那么本文就是你必须把最初10分钟投入于其中的所在.
一、C#、C++和Java
C#的语言规范由Microsoft的Anders Hejlsberg与Scott Wiltamuth编写.在当前Microsoft天花乱坠的宣传中,对C#和C++、Java作一番比较总是很有趣的.考虑到当前IT媒体的舆论倾向,如果你早就知道C#更接近Java而不是C++,事情也不值得大惊小怪.对于刚刚加入这场讨论的读者,下面的表1让你自己作出判断.显然,结论应该是:Java和C#虽然不是孪生子,但C#最主要的特色却更接近Java而不是C++.
表1:比较C#、C++和Java最重要的功能
功能 C# C++ Java
继承 允许继承单个类,允许实现多个接口 允许从多个类继承 允许继承单个类,允许实现多个接口
接口实现 通过"interface"关键词 通过抽象类 通过"interface"关键词
内存管理 由运行时环境管理,使用垃圾收集器 需要手工管理 由运行时环境管理,使用垃圾收集器
指针 支持,但只在很少使用的非安全模式下才支持.通常以引用取代指针 支持,一种很常用的功能. 完全不支持.代之以引用.
源代码编译后的形式 .NET中间语言(IL) 可执行代码 字节码
单一的公共基类 是否是异常处理 异常处理 返回错误 异常处理.
了解表1总结的重要语言功能之后,请继续往下阅读,了解C#和Java的一些重要区别.
二、语言规范的比较
2.1、简单数据类型
简单数据类型(Primitive)在C#中称为值类型,C#预定义的简单数据类型比Java多.例如,C#有unit,即无符号整数.表2列出了所有C#的预定义数据类型:
表2:C#中的值类型
类型 说明

object 所有类型的最终极的基类 
 
string 字符串类型;字符串是一个Unicode字符的序列 
 
sbyte 8位带符号整数 
 
short 16位带符号整数 
 
int 32位带符号整数 
 
long 64位带符号整数 
 
byte 8位无符号整数 
 
ushort 16位无符号整数 
 
uint 32位无符号整数 
 
ulong 64位无符号整数 
 
float 单精度浮点数类型 
 
double 双精度浮点数类型 
 
bool 布尔类型;bool值或者是true,或者是false 
 
char 字符类型;一个char值即是一个Unicode字符 
 
decimal 有28位有效数字的高精度小数类型 
 
2.2、常量 
 
忘掉Java中的static final修饰符.在C#中,常量可以用const关键词声明. 
 
public const int x = 55; 
 
此外,C#的设计者还增加了readonly关键词.如果编译器编译时未能确定常量值,你可以使用readonly关键词.readonly域只能通过初始化器或类的构造函数设置. 
 
2.3、公用类的入口点 
 
在Java中,公用类的入口点是一个名为main的公用静态方法.main方法的参数是String对象数组,它没有返回值.在C#中,main方法变成了公用静态方法Main(大写的M),Main方法的参数也是一个String对象数组,而且也没有返回值,如下面的原型声明所示: 
 
public static void Main(String[] args) 
 
但是,C#的Main方法不局限于此.如果不向Main方法传递任何参数,你可以使用上述Main方法的一个重载版本,即不带参数列表的版本.也就是说,下面的Main方法也是一个合法的入口点: 
 
public static void Main() 
 
另外,如果你认为有必要的话,Main方法还可以返回一个int.例如,下面代码中的Main方法返回1: 
 
using System; 
 
public class Hello { 
 
public static int Main() { 
 
Console.WriteLine("Done"); 
 
return 1; 
 
} 
 
} 
 
与此相对,在Java中重载main方法是不合法的. 
 
2.4、switch语句 
 
在Java中,switch语句只能处理整数.但C#中的switch语句不同,它还能够处理字符变量.请考虑下面用switch语句处理字符串变量的C#代码: 
 
using System; 
 
public class Hello { 
 
public static void Main(String[] args) { 
 
switch (args[0]) { 
 
case "老板": 
 
Console.WriteLine("早上好!我们随时准备为您效劳!"); 
 
break; 
 
case "雇员": 
 
Console.WriteLine("早上好!你可以开始工作了!"); 
 
break; 
 
default: 
 
Console.WriteLine("早上好!祝你好运!"); 
 
break; 
 
} 
 
} 
 
} 
 
与Java中的switch不同,C#的switch语句要求每一个case块或者在块的末尾提供一个break语句,或者用goto转到switch内的其他case标签. 
 
2.5、foreach语句 
 
foreach语句枚举集合中的各个元素,为集合中的每一个元素执行一次代码块.请参见下面的例子. 
 
using System; 
 
public class Hello { 
 
public static void Main(String[] args) { 
 
foreach (String arg in args) 
 
Console.WriteLine(arg); 
 
} 
 
} 
 
如果在运行这个执行文件的时候指定了参数,比如"Hello Peter Kevin Richard",则程序的输出将是下面几行文字: 
 
Peter 
 
Kevin 
 
Richard 
 
2.6、C#没有>>>移位操作符 
 
C#支持uint和ulong之类的无符号变量类型.因此,在C#中,右移操作符(即">>")对于无符号变量类型和带符号变量类型(比如int和long)的处理方式不同.右移uint和ulong丢弃低位并把空出的高位设置为零;但对于int和long类型的变量,">>"操作符丢弃低位,同时,只有当变量值是正数时,">>"才把空出的高位设置成零;如果">>"操作的是一个负数,空出的高位被设置成为1. 
 
Java中不存在无符号的变量类型.因此,我们用">>>"操作符在右移时引入负号位;否则,使用">>"操作符. 
 
2.7、goto关键词 
 
Java不用goto关键词.在C#中,goto允许你转到指定的标签.不过,C#以特别谨慎的态度对待goto,比如它不允许goto转入到语句块的内部.在Java中,你可以用带标签的语句加上break或continue取代C#中的goto. 
 
2.8、声明数组 
 
在Java中,数组的声明方法非常灵活,实际上有许多种声明方法都属于合法的方法.例如,下面的几行代码是等价的: 
 
int[] x = { 0, 1, 2, 3 }; 
 
int x[] = { 0, 1, 2, 3 }; 
 
但在C#中,只有第一行代码合法,[]不能放到变量名字之后. 
 
2.9、包在C#中,包(Package)被称为名称空间.把名称空间引入C#程序的关键词是"using".例如,"using System;"这个语句引入了System名称空间. 
 
然而,与Java不同的是,C#允许为名称空间或者名称空间中的类指定别名: 
 
using TheConsole = System.Console; 
 
public class Hello { 
 
public static void Main() { 
 
TheConsole.WriteLine("使用别名"); 
 
} 
 
} 
 
虽然从概念上看,Java的包类似于.NET的名称空间.然而,两者的实现方式不同.在Java中,包的名字同时也是实际存在的实体,它决定了放置.java文件的目录结构.在C#校 锢淼陌 吐呒 拿浦涫峭耆 掷氲模 簿褪撬担 瓶占涞拿 植换岫晕锢淼拇虬 绞讲 魏斡跋臁T贑#中,每一个源代码文件可以从属于多个名称空间,而且它可以容纳多个公共类. 
 
.NET中包的实体称为程序集(Assembly).每一个程序集包含一个manifest结构.manifest列举程序集所包含的文件,控制哪些类型和资源被显露到程序集之外,并把对这些类型和资源的引用映射到包含这些类型与资源的文件.程序集是自包含的,一个程序集可以放置到单一的文件之内,也可以分割成多个文件..NET的这种封装机制解决了DLL文件所面临的问题,即臭名昭著的DLL Hell问题. 
 
2.10、默认包 
 
在Java中,java.lang包是默认的包,它无需显式导入就已经自动包含.例如,要把一些文本输出到控制台,你可以使用下面的代码: 
 
System.out.println("Hello world from Java"); 
 
C#中不存在默认的包.如果要向控制台输出文本,你使用System名称空间Console对象的WriteLine方法.但是,你必须显式导入所有的类.代码如下: 
 
using System; 
 
public class Hello { 
 
public static void Main() { 
 
Console.WriteLine("Hello world from C#"); 
 
} 
 
} 
 
2.11、面向对象 
 
Java和C#都是完全面向对象的语言.在面向对象编程的三大原则方面,这两种语言接近得不能再接近. 
 
继承:这两种语言都支持类的单一继承,但类可以实现多个接口.所有类都从一个公共的基类继承. 
 
封装与可见性:无论是在Java还是C#中,你都可以决定类成员是否可见.除了C#的internal访问修饰符之外,两者的可见性机制非常相似. 
 
多态性:Java和C#都支持某些形式的多态性机制,且两者实现方法非常类似. 
 
2.12、可访问性 
 
类的每个成员都有特定类型的可访问性.C#中的访问修饰符与Java中的基本对应,但多出了一个internal.简而言之,C#有5种类型的可访问性,如下所示: 
 
public:成员可以从任何代码访问. 
 
protected:成员只能从派生类访问. 
 
internal:成员只能从同一程序集的内部访问. 
 
protected internal:成员只能从同一程序集内的派生类访问. 
 
private:成员只能在当前类的内部访问. 
 
2.13、派生类 
 
在Java中,我们用关键词"extends"实现继承.C#采用了C++的类派生语法.例如,下面的代码显示了如何派生父类Control从而创建出新类Button: 
 
public class Button: Control { . . } 
 
2.14、最终类 
 
由于C#中不存在final关键词,如果想要某个类不再被派生,你可以使用sealed关键词,如下例所示: 
 
sealed class FinalClass { . . } 
 
2.15、接口 
 
接口这个概念在C#和Java中非常相似.接口的关键词是interface,一个接口可以扩展一个或者多个其他接口.按照惯例,接口的名字以大写字母"I"开头.下面的代码是C#接口的一个例子,它与Java中的接口完全一样: 
 
interface IShape { void Draw(); } 
 
扩展接口的语法与扩展类的语法一样.例如,下例的IRectangularShape接口扩展IShape接口(即,从IShape接口派生出IRectangularShape接口). 
 
interface IRectangularShape: IShape { int GetWidth(); } 
 
如果你从两个或者两个以上的接口派生,父接口的名字列表用逗号分隔,如下面的代码所示: 
 
interface INewInterface: IParent1, IParent2 { } 
 
然而,与Java不同,C#中的接口不能包含域(Field). 
 
另外还要注意,在C#中,接口内的所有方法默认都是公用方法.在Java中,方法声明可以带有public修饰符(即使这并非必要),但在C#中,显式为接口的方法指定public修饰符是非法的.例如,下面的C#接口将产生一个编译错误. 
 
interface IShape { public void Draw(); } 
 
2.16、is和as操作符 
 
C#中的is操作符与Java中的instanceof操作符一样,两者都可以用来测试某个对象的实例是否属于特定的类型.在Java中没有与C#中的as操作符等价的操作符.as操作符与is操作符非常相似,但它更富有"进取心":如果类型正确的话,as操作符会尝试把被测试的对象引用转换成目标类型;否则,它把变量引用设置成null. 
 
为正确理解as操作符,首先请考虑下面这个例子中is操作符的运用.这个例子包含一个IShape接口,以及两个实现了IShape接口的类Rectangle和Circle. 
 
using System; 
 
interface IShape { 
 
void draw(); 
 
} 
 
public class Rectangle: IShape { 
 
public void draw() { 
 
} 
 
public int GetWidth() { 
 
return 6; 
 
} 
 
} 
 
public class Circle: IShape { 
 
public void draw() { 
 
} 
 
public int GetRadius() { 
 
return 5; 
 
} 
 
} 
 
public class LetsDraw { 
 
public static void Main(String[] args) { 
 
IShape shape = null; 
 
if (args[0] == "rectangle") { 
 
shape = new Rectangle(); 
 
} 
 
else if (args[0] == "circle") { 
 
shape = new Circle(); 
 
} 
 
if (shape is Rectangle) { 
 
Rectangle rectangle = (Rectangle) shape; 
 
Console.WriteLine("Width : " + rectangle.GetWidth()); 
 
} 
 
if (shape is Circle) { 
 
Circle circle = (Circle) shape; 
 
Console.WriteLine("Radius : " + circle.GetRadius()); 
 
} 
 
} 
 
} 
 
编译好代码之后,用户可以输入"rectangle"或者"circle"作为Main方法的参数.如果用户输入的是"circle",则shape被实例化成为一个Circle类型的对象;反之,如果用户输入的是"rectangle",则shape被实例化成为Rectangle类型的对象.随后,程序用is操作符测试shape的变量类型:如果shape是一个矩形,则shape被转换成为Rectangle对象,我们调用它的GetWidth方法;如果shape是一个圆,则shape被转换成为一个Circle对象,我们调用它的GetRadius方法. 
 
如果使用as操作符,则上述代码可以改成如下形式: 
 
using System; 
 
interface IShape { 
 
void draw(); 
 
} 
 
public class Rectangle: IShape { 
 
public void draw() { 
 
} 
 
public int GetWidth() { 
 
return 6; 
 
} 
 
} 
 
public class Circle: IShape { 
 
public void draw() { 
 
} 
 
public int GetRadius() { 
 
return 5; 
 
} 
 
} 
 
public class LetsDraw { 
 
public static void Main(String[] args) { 
 
IShape shape = null; 
 
if (args[0] == "rectangle") { 
 
shape = new Rectangle(); 
 
} 
 
else if (args[0] == "circle") { 
 
shape = new Circle(); 
 
} 
 
Rectangle rectangle = shape as Rectangle; 
 
if (rectangle != null) { 
 
Console.WriteLine("Width : " + rectangle.GetWidth()); 
 
} 
 
else { 
 
Circle circle = shape as Circle; 
 
if (circle != null) 
 
Console.WriteLine("Radius : " + circle.GetRadius()); 
 
} 
 
} 
 
} 
 
在上面代码的粗体部分中,我们在没有测试shape对象类型的情况下,就用as操作符把shape转换成Rectangle类型的对象.如果shape正好是一个Rectangle,则shape被转换成为Rectangle类型的对象并保存到rectangle变量,然后我们调用它的GetWidth方法.如果这种转换失败,则我们进行第二次尝试.这一次,shape被转换成为Circle类型的对象并保存到circle变量.如果shape确实是一个Circle对象,则circle现在引用了一个Circle对象,我们调用它的GetRadius方法. 
 
2.17、库C#没有自己的类库.但是,C#共享了.NET的类库.当然,.NET类库也可以用于其他.NET语言,比如VB.NET或者JScript.NET.值得一提的是StringBuilder类,它是对String类的补充.StringBuilder类与Java的StringBuffer类非常相似. 
 
2.18、垃圾收集 
 
C++已经让我们认识到手工管理内存是多么缺乏效率和浪费时间.当你在C++中创建了一个对象,你就必须手工地拆除这个对象.代码越复杂,这个任务也越困难.Java用垃圾收集器来解决这个问题,由垃圾收集器搜集不再使用的对象并释放内存.C#同样采用了这种方法.应该说,如果你也在开发一种新的OOP语言,追随这条道路是一种非常自然的选择.C#仍旧保留了C++的内存手工管理方法,它适合在速度极端重要的场合使用,而在Java中这是不允许的. 
 
2.19、异常处理 
 
如果你听说C#使用与Java相似的异常处理机制,你不会为此而惊讶,对吧?在C#中,所有的异常都从一个名为Exception的类派生(听起来很熟悉?)另外,正如在Java中一样,你还有熟悉的try和catch语句.Exception类属于.NET System名称空间的一部分. 
 
三、Java没有的功能 
 
C#出生在Java成熟之后,因此,C#拥有一些Java(目前)还没有的绝妙功能也就不足为奇. 
 
3.1、枚举器 
 
枚举器即enum类型(Enumerator,或称为计数器),它是一个相关常量的集合.精确地说,enum类型声明为一组相关的符号常量定义了一个类型名字.例如,你可以创建一个名为Fruit(水果)的枚举器,把它作为一个变量值的类型使用,从而把变量可能的取值范围限制为枚举器中出现的值. 
 
public class Demo { 
 
public enum Fruit { 
 
Apple, Banana, Cherry, Durian 
 
} 
 
public void Process(Fruit fruit) { 
 
switch (fruit) { 
 
case Fruit.Apple: 
 
... 
 
break; 
 
case Fruit.Banana: 
 
... 
 
break; 
 
case Fruit.Cherry: 
 
... 
 
break; 
 
case Fruit.Durian: 
 
... 
 
break; 
 
} 
 
} 
 
} 
 
在上例的Process方法中,虽然你可以用int作为myVar变量的类型,但是,使用枚举器Fruit之后,变量的取值范围限制到了Applet、Banana、Cherry和Durian这几个值之内.与int相比,enum的可读性更好,自我说明能力更强. 
 
3.2、结构 
 
结构(Struct)与类很相似.然而,类是作为一种引用类型在堆中创建,而结构是一种值类型,它存储在栈中或者是嵌入式的.因此,只要谨慎运用,结构要比类快.结构可以实现接口,可以象类一样拥有成员,但结构不支持继承. 
 
然而,简单地用结构来取代类可能导致惨重损失.这是因为,结构是以值的方式传递,由于这种传递方式要把值复制到新的位置,所以传递一个"肥胖的"结构需要较大的开销.而对于类,传递的时候只需传递它的引用. 
 
下面是一个结构的例子.注意它与类非常相似,只要把单词"struct"替换成"class",你就得到了一个类. 
 
struct Point { 
 
public int x, y; 
 
public Point(int x, int y) { 
 
this.x = x; 
 
this.y = y; 
 
} 
 
} 
 
3.3、属性 
 
C#类除了可以拥有域(Field)之外,它还可以拥有属性(Property).属性是一个与类或对象关联的命名的特征.属性是域的一种自然扩展――两者都是有类型、有名字的类成员.然而,和域不同的是,属性不表示存储位置;相反,属性拥有存取器(accessor),存取器定义了读取或者写入属性值时必须执行的代码.因此,属性提供了一种把动作和读取、写入对象属性值的操作关联起来的机制,而且它们允许属性值通过计算得到. 
 
在C#中,属性通过属性声明语法定义.属性声明语法的第一部分与域声明很相似,第二部分包括一个set过程和/或一个get过程.例如,在下面的例子中,PropertyDemo类定义了一个Prop属性. 
 
public class PropertyDemo { 
 
private string prop; 
 
public string Prop { 
 
get { 
 
return prop; 
 
} 
 
set { 
 
prop = value; 
 
} 
 
} 
 
} 
 
如果属性既允许读取也允许写入,如PropertyDemo类的Prop属性,则它同时拥有get和set存取过程.当我们读取属性的值时,get存取过程被调用;当我们写入属性值时,set存取过程被调用.在set存取过程中,属性的新值在一个隐含的value参数中给出. 
 
与读取和写入域的方法一样,属性也可以用同样的语法读取和写入.例如,下面的代码实例化了一个PropertyDemo类,然后写入、读取它的Prop属性. 
 
PropertyDemo pd = new PropertyDemo(); 
 
pd.Prop = "123"; // set 
 
string s = pd.Prop; // get 
 
3.4、以引用方式传递简单数据类型的参数 
 
在Java中,当你把一个简单数据类型的值作为参数传递给方法时,参数总是以值的方式传递――即,系统将为被调用的方法创建一个参数值的副本.在C#中,你可以用引用的方式传递一个简单数据类型的值.此时,被调用的方法将直接使用传递给它的那个值――也就是说,如果在被调用方法内部修改了参数的值,则原来的变量值也随之改变. 
 
在C#中以引用方式传递值时,我们使用ref关键词.例如,如果编译并运行下面的代码,你将在控制台上看到输出结果16.注意i值被传递给ProcessNumber之后是如何被改变的. 
 
using System; 
 
public class PassByReference { 
 
public static void Main(String[] args) { 
 
int i = 8; 
 
ProcessNumber(ref i); 
 
Conso...