资源描述
论述C#与C++在继承性和多态性上的异同
C++语言是一种同时支持面向过程程序设计和面向对象程序设计的混合型高级程序设计语言。C++支持封装性、继承性和多态性。C#是从C和C++语言演化而来的,它在语句、表达式和运算符方面沿用了许多C++的功能,同时也有着相当大的改进和创新。为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或自己以前的开发成果,同时又希望在自己的开发过程中能够有足够的灵活性,不拘泥于复用的模块。C#这种完全面向对象的程序设计语言提供了两个重要的特性--继承性和多态性。
一、继承性:
继承是面向对象程序设计的主要特征之一,它可以让您重用代码,可以节省程序设计的时间。继承就是在类之间建立一种相交关系,使得新定义的派生类的实例可以继承已有的基类的特征和能力,而且可以加入新的特性或者是修改已有的特性建立起类的新层次。
我们从C#的派生和C++的派生类的定义格式和例子来分析。
(1)C#:
[访问修饰符] class 类名[:基类名]
{
类的成员;
};
例子:
using System;
public class Person //这是一个基类
{
//定义数据成员
public string Name; //姓名
public char Sex; //性别
//定义构造函数,以初始化字段
public Person(string name, char sex)
{
Name = name;
Sex = sex;
}
//定义方法成员
public string Answer()
{
return string.Format("姓名:{0},性别:{1}。", Name, Sex);
}
}
public class Student : Person //这是一个派生类
{
//扩展数据成员
public string School; //学校
private int Score; //成绩
//定义构造函数,自动调用基类的构造函数辅助完成字段的初始化
public Student(string name, char sex, string school, int score)
: base(name, sex)
{
School = school;
Score = score;
}
//扩展方法成员
public float Examine() //返回考试成绩
{
return Score;
}
}
class TestClass
{
static void Main()
{
//创建Student对象
Student s = new Student("张伟", '男', "电子科大成都学院", 480);
Console.WriteLine("该生信息如下:");
Console.WriteLine(s.Answer()); //调用对象继承来的方法
Console.WriteLine("学校:{0},考试成绩:{1}", s.School, s.Examine());
}
}
(2)C++:
Class <派生类名>:[继承方式]<基类名1>[,[继承方式]<基类名2>,````[继承方式]<基类名3>]
{
<派生类新增的数据成员和成员函数定义>
};
例子:
typedef char string80[80];
class Date
{
public:
Date() {}
Date(int y, int m, int d) { SetDate(y, m, d); }
void SetDate(int y, int m, int d)
{
Year = y;
Month = m;
Day = d;
}
void GetStringDate(string80 &Date)
{
sprintf(Date, "%d/%d/%d", Year, Month, Day);
}
protected:
int Year, Month, Day;
};
class Time
{
public:
Time() {}
Time(int h, int m, int s) { SetTime(h, m, s); }
void SetTime(int h, int m, int s)
{
Hours = h;
Minutes = m;
Seconds = s;
}
void GetStringTime(string80 &Time)
{
sprintf(Time, "%d:%d:%d", Hours, Minutes, Seconds);
}
protected:
int Hours, Minutes, Seconds;
};
class TimeDate:public Date, public Time
{
public:
TimeDate():Date() {}
TimeDate(int y, int mo, int d, int h, int mi, int s):Date(y, mo, d), Time(h, mi, s) {}
void GetStringDT(string80 &DTstr)
{
sprintf(DTstr, "%d/%d/%d;%d:%d:%d", Year, Month, Day, Hours, Minutes, Seconds);
}
};
void main()
{
TimeDate date1, date2(1998, 8, 12, 12, 45, 10);
string80 DemoStr;
date1.SetDate(1998, 8, 7);
date1.SetTime(10, 30, 45);
date1.GetStringDT(DemoStr);
cout<<"The date1 date and time is:"< date1.GetStringDate(DemoStr);
cout<<"The date1 date is:"< date1.GetStringTime(DemoStr);
cout<<"The date1 time is:"< date2.GetStringDT(DemoStr);
cout<<"The date2 date and time is:"< }
首先,我们比较C++源代码中的第C行和C#源代码中的第C行。我们可以看到在C++中的继承是分为: 公有继承,私有继承,保护继承。而在C#中只有默认的公有继承。这样简化了继承的方式,至于限制派生类访问基类的数据以及方法成员,将由基类的数据以及方法成员的访问控制权限决定。
(1) C#中,派生类只能从一个类中继承。这是因为,在C++中,人们在大多数情况下不需要一个从多个类中派生的类。从多个基类中派生一个类,往往会带来许多问题,从而抵消了这种灵活性带来的优势。C#中的继承符合下列规则:继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object 类作为所有类的基类。
(2) 派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。构造函数和析构函数不能被继承。除此以外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
(3) 派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。类可以定义虚方法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而使得类可以展示出多态性。
(4) C#中,派生类只能从一个类中继承,但可以通过接口实现多重继承。
在C#中可以通过base关键字来访问基类成员,具体的访问规则如下:
(1) 调用基类上已被其他方法重写的方法。
(2) 指定创建派生类实例时应调用的基类构造函数。
(3) 基类访问只能在构造函数、实例方法或实例属性访问器中进行。
(4) 从静态方法中使用 base 关键字是错误的。
我们能看出来两者不同之处,(1)C#中,派生类只能从一个类中继承,但C++中有单一继承,多重继承和多极继承。(2)C#的默认继承方式是public,而C++的默认继承方式是private。C++可以从多个基类中继承下来,每个基类都要设定继承方式。
两者的相同之处(1)派生类从它们的直接基类中继承成员:方法、域、属性、事件、索引指示器。(2)私有成员,构造函数和析构函数都不能被继承。(3)派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。(4)运行时,派生类对构造函数和析构函数的顺序一样。(3)派生类中的构造函数都用base来引用基函数构造函数来给基函数的成员赋初值。
C#中在属性的重载中,提出了密封属性。用sealed 修饰符声明的属性为密封属性。类的密封属性不允许在派生类中被继承。密封属性的访问器同样也是密封的。C#和C++在访问派生类与基类同名成员时,有所不同,因为C#每一个派生类只有一个基类,访问基类成员时通过base 关键字访问。C++的继承有多种方式,可能有一个基类,也可能有几个基类,所以访问基类的成员时,通过:基类名::成员名,来达到访问目的。
二、多态性:
在面向对象的系统中,多态性是一个非常重要的概念,它允许客户对一个对象进行操作,由对象来完成一系列的动作,具体实现哪个动作,如何实现由系统负责解释。
多态性的定义是:同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。C#和C++均支持两种类型的多态性:编译时多态性和运行时多态性。前者是在编译的过程中确定了同名操作的具体操作对象,而后者则是程序运行过程中动态地确定操作所针对的具体对象。这种确定具体对象的过程就是联编。联编是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程:用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。按照联编进行的阶段的不同,可以分为两种不同的联编方法:静态联编和动态联编,这两种联编过程分别对应着多态的两种实现方式。
(一)分析C++与C#中重载多态性:
在C++和C#编译器都有自己的一些算法用于重新解析。为了进行重新解析,函数以它的签名来表示,签名就是函数名加上它的参数列表(参数的数量以及每个参数的类型)。但两者的同之处在于: C++中是允许把高级转化为低级,例如long转换为int 类型进行函数调用的。但是在C#中是不允许把double转换为int类型的。即C#中不允许进行收缩转换,因为有可能会使内存产生溢出。如果非要转换的话,就必须使用类型强制转换符。
(二)分析C++与C#中运算符重载多态性:
运算符重载是对已经有的运算符赋予多重含义,使同一个运算符作用于不同的类型的数据导致不同类型的行为。运算符重载实质就是函数重载。在实现过程中,首先把指定的运算表达式转换为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数,这个过程是在编译过程中完成的。
C++的运算符重载的规则如下:
(1)C++中运算符除了类属关系运算符“.”、成员指针运算符“*”、作用域分辨符号“::”、sizeof运算符和三目运算符“?”不能被重载,其他的都可以重载,但只能重载C++中已有的运算符。
(2)重载之后运算符的优先级和结合性都不会改变。
(3)运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有的功能相类似,不能改变原运算符的操作对象个数,同时至少要有一个操作对象是自定义类型。
C#中的操作符重载的作用与C++是一致的,但是C#中的操作符重载也有其自己的特点。如下所示:
(1)C#中,操作符重载总是在类中进行声明,并且通过调用类的成员方法来实现。操作符重载声明的格式为:type operator operator-name(formal-param-list)
(2) C#中不能被重载的操作符:=、&&、||、?:、new、typeof、sizeof、is其他的都可以重载,但只能重载C#中已有的运算符。
两者唯一不同之处在于C#中的运算符重载函数必须是静态的。
(三)C++与C#中强制多态性分析:
数据类型的转换就是,将存储在变量中的数值由一种类型转换成另外一种类型。在C++中,数据类型转换可以是隐式的,也可以是显式的:
float I = 100.1;
long j = I; //隐式转换
int k = (int)I; //显式转换
int m=int(I); //显式转换,新C++风格
C++中,有两种显式的数据类型转换格式——旧的C风格,其中数据类型名放在括号内。新风格,其中变量名放在括号内。在C#中,显式转换数据类型总是用旧的C风格语法,不能用C++新的风格。C#的类型安全性要比C++要高,所以它的数据间的转换不灵活。C#的显式转换和隐式转换都是正式的转换。定义为隐式的转换可以用显式或隐式两种转换来执行。而对定义为显式转换的数据转换使用隐式转换会出现错误。这点与C++不同,C++只会出现警告。
(四)C++与C#中包含多态性分析:
包含多态性是指通过继承提供多态性。包含多态性反映了能够在多于一个类的对象中完成同一事物的能力——用同一种方法在不同的类中处理不同的对象。包含多态性是运行时多态。
C#和C++都通过继承来为我们提供包含多态性。它们均提供了virtual关键字用于定义一个支持多态的方法。子类对于虚拟方法可以自由地提供它自己的实现,这称为重写。下面是一些关于C#虚拟方法的要点:
(1) 如果方法是非虚拟的,编译器简单地使用所引用的类型来调用适当的方法。
(2) 如果方法是虚拟的,编译器将产生代码来在运行时检查所引用的类,来从适当的类型中调用适当的方法。
(3) 当一个虚拟方法被调用时,运行时会进行检查(方法迟绑定)来确定对象和调用适当的方法,所有这些都是在运行时进行的。
对于非虚拟方法,这些信息在编译期间都是无效的,因此不会引起运行时检查,所以调用非虚拟方法的效率会略微高一些。但在很多时候虚拟方法的行为会更有用,损失的那些性能所换来的功能是很值得的。在C++和C#中包含多态性是很类似的,他们均是通过虚函数来实现的。通过下面的代码来分析它们间的异同。
C#中可以用两种方法来实现类成员的多态性:一是使用new关键字重新定义类的成员,看一例子:
Public class Pesrson
{
Public string id;
Public string Name;
Public string Answer()
{
Return string.Format(“工作证号:{0},姓名:{1}。”,id,Name);
}
}
Public class Student : Person
{
Public new string Answer()
{
Return string.Format(“学号:{0},姓名:{1}。”,id,Name);
}
}
C#和C++均提供了virtual关键字用于定义一个支持多态的方法。
我们直接看两个例子再来分析:
C#:
using System;
namespace duotai3
{
using System ;
public class DrawingBase
{
public virtual void Draw( )
{
Console.WriteLine("I‘m just a generic drawing object.") ;
}
}
public class Line : DrawingBase
{
public override void Draw( )
{
Console.WriteLine("I‘m a Line.") ;
}
}
public class Circle : DrawingBase
{
public override void Draw( )
{
Console.WriteLine("I‘m a Circle.") ;
}
}
public class Square : DrawingBase
{
public override void Draw( )
{
Console.WriteLine("I‘m a Square.") ;
}
}
public class DrawDemo
{
public static void Main(string[] args)
{
DrawingBase [] dObj = new DrawingBase [4];
dObj[0] = new Line( ) ;
dObj[1] = new Circle( ) ;
dObj[2] = new Square( ) ;
dObj[3] = new DrawingBase( ) ;
foreach (DrawingBase drawObj in dObj)
drawObj.Draw( ) ;
}
}
}
程序运行结果:
I‘m a Line !
I‘m a Circle !
I‘m a Square !
I‘m just a generic drawing object!
C++:
#include<iostream>
using namespace std;
class DrawingBase
{
public:
virtual void Draw()
{
cout<<"I‘m just a generic drawing object!"<<endl;
}
};
class Line:public DrawingBase
{
public :
virtual void Draw()
{
cout<<"I‘m a Line !"<<endl;
}
};
class Circle:public DrawingBase
{
public:
virtual void Draw()
{
cout<<"I‘m a Circle !"<<endl;
}
};
class Square:public DrawingBase
{
public:
void Draw() //(A)
{
cout<<"I‘m a Square !"<<endl;
}
};
void main()
{
DrawingBase *dObj[4];
dObj[0] = new Line( ) ;
dObj[1] = new Circle( ) ;
dObj[2] = new Square( ) ;
dObj[3] = new DrawingBase( ) ;
for(int i=0;i<4;i++)
dObj[i]->Draw();
system("pause");
}
程序运行结果:
I‘m a Line !
I‘m a Circle !
I‘m a Square !
I‘m just a generic drawing object!
我们能很清楚地从上面两个例子看出来在实现类的包含多态性用虚函数实现的时候,C++与C#有一个非常重要的区别:正如我前面所说,在C++中可以在派生类中省略掉virtual虚函数关键字,但是在C#中必须在派生类中虚拟函数使用override关键字,否则编译器不会把他当成虚拟函数,该派生类也不会具有多态性。
C#中字段不能是虚拟的,只有方法,属性,事件和索引器可以是虚拟的。C++静态成员函数,构造函数,内联函数都不能为虚,而析构函数往往被声明为虚函数。
展开阅读全文