虚函数的好处是,你只要有一个基类的指针,就可以根据情况来执行派生类中的函数。
比如你给的程序中,main中只有一个指针, 他是指向基类A的 p;
但是你注意这里, p = &b; p->print();
这里将输出 20; 及bsetb(20)的结果。
而如果print不是虚函数,这里将输出 10, 你可以自己试试;
也就是说,我们用基类类型的指针调用派生类的函数, 那么该函数必须为虚函数(注意,基类中的虚函数将会是派生类中所有相应函数默认为虚函数)。否则, 该指针将调用基类中的相应函数。
在就题解释一下, 如题, 如果A中print不是虚函数,
void main()
{
A a,p;
B b;
aseta(5);//aa = 5
bseta(10); //ba = 5;
bsetb(20); //bb = 20;
p=&a;p->print(); //aprint() 输出5
p=&b;p->print(); //注意,因为print不再是虚函数, 而p是A类型的指针, &b却是B类型的类的地址, 这里,编译器会处理成,p->print()调用的是b的基类中的print(),也就是说,因为指针类型不符,本来被覆盖了的基类print 又被调用了出来(这也算是一种处理机制了,别问为什么。)
}
好了,这就是虚函数, 统一的基类指针,根据其指向对象的类型,相应的选择函数。 如果你不设成虚函数,那就没这功能。
C++中的虚函数和纯虚函数用法
1虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。
2虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)
只有声明而没有定义。
3虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
4虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
5虚函数的定义形式:virtual {method body} ;纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
6如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。
虚函数的作用就是接口类能够提供协议(即这个接口的功能是什么,比如都是进行绘画,相对来说比较抽象),而各个子类可以给出具体不同的实现(比如长方形绘画时可以画出长方形,而三角形则画出来的就是三角形)。调用虚函数的客户只需要知道虚函数的提供的协议,而不需要关心这个实现这个虚函数的到底是个什么类(因为都是从一个基类继承的)。
当然你可能会想,为什么不能用非虚函数做上面的事情,我们可以实现时指定一个类型,比如有一个成员用来标识形状的类型,而在实现时用这个类型来判定如何进行绘画,简而言之就是if else或者switch语句,如同下面的代码:
class shape {int type;
public:
void draw() {
switch (type) {
case rectangle:
draw_rectangle();
break;
case circle:
draw_circle();
break;
}
};
这个问题在于,当你有一个很多shape时,这个switch case将难以管理,因为有时候代码中有些地方就是需要根据类型来做判断,但是此时你会倾向于使用switch case来实现这些代码。久而久之,一旦加个新类型时,就会很难维护,因为要改动很多地方,就容易出错(当然如果改动地方不多,问题不会很大)。
但是如果使用虚函数实现,那么各个子类管理自己的实现,而有需要依据类型来判断的地方,可以通过虚函数来实现,因为每个类可以定制这种行为,对于draw而言,可以
class rectangle : public shape {public:
virtual void draw() {
// draw rectangle;
}
};
class circle : public shape {
public:
virtual void draw() {
// draw circle
}
};
这样当我们需要加新shape的时候,只需要添加一个新的子类,实现draw(或者其他依赖于类型的函数时),就不用到处去修改那些swtich case语句了,也不容易出错了。
敏捷开发一书中,提到的OCP(开放封闭原则),就可以用虚函数来做到。
using System;
using SystemCollectionsGeneric;
using SystemLinq;
using SystemText;
namespace ConsoleApplication2
{
class Program
{
class S1
{
public virtual void Method()
{
ConsoleWriteLine("这是S1实例方法");
}
}
class S2:S1
{
public virtual void Method()
{
ConsoleWriteLine("这是S2实例方法");
}
}
class S3 : S2
{
public virtual void Method()
{
ConsoleWriteLine("这是S3实例方法");
}
}
static void Main(string[] args)
{
S3 s = new S3();
// 调用S3Methoed
sMethod();
// 调用S2Methoed
((S2)s)Method();
// 调用S1Methoed
((S1)s)Method();
}
}
}
调用顺序显然是fun1调用D类成员的show函数,fun2和fun3调用B类成员的show函数
原因:全局main函数中,p是上转型对象,使用D类型的成员(谁叫它用了D类的构造函数嘛),而且没有强制转换为B类类型(只是弄了个B类指针而已),因此fun1要调用D类的成员函数。对象b和fun2不用解释,对象d虽然是D类类型的,但在调用fun3函数时,函数参数b被强制转换成B类类型,因此使用B类的成员函数
virtual void print()const;//f2
virtual void print() //f1
f1和f2的函数签名是不一样的,circle内的f1类型的print 和getArea都是Two类里面的,circle内的f2类型的函数是f1类型的函数的重载
你是说如果把base::Out改成Out会出错是吧?
因为如果不指明调用基类的虚函数,那么编译器就必须要等派生类同名的重载函数产生后,调用派生名的函数。而要生成派生类的Out函数,必须先生成派生类的实例。而生成派生类的实例,又必须先调用基类的构造函数。基类的构造函数中又调用了Out,而这时候派生类的实例还没有生成,相应的Out函数也还没生成,所以直接调用Out会导致无法链接到有效的派生类的Out函数,所以出错。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)