`
nathan09
  • 浏览: 144680 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

c++,C#,java中的类

 
阅读更多

C++:

1 纯虚函数:

纯虚函数是一种特殊的虚函数,它的一般格式如下:

class< 类名 >

{

virtual < 类型 >< 函数名 >(< 参数表 >)=0;

……

};

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

一般情况下可将一个成员函数在基类中声明为纯虚函数,其实现在派生类中完成。

2、抽象类:

抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。

(1)抽象类的定义:

称带有纯虚函数的类为抽象类。

(2)抽象类的作用:

抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。

(3)使用抽象类时注意:

? 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

? 抽象类是不能定义对象的。

虚函数

虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。

下面是对C++的虚函数这玩意儿的理解。

什么是虚函数

(如果不知道虚函数为何物,但有急切的想知道,那你就应该从这里开始)

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码

class A{

public:

void print(){ cout<<”This is A”<<endl;}

};

class B:public A{

public:

void print(){ cout<<”This is B”<<endl;}

};

int main(){ //为了在以后便于区分,我这段main()代码叫做main1

A a;

B b;

a.print();

b.print();

}

通过class Aclass Bprint()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is AThis is B。但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。

int main(){ //main2

A a;

B b;

A* p1=&a;

A* p2=&b;

p1->print();

p2->print();

}

运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A。问题来了,p2明明指向的是class B的对象但却是调用的class Aprint()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数

class A{

public:

virtual void print(){ cout<<This is A<<endl;} //现在成了虚函数了

};

class B:public A{

public:

void print(){ cout<<This is B<<endl;} //这里需要在前面加上关键字virtual吗?

};

毫无疑问,class A的成员函数print()已经成了虚函数,那么class Bprint()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class Bprint()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了。

现在重新运行main2的代码,这样输出的结果就是This is AThis is B了。

现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

虚函数是如何做到的

(如果你没有看过《Inside The C++ Object Model》这本书,但又急切想知道,那你就应该从这里开始)

虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类

class A{ //虚函数示例代码

public:

virtual void fun(){cout<<1<<endl;}

virtual void fun2(){cout<<2<<endl;}

};

class B:public A{

public:

void fun(){cout<<3<<endl;}

void fun2(){cout<<4<<endl;}

};

由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtblvtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图

通过上图,可以看到这两个vtbl分别为class Aclass B服务。现在有了这个模型之后,我们来分析下面的代码

A *p=new A;

p->fun();

毫无疑问,调用了A::fun(),但是A::fun()是如何被调用的呢?它像普通函数那样直接跳转到函数的代码处吗?No,其实是这样的,首先是取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl这里,由于调用的函数A::fun()是第一个虚函数,所以取出vtbl第一个slot里的值,这个值就是A::fun()的地址了,最后调用这个函数。现在我们可以看出来了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务。

而对于class Aclass B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class Aclass B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。通过上面的分析,现在我们来实作一段代码,来描述这个带有虚函数的类的简单模型。

#include<iostream>

using namespace std;

//将上面“虚函数示例代码”添加在这里

int main(){

void (*fun)(A*);

A *p=new B;

long lVptrAddr;

memcpy(&lVptrAddr,p,4);

memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);

fun(p);

delete p;

system("pause");

}

VCDev-C++编译运行一下,看看结果是不是输出3,如果不是,那么太阳明天肯定是从西边出来。现在一步一步开始分析

void (*fun)(A*); 这段定义了一个函数指针名字叫做fun,而且有一个A*类型的参数,这个函数指针待会儿用来保存从vtbl里取出的函数地址

A* p=new B; 这个我不太了解,算了,不解释这个了

long lVptrAddr; 这个long类型的变量待会儿用来保存vptr的值

memcpy(&lVptrAddr,p,4); 前面说了,他们的实例对象里只有vptr指针,所以我们就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中,所以复制出来的4bytes内容就是vptr的值,即vtbl的地址

现在有了vtbl的地址了,那么我们现在就取出vtbl第一个slot里的内容

memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4); 取出vtbl第一个slot里的内容,并存放在函数指针fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指针,所以我们要把它先转变成指针类型

fun(p); 这里就调用了刚才取出的函数地址里的函数,也就是调用了B::fun()这个函数,也许你发现了为什么会有参数p,其实类成员函数调用时,会有个this指针,这个p就是那个this指针,只是在一般的调用中编译器自动帮你处理了而已,而在这里则需要自己处理。

delete p;system("pause"); 这个我不太了解,算了,不解释这个了

如果调用B::fun2()怎么办?那就取出vtbl的第二个slot里的值就行了

memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4); 为什么是加4呢?因为一个指针的长度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4); 这更符合数组的用法,因为lVptrAddr被转成了long*型别,所以+1就是往后移sizeof(long)的长度

三, 以一段代码开始

#include<iostream>

using namespace std;

class A{ //虚函数示例代码2

public:

virtual void fun(){ cout<<"A::fun"<<endl;}

virtual void fun2(){cout<<"A::fun2"<<endl;}

};

class B:public A{

public:

void fun(){ cout<<"B::fun"<<endl;}

void fun2(){ cout<<"B::fun2"<<endl;}

}; //end//虚函数示例代码2

int main(){

void (A::*fun)(); //定义一个函数指针

A *p=new B;

fun=&A::fun;

(p->*fun)();

fun = &A::fun2;

(p->*fun)();

delete p;

system("pause");

}

你能估算出输出结果吗?如果你估算出的结果是A::funA::fun2,呵呵,恭喜恭喜,你中圈套了。其实真正的结果是B::funB::fun2,如果你想不通就接着往下看。给个提示,&A::fun&A::fun2是真正获得了虚函数的地址吗?

首先我们回到第二部分,通过段实作代码,得到一个“通用”的获得虚函数地址的方法

#include<iostream>

using namespace std;

//将上面“虚函数示例代码<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" /><chmetcnv w:st="on" tcsc="0" numbertype="1" negative="False" hasspace="False" sourcevalue="2" unitname="”"><span lang="EN-US">2</span><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'">”</span></chmetcnv>添加在这里

void CallVirtualFun(void* pThis,int index=0){

void (*funptr)(void*);

long lVptrAddr;

memcpy(&lVptrAddr,pThis,4);

memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);

funptr(pThis); //调用

}

int main(){

A* p=new B;

CallVirtualFun(p); //调用虚函数p->fun()

CallVirtualFun(p,1);//调用虚函数p->fun2()

system("pause");

}

现在我们拥有一个“通用”的CallVirtualFun方法。

这个通用方法和第三部分开始处的代码有何联系呢?联系很大。由于A::fun()A::fun2()是虚函数,所以&A::fun&A::fun2获得的不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,我们形象地把这段代码看作那段CallVirtualFun。编译器在编译时,会提供类似于CallVirtualFun这样的代码,当你调用虚函数时,其实就是先调用的那段类似CallVirtualFun的代码,通过这段代码,获得虚函数地址后,最后调用虚函数,这样就真正保证了多态性。同时大家都说虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码。

最后的说明:本文的代码可以用VC6Dev-C++<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899">4.9.8</chsdate>.0通过编译,且运行无问题。其他的编译器小弟不敢保证。其中,里面的类比方法只能看成模型,因为不同的编译器的低层实现是不同的。例如this指针,Dev-C++gcc就是通过压栈,当作参数传递,而VC的编译器则通过取出地址保存在ecx中。所以这些类比方法不能当作具体实现

C#:

抽象类(abstract class):

abstract 修饰符用于表示所修饰的类是不完整的,并且它只能用作基类。抽象类与非抽象类在以下方面是不同的:

抽象类不能直接实例化,并且对抽象类使用 new

运算符是编译时错误。虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为

null,或者含有对非抽象类的实例的引用(此非抽象类是从抽象类派生的)。

允许(但不要求)抽象类包含抽象成员。

抽象类不能被密封。

当从抽象类派生非抽象类时,这些非抽象类必须具体实现所继承的所有抽象成员,从而重写那些抽象成员。在下面的示例中

abstract class A

{

public abstract void F();

}

abstract class B: A

{

public void G() {}

}

class C: B

{

public override void F() {

// actual implementation of F

}

}

抽象类 A 引入抽象方法 F。类 B 引入另一个方法 G,但由于它不提供 F 的实现,B 也必须声明为抽象类。类 C 重写

F,并提供一个具体实现。由于 C 中没有了抽象成员,因此可以(但并非必须)将 C 声明为非抽象类。

接口(interface)

接口是其他类型为确保它们支持某些操作而实现的引用类型。接口从不直接创建而且没有实际的表示形式,其他类型必须转换为接口类型。

一个接口定义一个协定。实现接口的类或结构必须遵守其协定。接口可以包含方法、属性、索引器和事件作为成员。

abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。

在类声明中使用 abstract 修饰符以指示类只能是其他类的基类。

抽象类具有以下特性:

抽象类不能实例化。

抽象类可以包含抽象方法和抽象访问器。

不能用 sealed 修饰符修改抽象类,这意味着该类不能被继承。

从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。

在方法或属性声明中使用 abstract 修饰符以指示此方法或属性不包含实现。

抽象方法具有以下特性:

抽象方法是隐式的 virtual 方法。

只允许在抽象类中使用抽象方法声明。

因为抽象方法声明不提供实实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号 ({ })。例如:

public abstract void MyMethod();实现由 overriding 方法提供,它是非抽象类的成员。

在抽象方法声明中使用 static virtual 修饰符是错误的。

除了在声明和调用语法上不同外,抽象属性的行为与抽象方法一样。

在静态属性上使用 abstract 修饰符是错误的。

在派生类中,通过包括使用 override 修饰符的属性声明可以重写抽象的继承属性。

抽象类必须为所有接口成员提供实现。

实现接口的抽象类可以将接口方法映射到抽象方法上。

例如:

interface I

{

void M();

}

abstract class C: I

{

public abstract void M();

}

它们之间的区别: 

1.类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类

接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”

抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中

2.接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;

3.一个类一次可以实现若干个接口,但是只能扩展一个父类

4.接口可以用于支持回调,而继承并不具备这个特点.

5.抽象类不能被密封。

6.抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非 虚的,当然您也可以声明为虚的。

7.(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。

8抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。

9 好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。

10 尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.

11 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法

如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单的方法来控制组件版本。如果创建的功能将在大范围的全异对象间使用,则使用接口。如果要设计小而简练的功能块,则使用接口。如果要设计大的功能单元,则使用抽象类。

如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类。

这个只是我学习C#一月以来自己的一点学习心得,希望与csdn上各位朋友多指教!

c#中的抽象类和接口

在本文中,我宁可相信二者是同时出现的,是设计者在高级程序语言设计的初始阶段就设计出来的两个巧妙的功能(我这样说,当然还包括了这种想法:设计者也考虑到了二者的区别),而不是设计者在时间进程中逐个加进来的一个个功能块(是同时出现的,还是逐个出现,目前对于我确是未知)。

c#中可以多继承接口,却只能继承自一个抽象类;

抽象类中的方法可以包含一些实现,但接口却只能定义方法的一种规范,不能包含实现;

继承类必须实现借口中所有的方法,而不一定要实现抽象类中的所有方法;

抽象类可以继承自抽象类 ,但接口不能继承自接口;

派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员);另外,接口有如下特性:

接口除了可以包含方法之外,还可以包含属性、索引器、事件,而且这些成员都被定义为公有的。除此之外,不能包含任何其他的成员,例如:常量、域、构造函数、析构函数、静态成员。

二者均不能实例化。关于二者的使用情况的理解,下面这段话是最好的解释:

抽象类用于部分实现一个类,再由用户按需求对其进行不同的扩展和完善;接口只是定义一个行为的规范或规定。

抽象类在组件的所有实现间提供通用的已实现功能;接口创建在大范围全异对象间使用的功能。

抽象类主要用于关系密切的对象;而接口适合为不相关的类提供通用功能。

抽象类主要用于设计大的功能单元;而接口用于设计小而简练的功能块。

例如:

Window窗体可以用抽象类来设计,可以把公有操作和属性放到一个抽象类里,让窗体和对话框继承自这个抽象类,再根据自己的需求进行扩展和完善。

打印操作可以作为一个接口提供给每个需要此功能的窗体,因为窗体的内容不同,就要根据他们自己的要求去实现自己的打印功能。打印时只通过接口来调用,而不用在乎是那个窗体要打印。

抽象类:

在定义类的前面加上关键字abstract,那么这个类就是抽象类了,抽象类本身无法产生实例对象,而且抽象类包含了一个以上的抽象方法,这些方法只是提供函数名称,并没有定义如何具体实现,由继承的派生类实现,派生类同时必须实现所有抽象类的方法,否则其本身将成为另外一个抽象类。需要我们注意的一点是,当派生类重写抽象类的方法时,要使用override关键字来重写抽象类所定义的方法。

1、声明一个抽象方法使用abstract关键字。

2、一个类中可以包含一个或多个抽象方法。

3、抽象类中可以存在非抽象的方法。

4、抽象类不能被直接被实例化。

5、实现抽象类用“:”(冒号),实现抽象方法用override关键字。

6、抽象类可以被抽象类所继承,结果仍是抽象类。

7、抽象方法被实现后,不能更改修饰符。

C#中虛函數,抽象,接口的簡單説明

虛函數:由virtual聲明,它允許在派生類中被重寫,要重寫方法,必須先聲名為virtual

public class myclass

{

public virtual int myint()

{

函數体;

}

}

class myclass1:myclass

{

public override int myint()

{

函數体1

}

}

抽象類、抽象函數:由abstract聲明,在抽象類中可以定義抽象方法,抽象方法基本沒有執行代碼,派生類必須重寫它,提供其執行代碼

public abstract class myclass

{

public abstract int myint();

}

public class myclass1:myclass

{

public override int myint()

{

函數体;

}

}

接口類:由interface聲明,是特殊的抽象類,是方法、屬性、事件和索引符的組合,沒有字段,其成員無執行方式,無構造函數,不允許進行運算符重載,接口和它的成員沒有任何訪問修飾符,它總是公共的,不能聲明為虛擬或靜態,繼承自接口的派生類必須實現接口中的所有方法

interface Imyinterface

{

void myfunction();

string name

{

get;

set;

}

}

class myclass:Imyinterface

{

void myfunction()

{

函數体;

}

string name

{

get

{

return name;

}

set

{

name=value;

}

}

}

.类与结构的差别

  1.值类型与引用类型

  结构是值类型:值类型在堆栈上分配地址,所有的基类型都是结构类型,例如:int 对应System.int32 结构,string 对应 system.string 结构 ,通过使用结构可以创建更多的值类型

  类是引用类型:引用类型在堆上分配地址

堆栈的执行效率要比堆的执行效率高,可是堆栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑

  因为结构是值类型所以结构之间的赋值可以创建新的结构,而类是引用类型,类之间的赋值只是复制引用

  注:

  1.虽然结构与类的类型不一样,可是他们的基类型都是对象(object,C#中所有类型的基类型都是object

  2.虽然结构的初始化也使用了New 操作符可是结构对象依然分配在堆栈上而不是堆上,如果不使用“新建”(new),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用

  2.继承性

  结构:不能从另外一个结构或者类继承,本身也不能被继承,虽然结构没有明确的用sealed声明,可是结构是隐式的sealed .

  类:完全可扩展的,除非显示的声明sealed 否则类可以继承其他类和接口,自身也能被继承

  注:虽然结构不能被继承 可是结构能够继承接口,方法和类继承接口一样

  例如:结构实现接口

  interface IImage

  {

  void Paint();

  }

  struct Picture : IImage

  {

  public void Paint()

  {

  // painting code goes here

  }

  private int x, y, z; // other struct members

  }

  3.内部结构:

结构:

  没有默认的构造函数,但是可以添加构造函数

  没有析构函数

  没有 abstract sealed(因为不能继承)

  不能有protected 修饰符

  可以不使用new 初始化

在结构中初始化实例字段是错误的

  类:

有默认的构造函数

有析构函数

  可以使用 abstract sealed

  有protected 修饰符

必须使用new 初始化

  三.如何选择结构还是类

  讨论了结构与类的相同之处和差别之后,下面讨论如何选择使用结构还是类:

  1.堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些

  2.结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。

  3.在表现抽象和多级别的对象层次时,类是最好的选择

  4.大多数情况下该类型只是一些数据时,结构时最佳的选择

C++的类与C#的类

词:C#

阅读提示:微软公司给C#(读为C-Sharp)赋予C++某些面向对象的本质,比如模板,但改变了类的创建方法。本文,我将对比C++C#的类,并着重说明微软在C#中类创建和使用方面的改变。

一、简介

面向对象(OO)编程在应用设计中已经发展二十来年了。程序不再是一系列函数的堆彻(象一些范例那样的程序),而是对象的集合,每个对象都有其独特的属性和方法来与其它对象打交道。

"C"语言系列是面向对象设计发展的最好例子。C++为开发者提供了优秀的面向对象编程工具,程序员可以显式地创建构造函数,拷贝构造函数,重载操作符,使用模板等等。

C++这样复杂语言的主要问题是程序员要花上好几个月来掌握面向对象设计本质。新程序员必须学会掌握模板,函数重载,当然还要会创建和使用功能良好的类。

微软公司给C#(读为C-Sharp)赋予C++某些面向对象的本质,比如模板,但改变了类的创建方法。本文,我将对比C++C#的类,并着重说明微软在C#中类创建和使用方面的改变。

本文对象是熟练的C++程序员,通过一些细节来解释C#面向对象的本质。

二、C#的类有了哪些改变?

如你所知,C#是部分基于C++,部分基于Java语法的语言。C#中还有一些细节上的改变,使得它可以用于现代设计。当你开始用C#建类时就会立即看到这点。让我们通过一个简单的例子开始了解在C++C#中是如何建类并进行实例化的。

C++版本:

#include

class MyClass

{

 public: void doSomething()

 { std::cout << "This is some text";

 }

};

void main()

{ MyClass mc;

 mc.doSomething();

}

C# 版本:

using System;

class MyClass

{

public void doSomething()

{

Console.WriteLine("This is some text");

}

}

class EntryPoint

{ public static void Main()

{ MyClass mc = new MyClass();

mc.doSomething();

}

}

上面的代码中有几个不同之处。

首先,C++#include包含语句来指明包含文件iostream.h的物理路径。C#则告诉编译器程序将在System命名空间下操作,所有的命名空间和类都属于System命名空间。C#通过命名空间的名字来决定程序的作用范围(本例中只有System一个命名空间),而不用指明物理路径的包含文件方法。

其次,C#的主程序用Main(注意M是大写)

第三,C++的类声明结束后要在最后的大括号后面用分号结尾。C#则可用可不用,往往都是省略。

第四,你能看到在C#中必须显式地声明方法和成员的作用域。若不加声明,缺省为私有(只有类成员可以访问),这点与C++一样。C#中有5种作用域:

 公有(public):其他类成员也可以访问

 私有(private):只有类成员才能访问

 保护(protected):类成员和继承类成员可以访问

 内部(internal):只有汇编里的成员才能访问(C#的汇编是代码和资源数据的结合,以asmx作文件后缀)

 内部保护(protected internal):类成员和继承类成员可以访问

最后,与Java一样,C#的方法也可以声明为静态(static)的。静态变量的使用在C#C++是一样的。在C#里,可以这样创建并调用类的静态方法:

using System;

class MyClass

{ public static void doSomething()

 {Console.WriteLine("This is some text");

 }

};

class EntryPoint

{

 public static void Main()

 {

MyClass.doSomething();

 } }

注意,这里直接使用类声明而没有创建类的实例。这是为C#语言增加的非常便利的使用方法,可以节省我们的时间和内存。就是说,不要创建类实例,可以直接调用类的方法。

三、用类修饰语限制对类的访问

以前只能对类成员和类方法设定限制,但不能对类实体作限制。C#可以通过声明类修饰语来对类的实例实行限制,如上节提到的作用域。

C++不能对整个类作限制。看一下C++的类声明:

class Car

{

 public:

Car();

Car(Car &c);

virtual ~Car();

 private:

int numCars;

Car* previous;

Car* next;

};

这里有二种访问类型:公有(public)和私有(private)。继承或将类Car实例化后,程序只能继承这些代码,不能作其它变动,如果要作其它变动就不能将其作为基类。

C#对此了改变。可以附加访问修饰语来限制类成员和方法以及类实例的访问权。C#设定8个访问权限:

 公有(public):可以被所有其它的类访问。没有其它限制修饰语,它的公有性质就一直是缺省的。

 私有(private):只有类成员才能访问。

 保护(protected):类成员和继承类成员可以访问。

 内部(internal):只有汇编里的成员才能访问(C#的汇编是代码和资源数据的结合,以asmx作文件后缀)

 内部保护(protected internal):类成员和继承类成员可以访问。

 密封(sealed):所有继承类都不能访问。无论直接或间接地将它作为基类,C#编译器都会跳错。

 抽象(abstract):与C++的虚(virtual)类或虚方法相似,抽象类不能直接实例化,抽象函数含有函数名。但在作为基类或继承类时可以使用。

 新建(new):用new创建嵌套类,可以隐藏继承方式,告诉编译器创建一个类的新版本。

举例来说,如果要创建一个类,并且这个类不能被作为基类或继承,那么就要创建一个密封类:

sealed class Car

{

 public void paintCar()

 {

// Code to paint car goes here

 }

}

这时如果要创建一个继承类RedCar

internal class RedCar : Car

{

 // Won't work.

}

C#编译器就会跳错:

error CS0509: 'RedCar' : cannot inherit from sealed class 'Car' (不能从密封类'Car'继承)

四、C++C#中的虚函数

C++C#都支持虚函数。在下面的C++例子里,有二个类,分别称为Base(基类)Derived(继承类)

#include

using namespace std;

class Base

{

 public:

void doWork()

{

 cout << "Base class working";

}

 protected:

virtual void doWork1() = 0;

};

class Derived : public Base

{

 public:

void doWork2()

{

 cout << "Derived class working";

}

void doWork1()

{

 cout << "Dervied pure virtual function working";

}

};

void main()

{

 Derived d;

 d.doWork1();

}

基类里有二个函数,一个是doWork,另一个是纯虚函数doWork1doWork1只能被基类的继承类使用。在继承类(公有地继承于基类)里有一个新函数doWork2,和继承于基类纯虚函数的超越函数doWork1

C#里实现同样的功能要更容易些。看下面的C#代码:

using System;

abstract class Base

{  public void doWork()

 { Console.WriteLine("Base class working");

 }

 public abstract void doWork1();

}

class Derived : Base

{ public void doWork2()

 { Console.WriteLine("Derived class working");

 }

 public override void doWork1()

 { Console.WriteLine("Dervied pure virtual function working");

 }

};

class EntryPoint

{

 public static void Main()

 {

Derived d = new Derived();

d.doWork1();

 }

}

C#将基类定义为抽象类,将doWork1定义为抽象方法。这样就可以获得与上述C++纯虚函数同样的功能。Base类只能作为基类或被含有doWork1超越函数的继承类使用。

继承类在超越抽象函数doWork1时,在函数前面加上超越前缀(override)C#编译器在发现继承类里的override关键字后,就检查基类的同名函数。只要不是直接显式地调用基类函数,编译器总是使用继承类中的方法。

为了让继承类直接操作基类成员和方法,C# 为基类命名了一个别名base。用这个别名,继承类可以直接引用基类成员和方法。示例如下:

using System;

class first {

 public void writeIt()

 { Console.WriteLine("Writing from base class");

 }

}

class second : first

{

 public second()

 {

base.writeIt();

 }

}

class EntryPoint

{

 public static void Main()

 { second s = new second();

 }

}

在上述例子中,有二个类。一个是基类(first),另一个是继承类(second)。当创建second类的实例时,它的构造函数自动调用基类的writeIt方法,用控制台指令Console.WriteLine打印屏幕。由此引出C++C#中的多态性。

五、C++C#中的多态性实体的多态性使其具有多种表现形式。在C++C#中处理多态性是很相像的。看一个简单例子:

C++ 版本:

#include

#include

using namespace std;

class Person

{

 public:

Person()

{

 classType = "Person";

}

 friend void ShowType(Person& p);

 private:

string classType;

};

class Manager : public Person

{ public:

Manager()

{classType = "Manager";

}

 friend void ShowType(Person& p);

 private:

string classType;

};

void ShowType(Person& p)

{  cout << p.classType << endl;

} void main()

{ Person p;

 Manager m;

 ShowType(p);

 ShowType(m); }

C# 版本:

using System;

class Person {

 public Person()

 { classType = "Person";

 }

 public string classType;

}

class Manager : Person

{ public Manager()

 {

classType = "Manager";

 }

 public new string classType;

}

class EntryPoint

{ public static void ShowType(ref Person p)

 { Console.WriteLine(p.classType);

 }

 public static void Main()

 {

Person p = new Person();

Person m = new Manager();

 ShowType(ref p);

ShowType(ref m);

 }

}

在上面的例子里,有一个基类Person,一个继承于基类的Manager类。在EntryPoint类里,创建了一个静态函数ShowType,其表达为:

public static void ShowType(ref Person p)

注意参数里的ref关键字。ref告诉C#编译器向ShowType函数传递一个参数的引用(reference)。在C#中,如果不用ref关键字,函数的参数传递缺省为值(value)传递,将拷贝一个参数值传递到函数中去。

C++中的参数引用传递表达为: void ShowType(Person& p)

C++"&"符号表示参数引用使得程序员新手感到困惑,尤其是那些从VB转过来的人。

C#的主函数(entry point)里,创建了二个新的Person对象,pm

Person p = new Person();

Person m = new Manager();

值得一提是,关键字newC#C++中用法是不一样的。在C#里,new只创建一个对象实例。这个对象依然创建在管理堆里,但不返回指向对象的内存地址的指针。在上面的C#例子中,创建了二个Person类对象。第二个对象,m,却是Manager类的对象。它使用Manager的构造函数而不是用Person的构造函数。

Person类对象和Manager类对象引用到ShowType函数,记住,ManagerPerson的继承类,但C#的多态性将其表达为一个Person类:

ShowType(ref p);

ShowType(ref m);

当作用到ShowType函数时,它只处理Person对象。C#告诉它说mPerson继承类的对象,它就将mPerson类处理了。所以用pm作参数调用ShowType函数后得到的输出为:

Person

Person

[译者注:这样解释多态性有点离谱。这段 C#代码的真正含义在于诠释静态函数的作用,而不是什么多态性。上面一段C++代码则可以看成多态性用法的解释。]

六、结论

在我熟悉C#之前,我用了4VB2C++。我可以负责地说,C#是我用过的语言中最具活力和灵活性并使人愉快的语言,而且它是100%面向对象的。如果你是一个C++程序员,现在想转向电子商务编程或干脆就是想换一种更现代的语言,那么就是C#了。有这么三种原因:

如果会使用C#,你就能创建任何应用:Windows应用,控制台应用,Web应用和Web服务等等。

所有的.NET平台使用的语言都编译成为中间语言(IL),并能按照系统环境进行优化。

非常非常容易将C++转换成C#

C++ C# Java 关于C#,C++,Java在继承,覆盖和多态,抽象类等几个方面的比较归纳。

C#,C++visual studio2005编译通过;java代码用JDK<chsdate w:st="on" isrocdate="False" islunardate="False" day="30" month="12" year="1899">1.4.2</chsdate>编译通过。

一、继承中的带参数构造函数

=============================

C#示例:

//myClass.cs

using System;

using System.Collections.Generic;

using System.Text;

namespace myClass

class myFirst{

int value_myFirst;

public myFirst(int f)

{

value_myFirst = f;

}

}

class mySecond : myFirst{

int value_mySecond;

//构造函数传递参数时,采用base关键字,sbase()中不需重新声明类型int

public mySecond(int s)

: base(s)

{

value_mySecond = s;

}

}

class Program

{

static void Main(string[] args)

{

}

}

}

============================

C++示例:

#include "stdafx.h"

class myFirst{

private:

int value_myFirst;

public:

myFirst(int f){

value_myFirst = f;

}

};

//继承需要声明继承的方式,此处是public

class mySecond : public myFirst{

private:

int value_mySecond;

public:

//构造函数传递参数时,用基类类名,s基类类名()中不需声明类型int

mySecond(int s) : myFirst(s){

value_mySecond = s;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

return 0;

}

=============================

java示例:

package com;

class myFirst{

int value_myFirst;

public myFirst(int f){

value_myFirst = f;

}

}

//继承采用extends关键字

class mySecond extends myFirst{

int value_mySecond;

public mySecond(int s){

//传递给基类构造函数时,采用super关键字,而且必须是第一条语句。

super(s);

value_mySecond = s;

}

}

public class myCon{

public static void main(String[] args){

}

}

注意:

1.注释中给出了三者的不同点。

2.另外,C++语法中定义的类后面必须加上分号";"

3.访问控制权限public等的格式,C#java比较类似,C++相差很大。

二、方法覆盖与多态

C#示例:

//myClass.cs

using System;

using System.Collections.Generic;

using System.Text;

namespace myClass

{

class myFirst

{

int value_myFirst;

public myFirst(int f)

{

value_myFirst = f;

}

public void f1()

{

System.Console.WriteLine("myFirst.f1()!");

}

public virtual void f2() //virtual也可以提到最前面

{

System.Console.WriteLine("myFirst.f2()!");

}

}

class mySecond : myFirst

{

int value_mySecond;

public mySecond(int s)

: base(s)

{

value_mySecond = s;

}

//使用关键字new覆盖基类中的同名方法

public new void f1() //new也可以提到最前面

{

System.Console.WriteLine("mySeconde.f1()!");

}

//error当基类函数myFirst.f1()没有声明为virtual,abstract时不能override

//public override void f1()

//{

// System.Console.WriteLine("mySeconde.f1()!");

//}

//基类函数中虽然声明是virtual,但是仍然可以用new覆盖。

//public new void f2()

//{

// System.Console.WriteLine("mySeconde.f2()!");

//}

////基类函数中虽然声明是virtual,用override覆盖。

public override void f2() //override也可以提到最前面

{

System.Console.WriteLine("mySeconde.f2()!");

}

}

class Program

{

static void Main(string[] args)

{

myFirst mf = new myFirst(1);

mySecond ms = new mySecond(2);

mf.f1(); //myFirst.f1()!

mf.f2(); //myFirst.f2()!

ms.f1(); //mySeconde.f1()!

ms.f2(); //mySeconde.f2()!

mf = ms; //向上转型之后

mf.f1(); //myFirst.f1()!

//mySeconde.f2()! 这是用override的运行结果;

//如果是new那么,结果是myFirst.f2()!

mf.f2();

}

}

}

=============================

C++示例

#include "stdafx.h"

#include <iostream>

using namespace std;

class myFirst{

private:

int value_myFirst;

public:

myFirst(int f){

value_myFirst = f;

}

void f1(){

cout<<"myFirst.f1()!"<<endl;

}

vitual void f2(){ //声明为虚函数

cout<<"myFirst.f2()!"<<endl;

}

};

class mySecond : public myFirst{

private:

int value_mySecond;

public:

mySecond(int s) : myFirst(s){

value_mySecond = s;

}

//直接覆盖基类函数,无需C#中的new

void f1(){

cout<<"mySecond.f1()!"<<endl;

}

//覆盖基类需函数

void f2(){

cout<<"mySecond.f2()!"<<endl;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

myFirst *mf = new myFirst(1);

mySecond *ms = new mySecond(1);

mf->f1(); //myFirst.f1()!

mf->f2(); //myFirst.f2()!

ms->f1(); //mySecond.f1()!

ms->f2(); //mySecond.f2()!

mf = ms; //向上转型

mf->f1(); //myFirst.f1()!

mf->f2(); //mySecond.f2()!

myFirst mf1(1); //也可以

mf1.f1();

return 0;

}

=============================

java示例

//myCon.java

package com;

class myFirst{

int value_myFirst;

public myFirst(int f){

value_myFirst = f;

}

public void f1(){

System.out.println("myFirst.f1()!");

}

}

class mySecond extends myFirst{

int value_mySecond;

public mySecond(int s){

super(s);

value_mySecond = s;

}

public void f1(){

System.out.println("mySecond.f1()!");

}

}

class myThird extends myFirst{

int value_myThird;

public myThird(int t){

super(t);

value_myThird = t;

}

public void f1(){

System.out.println("myThird.f1()!");

}

}

public class myCon{

public static void main(String[] args){

myFirst mf = new myFirst(1);

mySecond ms = new mySecond(1);

myThird mt = new myThird(1);

mf.f1(); //myFirst.f1()!

ms.f1(); //mySecond.f1()!

mt.f1(); //myThird.f1()!

//向上转型,由于java的动态绑定机制,

//使得java能够调用派生类mySecondf1()

mf = ms;

mf.f1(); //mySecond.f1()!

mf = mt;

mf.f1(); //myThird.f1()!

}

}

为了实现多态:

1.C#基类方法要声明为virtual,派生类覆盖时要用override

2.C++基类方法要声明为virtual,派生类方法直接覆盖;

3.java直接覆盖就可以实现多态。

三、抽象类

C#示例

上面已经说明,虽然基类方法声明为virtual,以便派生类用override覆盖,但是派生类仍然可以用

new关键字覆盖(不具有多态性)

可以强制让派生类覆盖基类的方法,将基类方法声明为抽象的,采用abstract关键字。

抽象方法没有方法体,由派生类来提供。

如果派生类不实现基类的抽象方法,则派生类也需要声明为abstract

//myClass.cs

using System;

using System.Collections.Generic;

using System.Text;

namespace myClass{

//类中只要存在抽象方法,就必须声明为抽象类

abstract class myFirst

{

int value_myFirst;

public myFirst(int f)

{

value_myFirst = f;

}

//抽象方法没有方法体,以分号结尾。

public abstract void f1();

public void f2()

{

System.Console.WriteLine("myFirst.f2()!");

}

public virtual void f3()

{

System.Console.WriteLine("myFirst.f3()!");

}

}

class mySecond : myFirst

{

int value_mySecond;

public mySecond(int s)

: base(s)

{

value_mySecond = s;

}

//覆盖基类抽象方法

public override void f1()

{

System.Console.WriteLine("mySeconde.f1()!");

}

//覆盖基类一般方法

public new void f2()

{

System.Console.WriteLine("mySeconde.f2()!");

}

//覆盖基类虚拟方法

public override void f3()

{

System.Console.WriteLine("mySecond.f3()!");

}

}

class Program

{

static void Main(string[] args)

{

//抽象类和接口不能声明对象

//myFirst mf = new myFirst(1);

mySecond ms = new mySecond(2);

ms.f1(); //mySeconde.f1()!

ms.f2(); //mySeconde.f2()!

ms.f3(); //mySecond.f3()!

//这里向上转型采用强类型转换的方式

((myFirst)ms).f1(); //mySeconde.f1()!

((myFirst)ms).f2(); //myFirst.f2()!

((myFirst)ms).f3(); //mySecond.f3()!

}

}

}

=============================

C++示例

纯虚函数是在基类中只宣布某个虚函数的原型,并且为了明确通知编译系统,

该虚函数在基类中不再定义具体操作代码,而在函数原型结束分号的左侧写

"=0"标识。这个不包含任何代码的虚函数被成为纯虚函数。

抽象类是含有纯虚函数的类,这种类不能声明任何对象,其作用就是为它的

派生类提供一种规定输入数据和返回类型接口的模板。

从抽象类派生的派生类,必须对基类的纯虚函数进行覆盖;否则编译系统将

报错。

基类中虚函数被派生类覆盖,则派生类对象调用的是派生类中重新定义的函

数代码。

基类中虚函数没有被派生类覆盖,则派生类对象调用的是基类中定义的函数

代码。

基类的纯虚函数在其派生类中必须被覆盖。

#include "stdafx.h"

#include <iostream>

using namespace std;

class myFirst{ //抽象类

private:

int value_myFirst;

public:

myFirst(int f){

value_myFirst = f;

}

void f1(){ //一般函数

cout<<"myFirst.f1()!"<<endl;

}

virtual void f2(){ //虚函数

cout<<"myFirst.f2()!"<<endl;

}

virtual void f3()=0; //纯虚函数

};

class mySecond : public myFirst{

private:

int value_mySecond;

public:

mySecond(int s) : myFirst(s){

value_mySecond = s;

}

void f1(){ //覆盖基类一般函数

cout<<"mySecond.f1()!"<<endl;

}

void f2(){ //覆盖基类虚函数

cout<<"mySecond.f2()!"<<endl;

}

void f3(){ //覆盖基类纯虚函数

cout<<"mySecond.f3()!"<<endl;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

//myFirst *mf = new myFirst(1); //抽象类不能创建对象

mySecond *ms = new mySecond(1);

ms->f1(); //mySecond.f1()!

ms->f2(); //mySecond.f2()!

ms->f3(); //mySecond.f3()!

//向上转型采用强类型转换

((myFirst *)ms)->f1(); //myFirst.f1()!

((myFirst *)ms)->f2(); //mySecond.f2()!

((myFirst *)ms)->f3(); //mySecond.f3()!

return 0;

}

=============================

java示例

java提供了抽象方法的机制abstract method,这种方法不完整,仅有声明

没有方法体

abstract void f();

包含抽象方法的类叫抽象类,如果类中包含一个或多个抽象方法,则该类必

须被声明为抽象类,用abstract来修饰抽象类。

如果从一个抽象类继承,并创建这个新类的对象,必须给抽象基类中所有抽

象方法提供方法定义,否则,派生类也是抽象类,也用abstract修饰。

package com;

abstract class myFirst{

int value_myFirst;

public myFirst(int f){

value_myFirst = f;

}

public void f1(){

System.out.println("myFirst.f1()!");

}

public abstract void f2(); //抽象方法

}

//继承采用extends关键字

class mySecond extends myFirst{

int value_mySecond;

public mySecond(int s){

//传递给基类构造函数时,采用super关键字,而且必须是第一条语句。

super(s);

value_mySecond = s;

}

public void f1(){

System.out.println("mySecond.f1()!");

}

public void f2(){ //覆盖基类抽象方法

System.out.println("mySecond.f2()!");

}

}

//如果不实现基类抽象方法,那么此派生类也必须用abstract修饰

abstract class myThird extends myFirst{

int value_myThird;

public myThird(int t){

super(t);

value_myThird = t;

}

public void f1(){

System.out.println("myThird.f1()!");

}

}

public class myCon{

public static void main(String[] args){

//myFirst mf = new myFirst(1); //抽象函数不能创建对象

mySecond ms = new mySecond(1);

//myThird mt = new myThird(1);

ms.f1(); //mySecond.f1()!

ms.f2(); //mySecond.f2()!

((myFirst)ms).f1(); //mySecond.f1()!

((myFirst)ms).f2(); //mySecond.f2()!

}

}

关于抽象方法和抽象类javaC#比较类似;

C++叫纯虚函数和抽象类。

四、接口

C++中的没有接口的概念,它本身就可以多继承。

java中接口interface比抽象类abstract更进了一步,可看做"纯粹的抽象类"

它允许创建者为一个类建立其形式,方法名,参数列表,返回类型,但没有

任何方法体。接口中也可以包含成员函数,但是他们都是隐含的public

final

创建接口时用interface来代替class,前面可以有public,如果不加访问权

限,那么它就是默认的包访问权限。

接口中的方法默认为public

类实现接口要用implements关键字。

接口便于实现多重继承的效果,此处不作具体讨论。

package com;

interface myFirst{

int value_myFirst=1;

public void f1();

}

class mySecond implements myFirst{

int value_mySecond;

public mySecond(int s){

value_mySecond = s;

}

public void f1(){

System.out.println("mySecond.f1()!");

}

}

public class myCon{

static void play(myFirst mf){

mf.f1();

}

public static void main(String[] args){

//myFirst mf = new myFirst(1);

mySecond ms = new mySecond(1);

ms.f1(); //mySecond.f1()!

play(ms); //向上转型 mySecond.f1()!

((myFirst)ms).f1(); ////向上转型 mySecond.f1()!

}

}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics