虚函数、多态性和抽象类
多态性是指用一个名字定义不同的函数,这函数执行不同但又类似的操作,从而实现“一个接口,多种方法”。
多态性的实现与静态联编、动态联编有关。静态联编支持的多态性称为编译时的多态性,也称静态多态性,它是通过函数重载和运算符重载实现的。动态联编支持的多态性称为运行时的多态性,也称动态多态性,它是通过继承和虚函数实现的。
虚函数(virtual function)和多态性(Plymorphism)使得设计和实现易于扩展的系统成为可能。程序可以对层次中所有现有类的对象(基类对象)进行一般性处理。程序开发期间不存在的类可以用一般化程序稍作修改或不经修改即加进去,只要这些类属于一般处理的继承层次。程序中惟一要修改的部分是需要直接了解加进层次中的特定类的部分。
使用虚函数和多态性可简化源代码的长度。这种简化有助于程序的测试、调试和维护。
虚函数
假定一组形状类(如Circle、Triangle、Rectangle和Square等等)都是从基类Shape派生出来的。
在面向对象的程序设计中,我们可能要使每一个这样的类都能够计算自己的面积。尽管每个类都有它自己Area函数,但是计算每种形状的Area函数却是大不相同的。当需要计算面积时,不管它是什么形状,把它作为基类Shape的对象处理是再好不过的。然后,我们只需要简单地调用基类Shape的函数Area,并让程序动态地确定(即在执行时确定)使用哪个派生类的Area函数。
为了使这种行为可行,我们把基类中的函数Area声明为虚函数,然后在每个派生类中重新定义Area使之能够绘制合适的形状。虚函数的声明方法是在基类的函数原型前加上关键字virtual。
例如,基类Shape中可能出现:
virtual float Area() const; //虚函数,常量函数
上述原型声明函数Area是不取参数也返回float值的常量函数,而且是个虚函数。
注意:
为了指明某个成员函数具有多态性,用关键字virtual来标志其为虚函数。
如果虚函数在基类与子类中出现的仅仅是名字相同,而参数类型、个数不同或者返回类型不同,即使声明为virtual,也不会进行迟后联编。
一旦一个函数被声明为虚函数,即使重新定义类时没有声明虚函数,那么它从该点之后的继承层次结构中都是虚函数。
虽然函数在类层次结构的高层中声明为虚函数会使它在低层隐式地成为虚函数,但有些程序员为了提高程序的清晰性更喜欢在每一层中显式地声明这些虚函数。
没有定义虚函数的派生类简单地继承其直接基类的虚函数。
如果在基类中将函数Area声明为virtual,然后用基类指针或引用指明派生类对象并使用该指针调用Area函数(如pSharp->Area()),则程序会动态地(即在运行时)选择该派生类的Area函数,这称为动态关联
如果用名字和圆点成员选择运算符引用一个特定的对象来调用虚函数(如aCircle.Area()),则被调用虚函数是在编译时确定的(称为静态关联),调用的虚函数也就是为该特定对象的类定义(或继承该特定对象类)的函数。
纯虚函数和抽象类
如果将带有虚函数的类中的一个或者多个虚函数声明为纯虚函数,则该类就成为抽象类。纯虚函数是在声明时”初始化值”为0的函数,纯虚函数的一般形式:
virtual type func_name(参数表)=0。
例如:virtual float Area() const=0; //常量函数并且是纯虚函数
如果某个类是从一个带有纯虚函数的类派生出来的,并且没有在该派生类中提供该纯虚函数的定义,则该虚函数在派生类中仍然是纯虚函数,因而该派生类也是一个抽象类。
试图实例化一个抽象类对象(即包合一个或者多个纯虚函数的类)是一种语法错误。
虚函数和纯虚函数用法上有何区别?
有纯虚函数的类不能实例对象。
程序说明:
this关键字
this是一个关键字,它指向当前正在执行的类对象,使用this关键字告诉C++编译器到底要访问哪个变量。
虚函数与抽象类相关知识总结
1、基类中定义虚函数在前面加virtual,派生类中重新定义该函数时不用加virtual
2、虚函数必须是类的成员函数,不能将虚函数说明为全局(非成员)的函数,也不能说明为静态成员函数.
3、不能把友员函数(友员类)说明为虚函数,但虚函数可以是另一个类的友员(友员类).
4、析构函数可以是虚函数,但是构造函数不能是虚函数.
当在构造函数和析构函数中调用虚函数时,虚函数的虚特性将丢失,即不能实现动态联编.
5、一个函数被说明为虚函数,不管经历了多少次派生类层次,都保持其虚函数的特性.
6、当一个派生类中没有重新定义虚函数时,则使用其直接基类的虚函数版本.
7、纯虚函数:在基类中定义,任何派生类都必须定义自己的版本.
virtual 返回值类型 函数名(参数表)=0;
8、具有一个以上纯虚函数的类称为抽象类.
9、抽象类只能作为其他派生类的基类,不能建立自己的对象.
10、抽象类也不能作为参数类型,函数返回值类型或显示转换的类型.
11、可以声明抽象类的指针和引用.
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。在许多情况下,在基类中不能对虚函数给出有意义有实现,而把它说明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
抽象类
带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处
于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构
造函数说明为保护的访问控制权限。
抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的
子类是从这个根派生出来的。
抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组
子类共同的操作接口,而完整的实现留给子类。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,
而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函
数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
一、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
1.编译时多态性:通过重载函数实现;
2.运行时多态性:通过虚函数实现。
二、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数。可实现成员函数的动态重载。
三、纯虚函数
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数
的方法是在函数原型后加“=0”。如:
class Parent
{
virtual void Function()=0;//纯虚函数
}
四、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
抽象类
带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。
抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。
抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个
抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
virtual 关键字用于修饰方法、属性、索引器或事件声明,并且允许在派生类中重写这些对象。例如,此方法可被任何继承它的类重写。
public virtual double Area()
{
return x * y;
}
虚拟成员的实现可由派生类中的重写成员更改。
备注
调用虚方法时,将为重写成员检查该对象的运行时类型。将调用大部分派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。
默认情况下,方法是非虚拟的。不能重写非虚方法。
virtual 修饰符不能与 static、abstract 和 override 修饰符一起使用。
除了声明和调用语法不同外,虚拟属性的行为与抽象方法一样。
1、在静态属性上使用 virtual 修饰符是错误的。
2、通过包括使用 override 修饰符的属性声明,可在派生类中重写虚拟继承属性。
将变量或方法声明为final(最终),可以保证它们在使用中不被改变
abstract 修饰符可以和类、方法、属性、索引器及事件一起使用。在类声明中使用 abstract 修饰符以指示某个类只能是其他类的基类。标记为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。
备注
抽象类具有以下特性:
1、抽象类不能实例化。
2、抽象类可以包含抽象方法和抽象访问器。
3、不能用 sealed(C# 参考)修饰符修改抽象类,这意味着抽象类不能被继承。
4、从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实实现。
在方法或属性声明中使用 abstract 修饰符以指示方法或属性不包含实现。
抽象方法具有以下特性:
1、抽象方法是隐式的虚方法。
2、只允许在抽象类中使用抽象方法声明。
3、因为抽象方法声明不提供实际的实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号 ({ })。例如:
public abstract void MyMethod();
4、实现由一个重写方法提供,此重写方法是非抽象类的成员。
5、在抽象方法声明中使用 static 或 virtual 修饰符是错误的。
除了在声明和调用语法上不同外,抽象属性的行为与抽象方法一样。
1、在静态属性上使用 abstract 修饰符是错误的。
2、在派生类中,通过包括使用 override 修饰符的属性声明,可以重写抽象的继承属性。
抽象类必须为所有接口成员提供实现。
override 修饰符。
扩展或修改继承的方法、属性、索引器或事件的抽象实现或虚实现,必须使用 override 修饰符。
override 方法提供从基类继承的成员的新实现。通过 override 声明重写的方法称为重写基方法。重写的基方法必须与 override 方法具有相同的签名。
不能重写非虚方法或静态方法。重写的基方法必须是 virtual、abstract 或 override 的。
override 声明不能更改 virtual 方法的可访问性。override 方法和 virtual 方法必须具有相同的访问级别修饰符。
不能使用修饰符 new、static、virtual 或 abstract 来修改 override 方法。
重写属性声明必须指定与继承属性完全相同的访问修饰符、类型和名称,并且被重写的属性必须是 virtual、abstract 或 override 的。
接口就是纯抽象类,
委托当于函数指针,定义了委托就可以在不调用原方法名称的情况下调用那个方法 委托允许将方法作为参数进行传递。
枚举声明为一组属性相同的常量定义一个统一的类别名字。它常用于一些在编译时已知范围的常量。但这些常量的具体值要在执行时才能确定。
enum 枚举名
{ 枚举值表 };
enum weekday
{ sun,mon,tue,wed,thu,fri,sat } a,b,c;
a=sun;
b=mon;
c=tue;
printf("%d,%d,%d",a,b,c);
枚举声明为一组属性相同的常量定义一个统一的类别名字。它常用于一些在编译时已知范围的常量。但这些常量的具体值要在执行时才能确定。
枚举值是常量,不是变量 枚举元素本身由系统定义了一个表示序号的数值,从0 开始 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。
所谓泛型(Genericity),是指具有在多种数据类型上皆可操作的含意,与模板有些相似
sealed 修饰符。密封类不能被继承。密封方法会重写基类中的方法,但其本身不能在任何派生类中进一步重写
partial分部类型定义允许将类、结构或接口的定义拆分到多个文件中。编译是会自动编译在一起
在 File1.cs 中:
namespace PC
{
partial class A { }
}
在 File2.cs 中:
namespace PC
{
partial class A { }
}
extern 修饰符用于声明在外部(实现)的方法 例:public static extern int SampleMethod(int x);
readonly 关键字readonly 关键字与 const 关键字不同
const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。
sealed 修饰符 密封类不能被继承
unsafe 关键字(不安全代码)任何涉及指针的操作所必需的
lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。
如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。实现序列化
Object thisLock = new Object();
lock (thisLock)
{
// Critical code section
}
volatile 关键字表示字段可能被多个并发执行线程修改。声明为 volatile 的字段不受编译器优化(假
定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。
using 语句中声明对象 using 语句允许程序员指定使用资源的对象应当何时释放资源
定义一个范围,将在此范围之外释放一个或多个对象。
Font font2 = new Font("Arial", 10.0f);
using (font2)
{
// use font2
}