Skip navigation.

exploreopera

| Help

Sign up | Help

王志军个人空间

工作、生活、健康

Posts tagged with "C++"

Effective C++ 读书笔记

URL: http://my.opera.com/Maple2005/blog/show.dml/21742

#从C转向C++


条款1:尽量用const和inline(编译器)而不用#define(预处理)
理由:
a.#define(预处理)进入编译器之前预处理程序会将符号去掉,代以常量,在编译的时候报错信息指向常量而难以理解,定义一个const常量能很好的解决这个问题;
b. 用#define来实现那些看起来象函数而又不会导致函数调用的宏(eg: #define max(a,b) ((a) > (b) ? (a) : (b))),会发生奇怪而易错的事情,而使用内联函数安全而同样高效(eg: inline int max(int a, int b) { return a > b ? a : b; })。
注意:
1、const定义指针常量时会,除了指针的指向类型要定义成const外,指针也常要定义成const(eg: const char * const authorName = "Scott Meyers";);
2、定义某个类的常量首先要使它成为类的静态成员(eg:
class GamePlayer {
private:
static const int NUM_TURNS = 5; // constant eclaration
int scores[NUM_TURNS];// use of constant
...
),这样还要在类的实现代码中定义类的静态成员(之前只是声明)(eg: const int GamePlayer::NUM_TURNS;);
3、使用模版可以解决内联函数处理类型的限制(eg: template<class T>
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; },只要a、b可以转换成同种类型即可比较);
4、在使用95年之前的部分编译器时,也许会在定义某个类的常量时还会遇到部分问题,详见原书。

条款2:尽量用“iostream”而不用“stdio.h”
理由:
a.scanf/printf系列函数不是类型安全的,而且没有扩展性;
b.scanf/printf系列函数把要读写的变量和控制读写格式的信息分开来,而在使用">>和<<"时编译器可以根据不同的变量类型选择操作符的不同形式(在使用非标准用法时需要operator重载);
c.在传递读和写的对象时>>和<<采用的语法形式相同,所以不必像scanf那样死记一些规定,在这里Scott说:"编译器没别的什么事好做的,而你却不一样。"
注意:
1、在形如"friend ostream& operator<<(ostream& s, const Rational& );"的重载时operator<<不是成员函数,而传递给operator<<的不是Rational对象,而是定义为 const的对象的引用;
2、在一些特殊的实现内,iostream的操作实现起来比相应的C stream效率要低;
3、对于iostream库,不同的厂商遵循标准的程度也不同;
4、iostream库的类有构造函数而<stdio.h>里的函数没有,在某些涉及到静态对象初始化顺序的时候,如果可以确认不会带来隐患,用标准C库会更简单实用;
5、 <iostream.h>已经简化为<iostream>,当编译器同时支持 <iostream>和<iostream.h>时,如果使用了#include <iostream> 得到的是置于名字空间std(见条款28)下的iostream库的元素;如果使用#include <iostream.h>,得到的是置于全局空间的同样的元素。在全局空间获取元素会导致名字冲突,而设计名字空间的初衷正是用来避免这种名字冲突的发生。在这个地方Scott补充说:"打字时<iostream>比<iostream.h>少两个字,这也是很多人用它的原因。:)"。

条款3:尽量用new和delete而不用malloc和free
理由:
malloc 和free(及其变体)不知道构造函数和析构函数,当使用malloc申请内存空间的时候,在内存中实际上并没有创建这些对象,而即使已经创建这些对象,在释放的时候由于对象并不会调用析构函数,这些内存将丢失,而new和delete可以有效地与构造函数和析构函数交互。
注意:
new/delete和malloc/free不兼容,混用在一起回导致不可预测的结果,特别要注意一些函数内部到底使用malloc还是new分配内存的。

条款4:尽量使用C++风格的注释
理由:
在使用/**/嵌套注释的时候有可能会将注释提前结束。

#内存管理


条款5:对应的new和delete要采用相同的形式
理由:
否则会导致不可预见的结果
注意:
1、必须自己告诉delete要被删除的指针指向的是单个对象呢还是对象数组,后者需要使用"[]"(eg: delete [] stringptr2;),在删除单个对象时使用"[]"或者在删除数组对象的时候没有使用"[]"都将导致不可预见的结果。解决方法Scott总结为"如果你调用new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]。";
2、在使用typedef的时候,如果用new创建一个typedef定义的类型的对象后,该用什么形式的delete来删除。

