一 虚函数:在类的成员函数前面加上virtual关键字,则这个成员函数称为虚函数
注意:只有类成员函数加virtual才能称为虚函数,普通函数不可以
二 虚函数的重写:在子类定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(覆盖)了父类的这个虚函数
三 多态的两个条件:1.虚函数重写(覆盖 ) 两个要求(1)虚函数 (2)函数全都相同(参数,函数名,返回值)
2.父类的指针或引用调虚函数
注意:重写时子类的virtual可以不用写,默认继承了虚函数属性(但是父类的必须要写)
看如下代码分析执行结果
四 一些结论规则及其讲解
1.派生类重写基类的虚函数实现多态,要求函数名,参数列表,返回值完全相同(协变除外)
协变是什么呢,指返回值可以不同(分别为父,子类指针引用)
2.只有类的成员函数才可以定义为虚函数
3.静态成员函数不能定义为虚函数
4.如果在类外定义虚函数,只能在声明时加virtual,类外定义函数时不能加virtual
5.构造函数不能为虚函数
原因:虚函数是对象创造出来由对象才调的,构造函数说明对象还在初始化,创建的一个阶段。因此不可以。
6.虽然可以将operator=定义为虚函数,但是最好不要将其定义为虚函数,因为使用时容易引起混淆。
解释:成员函数里面子类operator=是一个合成版本,合成版本也就是说子类赋值时只赋值子类的,父类的不管
去调用父类的去赋值,这时就有一个问题,你如果把它定义为虚函数,构成多态的行为,就可能导致一 个死循环
7.不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中对象是不完整的,可能会发生未定义的行 为。
因为虚函数有可能调到子类,子类有可能没有初始化,有可能已经析构了,因此出错
class A
{
public:
A(int x)
:_pa(new int(10))
{}
A(const A& a)
{
a.f();//当拷贝构造子类对象时,子类的拷贝构造函数会调用父类的拷贝构造函数合成,
//此时构成了多态又调用子类函数,而子类对象都没有初始化好,因此会出错
}
virtual void f()const
{
cout << *_pa << endl;
cout << "A" << endl;
}
protected:
int* _pa;
};
class B:public A
{
public:
B()
:A(10)
, _pb(new int(20))
{}
B(const B& b)
:A(b)
{
f();
}
virtual void f()const
{
cout << *_pb << endl;
cout << "B" << endl;
}
protected:
int* _pb;
};
int main()
{
B b1;
B b2(b1);
system("pause");
return 0;
}
8.最好把基类的析构函数定义为虚函数。(另外析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但仍是构成覆盖,因为编译器在这里做了特殊处理)可以认为函数名是相同的
原因:
class A
{
public:
A(int x=10)
:_pa(new int(10))
{}
~A()
{
delete _pa;
cout << "~A()" << endl;
}
protected:
int* _pa;
};
class B:public A
{
public:
B()
:A(10)
, _pb(new int(20))
{}
~B()
{
delete _pb;
cout << "~B()" << endl;
}
protected:
int* _pb;
};
int main()
{
A* pa = new B;
delete pa;
system("pause");
return 0;
}
四 纯虚函数
在成员函数的形参后面加上=0,则成员函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在派生类中重新定义以后,派生类才能实例化出对象