条款6:析构函数里对指针成员调用delete
理由:
内存泄露的不断增长最终会导致程序夭折,因此在每增加一个指针成员到类里的时候要在析构函数里面delete。

条款7:预先准备好内存不够的情况
理由:
Scott在论述这个问题时说:"处理内存不够所产生的异常真可以算得上是个道德上的行为,但实际做起来又会象刀架在脖子上那样痛苦。所以,你有时会不去管它,也许一直没去管它。但你心里一定还是深深地隐藏着一种罪恶感:万一new真的产生了异常怎么办?"
注意:
1、当内存分配请求不能满足时,调用你预先指定的一个出错处理函数比简单地让系统内核产生错误信息来结束程序要好,因为后者无法对每种形式的异常进行处理;
2、 C++不支持专门针对于类的new-handler函数,需要自己实现--在每个类中提供自己版本的set_new_handler(保存传给它的任何指针,并返回在调用它之前所保存的任何指针)和operator new(保证为类的对象分配内存时用类的new-handler取代全局new-handler);
3、创建实现类的new-handler功能的基类(让所有的子类可以继承set_new_handler和operator new功能)以及设计模版使每个子类有不同的currenthandler数据成员,可设计出可重用代码(原书中给出了较为详尽的例子)。

条款8: 写operator new和operator delete时要遵循常规
理由:
函数的行为要和系统缺省的operator new一致,这包括了正确的返回值、可用内存不够时要调用出错处理函数等。
注意:
1、处理零字节请求的技巧在于把它作为请求一个字节来处理,operator new返回一个合法指针;
2、使用一个无限循环使得new-handler必须获得更多的内存、抛出异常或者返回失败这三件事中的一件;
3、如果想控制基于类的数组的内存分配,必须实现operator new[]。

条款9: 避免隐藏标准形式的new
理由:
在类里定义了一个称为"operator new"的函数后,会不经意地阻止了对标准new的访问
注意:
1、解决方法之一是在类里写一个支持标准new调用方式的operator new,它和标准new做同样的事;
2、解决方法之二是为每一个增加到operator new的参数提供缺省值。

条款10: 如果写了operator new就要同时写operator delete
理由:
1、当调用缺省operator new来分配对象时,得到的内存由于额外的数据信息可能要比存储这个指针(或一对指针)所需要的要多,使用自己定义版本的operator new将会得到更少的内存和更快的速度;
2、自行定义的operator new返回了一个不带头信息的内存的指针,而缺省的operator delete却假设传给它的内存包含头信息,两者不同时写将导致前后的不匹配。
注意:
1、在类的声明时表头指针声明为静态成员使得整个类只有一个自由链表而不是每个对象都有;
2、如果要删除的对象是从一个没有虚析构函数的类继承而来的,那传给operator delete的size值有可能不正确,因此必须保证基类必须要有虚析构函数;
3、在以上设计的operator new和operator delete中没有内存泄露,因为每一小块内存都放在自由链表中,所有的内存块要不被对象使用(由客户来负责避免内存泄露),要不就在自由链表上;
4、定义一个专门的类,使他的每个对象均是某类对象的内存分配器可提高代码的可重用性。

#构造函数,析构函数和赋值操作符


条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
理由:
使用缺省的拷贝和复制会产生一系列不良结果--例如在进行字符串的拷贝时,被拷贝指针曾指向的内存永远不会被删除而产生内存泄露;或者两个指针中任何一个调用析构函数都将导致另一指针指向的那块内存被删除等。
注意:
1、用delete去删除一个已经被删除的指针,其结果是不可预测的,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数;
2、在不需要实现拷贝构造函数和赋值操作符的时候,可以只声明这些函数(声明为private成员)而不去定义它们,这就防止了会有人去调用它们。

条款12: 尽量使用初始化而不要在构造函数里赋值
a.一些情况如const和引用数据成员只能用初始化,不能被赋值;
b.成员初始化列表还是比在构造函数里赋值效率要高。
注意:
1、如果用一个成员初始化列表来指定name必须用initname来初始化,name就会通过拷贝构造函数以仅一个函数调用的代价被初始化;
2、当有大量的固定类型的数据成员要在每个构造函数里以相同的方式初始化的时候对类的数据成员用赋值比用初始化更合理;
3、静态成员在程序运行的过程中只被初始化一次,所以每当类的对象创建时都去"初始化"它们没有任何意义。

条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同
理由:
类成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初始化列表中列出的顺序没一点关系--如果这种情况发生,编译器就要为每一个对象跟踪其成员初始化的顺序,以保证它们的析构函数以正确的顺序被调用--这将带来昂贵的开销。
注意:
初始化列表中成员列出的顺序和成员在类内声明的顺序保持一致。

条款14: 确定基类有虚析构函数
理由:
当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。Scott在这里说:"这意味着编译器生成的代码将会做任何它喜欢的事:重新格式化你的硬盘,给你的老板发电子邮件,把你的程序源代码传真给你的对手,无论什么事都可能发生。"∶P
注意:
1、使基类有虚构函数virtual,让派生类去定制自己的行为;
2、当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意,因为包含虚函数将使对象的体积翻番,而且降低代码的可移植性下降;
3、在定义抽象类的时候,定义纯虚构函数(eg: awov::~awov() {});

条款15: 让operator=返回*this的引用
理由:
采用缺省形式定义的赋值运算符里,对象返回值有两个很明显的候选者:赋值语句左边的对象(被this指针指向的对象)和赋值语句右边的对象(参数表中被命名的对象),在自定义的operator=中返回右边对象的版本往往不能通过
注意:
1、不要让operator=返回void--它妨碍了连续(链式)赋值操作;
2、不要让operator=返回const对象的引用;
3、当定义自己的赋值运算符时,必须返回赋值运算符左边参数的引用。

条款16: 在operator=中对所有数据成员赋值
理由:
只要想对赋值过程的某一个部分进行控制,就必须负责做赋值过程中所有的事。
注意:
1、当类里增加新的数据成员时,也要记住更新赋值运算符函数;
2、派生类的赋值运算符也必须处理它的基类成员的赋值。

条款17: 在operator=中检查给自己赋值的情况
理由:
自己给自己赋值的情况有可能发生在对同一对象不同名字的赋值,这样delete会将该对象删除,从而导致不可预见的结果发生。
注意:
1、可能发生的自己给自己赋值的情况先进行检查,如果该情况发生就立即返回;
2、一个方法是对相同内容的对象认为是同一对象(用operator==实现);
3、另一个确定对象身份是否相同的方法是用内存地址。

#类和函数:设计和声明


条款18: 争取使类的接口完整并且最小
理由:
a.接口中函数越多,以后的潜在用户就越难理解(也容易产生混淆),他们越难理解,就越不愿意去学该怎么用;
b.大的类接口难以维护;
c.长的类定义会导致长的头文件,浪费编译时间。

条款19: 分清成员函数,非成员函数和友元函数
注意:
1、成员函数和非成员函数最大的区别在于成员函数可以是虚拟的而非成员函数不行;
2、explicit构造函数不能用于隐式转换;
3、如果需要的话,编译器会对每个函数的每个参数执行隐式类型转换——但它只对函数参数表中列出的参数进行转换,决不会对成员函数所在的对象(即,成员函数中的*this指针所对应的对象)进行转换;
4、只要能避免使用友元函数就要避免;(在这个地方Scott说:“和现实生活中差不多,友元(朋友)带来的麻烦往往比它(他/她)对你的帮助多。”)
5、很多情况下,不是成员的函数从概念上说也可能是类接口的一部分,它们需要访问类的非公有成员的情况也不少。
6、最后Scott帮助我们总结该条款得出的结论——假设f是想正确声明的函数,c是和它相关的类:
•虚函数必须是成员函数。如果f必须是虚函数,就让它成为c的成员函数。
•operator>>和operator<<决不能是成员函数。如果f是operator>>或operator<<,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
•只有非成员函数对最左边的参数进行类型转换。如果f需要对最左边的参数进行类型转换,让f成为非成员函数。如果f还需要访问c的非公有成员,让f成为c的友元函数。
•其它情况下都声明为成员函数。如果以上情况都不是,让f成为c的成员函数。

条款20: 避免public接口出现数据成员
理由:
a.如果public接口里都是函数,用户每次访问类的成员时就用不着“抓脑袋”去想是否要使用括号;
b.如果使数据成员为public,每个人都可以对它读写,如果用函数来获取或设定它的值,就可以实现禁止访问、只读访问和读写访问等多种控制;
c.如果用函数来实现对数据成员的访问,以后就有可能用一段计算来取代这个数据成员,而使用这个类的用户却一无所知。

条款21: 尽可能使用const
理由:
通知编译器某种对象不能修改。
注意:
1、在判断到底指定指针本身为const,还是指定指针所指的数据为const,或二者同时指定为const……这诸多情况时,Scott告诉我们一种简单的方法——“画一条垂直线穿过指针声明中的星号(*)位置,如果const出现在线的左边,指针指向的数据为常量;如果const出现在线的右边,指针本身为常量;如果const在线的两边都出现,二者都是常量。”;
2、在指针所指为常量的情况下,有些程序员喜欢把const放在类型名之前,有些程序员则喜欢把const放在类型名之后、星号之前,这两种情况相同;
3、对函数返回值使用const有可能提高一个函数的安全性和效率;
4、mutable是C++标准组织为解决部分有关const问题的一个方案,他们可以在任何地方被修改,即使在const成员函数里;
5、在成员函数中尝试“消除const”往往会导致不可确定的后果。

未完待续

比较C++, C#和Java

,

对比C++和Java

事实上,Java本来就是从C++衍生出来的。
然而,C++和Java之间仍存在一些显著的差异。可以这样说,这些差异代表着技术的极大进步。一旦我们弄清楚了这些差异,就会理解为什么说Java是一种优秀的程序设计语言。本附录将引导大家认识用于区分Java和C++的一些重要特征。
(1) 最大的障碍在于速度:解释过的Java要比C的执行速度慢上约20倍。无论什么都不能阻止Java语言进行编译。写作本书的时候,刚刚出现了一些准实时编译器,它们能显著加快速度。当然,我们完全有理由认为会出现适用于更多流行平台的纯固有编译器,但假若没有那些编译器,由于速度的限制,必须有些问题是 Java不能解决的。
(2) 和C++一样,Java也提供了两种类型的注释。
(3) 所有东西都必须置入一个类。不存在全局函数或者全局数据。如果想获得与全局函数等价的功能,可考虑将static方法和static数据置入一个类里。注意没有象结构、枚举或者联合这一类的东西,一切只有“类”(Class)!
(4) 所有方法都是在类的主体定义的。所以用C++的眼光看,似乎所有函数都已嵌入,但实情并非如何(嵌入的问题在后面讲述)。
(5) 在Java中,类定义采取几乎和C++一样的形式。但没有标志结束的分号。没有class foo这种形式的类声明,只有类定义。

class aType()
void aMethod() {/* 方法主体 */}
}

(6) Java中没有作用域范围运算符“::”。Java利用点号做所有的事情,但可以不用考虑它,因为只能在一个类里定义元素。即使那些方法定义,也必须在一个类的内部,所以根本没有必要指定作用域的范围。我们注意到的一项差异是对static方法的调用:使用ClassName.methodName()。除此以外,package(包)的名字是用点号建立的,并能用import关键字实现C++的“#include”的一部分功能。例如下面这个语句:
import java.awt.*;
(#include并不直接映射成import,但在使用时有类似的感觉。)
(7) 与C++类似,Java含有一系列“主类型”(Primitive type),以实现更有效率的访问。在Java中,这些类型包括boolean,char,byte,short,int,long,float以及 double。所有主类型的大小都是固有的,且与具体的机器无关(考虑到移植的问题)。这肯定会对性能造成一定的影响,具体取决于不同的机器。对类型的检查和要求在Java里变得更苛刻。例如:
■条件表达式只能是boolean(布尔)类型,不可使用整数。
■必须使用象X+Y这样的一个表达式的结果;不能仅仅用“X+Y”来实现“副作用”。
(8) char(字符)类型使用国际通用的16位Unicode字符集,所以能自动表达大多数国家的字符。
(9) 静态引用的字串会自动转换成String对象。和C及C++不同,没有独立的静态字符数组字串可供使用。
(10) Java增添了三个右移位运算符“>>>”,具有与“逻辑”右移位运算符类似的功用,可在最末尾插入零值。“>>”则会在移位的同时插入符号位(即“算术”移位)。
(11) 尽管表面上类似,但与C++相比,Java数组采用的是一个颇为不同的结构,并具有独特的行为。有一个只读的length成员,通过它可知道数组有多大。而且一旦超过数组边界,运行期检查会自动丢弃一个异常。所有数组都是在内存“堆”里创建的,我们可将一个数组分配给另一个(只是简单地复制数组句柄)。数组标识符属于第一级对象,它的所有方法通常都适用于其他所有对象。
(12) 对于所有不属于主类型的对象,都只能通过new命令创建。和C++不同,Java没有相应的命令可以“在堆栈上”创建不属于主类型的对象。所有主类型都只能在堆栈上创建,同时不使用new命令。所有主要的类都有自己的“封装(器)”类,所以能够通过new创建等价的、以内存“堆”为基础的对象(主类型数组是一个例外:它们可象C++那样通过集合初始化进行分配,或者使用new)。
(13) Java中不必进行提前声明。若想在定义前使用一个类或方法,只需直接使用它即可——编译器会保证使用恰当的定义。所以和在C++中不同,我们不会碰到任何涉及提前引用的问题。
(14) Java没有预处理机。若想使用另一个库里的类,只需使用import命令,并指定库名即可。不存在类似于预处理机的宏。
(15) Java用包代替了命名空间。由于将所有东西都置入一个类,而且由于采用了一种名为“封装”的机制,它能针对类名进行类似于命名空间分解的操作,所以命名的问题不再进入我们的考虑之列。数据包也会在单独一个库名下收集库的组件。我们只需简单地“import”(导入)一个包,剩下的工作会由编译器自动完成。
(16) 被定义成类成员的对象句柄会自动初始化成null。对基本类数据成员的初始化在Java里得到了可靠的保障。若不明确地进行初始化,它们就会得到一个默认值(零或等价的值)。可对它们进行明确的初始化(显式初始化):要么在类内定义它们,要么在构建器中定义。采用的语法比C++的语法更容易理解,而且对于 static和非static成员来说都是固定不变的。我们不必从外部定义static成员的存储方式,这和C++是不同的。
(17) 在Java里,没有象C和C++那样的指针。用new创建一个对象的时候,会获得一个引用(本书一直将其称作“句柄”)。例如:
String s = new String("howdy";);
然而,C++引用在创建时必须进行初始化,而且不可重定义到一个不同的位置。但Java引用并不一定局限于创建时的位置。它们可根据情况任意定义,这便消除了对指针的部分需求。在C和C++里大量采用指针的另一个原因是为了能指向任意一个内存位置(这同时会使它们变得不安全,也是Java不提供这一支持的原因)。指针通常被看作在基本变量数组中四处移动的一种有效手段。Java允许我们以更安全的形式达到相同的目标。解决指针问题的终极方法是“固有方法” (已在附录A讨论)。将指针传递给方法时,通常不会带来太大的问题,因为此时没有全局函数,只有类。而且我们可传递对对象的引用。Java语言最开始声称自己“完全不采用指针!”但随着许多程序员都质问没有指针如何工作?于是后来又声明“采用受到限制的指针”。大家可自行判断它是否“真”的是一个指针。但不管在何种情况下,都不存在指针“算术”。
(18) Java提供了与C++类似的“构建器”(Constructor)。如果不自己定义一个,就会获得一个默认构建器。而如果定义了一个非默认的构建器,就不会为我们自动定义默认构建器。这和C++是一样的。注意没有复制构建器,因为所有自变量都是按引用传递的。
(19) Java中没有“破坏器”(Destructor)。变量不存在“作用域”的问题。一个对象的“存在时间”是由对象的存在时间决定的,并非由垃圾收集器决定。有个finalize()方法是每一个类的成员,它在某种程度上类似于C++的“破坏器”。但finalize()是由垃圾收集器调用的,而且只负责释放“资源”(如打开的文件、套接字、端口、URL等等)。如需在一个特定的地点做某样事情,必须创建一个特殊的方法,并调用它,不能依赖 finalize()。而在另一方面,C++中的所有对象都会(或者说“应该”)破坏,但并非Java中的所有对象都会被当作“垃圾”收集掉。由于 Java不支持破坏器的概念,所以在必要的时候,必须谨慎地创建一个清除方法。而且针对类内的基础类以及成员对象,需要明确调用所有清除方法。
(20) Java具有方法“过载”机制,它的工作原理与C++函数的过载几乎是完全相同的。


C#和Java的对比

自从互联网出现以来,Java就成了许多开发者的首选.而许多C++开发者在较短的时间内转移到了Java开发者的队伍中来. Java成功的开发和实现了许多高质量的多线程动画, 游戏, 以及分布的应用程序. 更重要的是,Java 语言是完全平台无关的.
被称作Apllets(小应用程序)的Java程序在被下载到本地以前必须首先通过安全认证,这样就避免了任何病毒钻入用户的计算机的企图. 许多新的改进,诸如旋摆软件包(swing package), Java2D API, 连网软件包(java .net)在最初的Java开发包发布以后被加进了这种语言里. 从Sun公司发布Java以来,它的许多竞争对手开发了这种热门语言的替代品,一些公司甚至在两年以前就开始了.
现在一家无需更多介绍的公司--微软已经发布了一种新的叫做微软.NET的技术. .NET家族的一种主要的语言是C#或者说C-sharp. 这种语言是从Java/C++继承而来的. 从我的对C#的六个月的技术上的经验来说,我觉得C#或多或少与Java是相似的.但是C++的许多功能,比方说操作符重载(它在Java中已经被移去了)还存在于C#中.
Java程序能够在安装了Java虚拟机或者JVM的任何平台上运行.但是.NET程序的平台要求安装了通用语言运行库或者称做CLR. 它是所有.NET语言运行所需的运行库.所以一种.NET语言能够调用另一种.NET语言写成的模块和函数. 更进一步的说,如果你学习了种像C#之类的语言,那么你就很容易学习另一种.NET语言因为所有的.NET语言遵循微软所谓的.NET Framework(架构). 这种架构包括了编写和安装健壮的.NET应用程序所必需的类库.使用.NET你可以通过ASP.NET进行服务器端的编程,这点非常像JAVA中的 servlets.
就象Java中的Applets, C#引入了WinForms 和WebForms的概念.使用WinForms你可以设计基于Windows的应用程序而使用WebForms你能够开发Web应用程序. 这点可以要么通过C#编程或者是使用Visual Studio.NET这样的开发环境工具来实现. 它包含了Visual C#,使用Visual C#你可以非常容易的编写C#应用程序.
在我们使用Visual C++编程的时候,WinForms几乎就是win32程序.用C++来开发这些程序是可能的.但是VC++给我们提供向导来简化我们的开发过程.现在 Visual C#也在做同样的工作.因此如果你已经是一个VC++的程序员了,那么你最好试一试VC#.

Java程序和C#程序的比较:我们应该比较一下用Java写成的程序和C#的有什么不同. 我们将讨论两个程序间主要的不同点.

Hello Java 程序

class Hello { // 类声明
public static void main(String args[]) { // main 方法,入口点
System.out.println("Hello Java";); // 向控制台输出Hello Java
}
}


Hello C# 程序

using System; // 调用System命名空间
class Hello { // 类声明
public static void Main () { // main 方法, 入口点
console.writeLine("Hello C#";); // 向控制台输出 Hello C#
}
}
在Java中, lang软件包是自动引入的.在简单的程序里没有必要引入这个包.但是在C#中,我们不得不在所有的程序里调用System命名空间. 请记住控制台是System命名空间下的一个类. 试着运行一下上面的程序并查看它们的输出结果.

Main方法

Java:你有可能编写一个没有main方法的Java程序,只要这个类是用来作为父类.但是只有类中包含有正确的Main方法的时候程序才能运行.
C#:没有正确的Main方法就不可能编译C#源程序.如果main方法缺失了,那么编译器就会显示一个错误消息:程序’filename.exe’没有定义入口点.

继承:
Java 和C#都不支持多重继承而且都提供接口的概念作为多重继承的替代品.但是C#在接口中包括了更高级的内容.请在本文末找出题为新增 加的特点的内容.
操作符和控制流语句:
Java中出现的所有诸如算术,逻辑,自增和自减等操作符都可以在C#中找到. 而Java中的像if, else, if-else, for, do-while, while等控 制流语句C#统统支持. 但是C# 增加了C++中流行的goto语句.
异常处理:
在C#中你可以重用try, catch, finally, throws 语句处理运行时错误(通常也叫做异常).C# 中的异常处理过程除了一些小的不同以外几乎 与Java完全相同. C#中System.Exception命名空间是所有异常类的基类.
多线程:
C#中多线程的应用比起Java来要容易得多.我们可以使用Monitor 和 Mutex类同步线程.
新增特点:
索引,属性和代理是C#中的新概念.索引几乎与Java中的向量相同. 对其新特点的完全的讨论已经超出了我们的范围.但是我将尽量在这个网 站的其它地方解释它们. C#引入了Java所没有的操作符重载的概念.但是Java和C#都支持方法重载.
让我们回到对接口的讨论,我们能够验证包括在接口中的方法是否是使用is 和 as 操作符实现的.当我们实现接口的时候能避免命名冲突.你甚至可以将两个接口结合到一个中来,并在类中实现这个复合的接口,如下面的代码所示:

using System;
public interface First {
void A();
void B();
}
public interface Second {
void C();
}
public interface Combined: First, Second { }
class Combineimple: Combined {
// 这里是程序代码
// main方法
} }
而且C#在显示编译错误的时候还显示它们的错误代码.你可以搜索与SDK有关的在线文档寻找和这个错误代码有关的内容并找出这个错误发生的原因的细节.