虚函数介绍
虚函数在类中声明为virtual的函数。
虚函数的形式为:
cpp
class 类名
{
//.......
virtual 返回值类型 函数名(参数列表);
};
例如,图形类(Shape)的show是虚函数。
cpp
#include <iostream>
using namespace std;
class Shape //图形类
{
public:
virtual void show()const //虚函数
{
cout << "我是一个图形" << endl;
}
};
class Rectangle:public Shape //长方形
{
public:
void show()const
{
cout << "我是一个长方形" << endl;
}
};
int main()
{
Shape s;
Rectangle r;
s.show();
r.show();
//把子类对象复制给父类对象,指针或者引用
cout << "把子类对象复制给父类对象,指针或者引用后" << endl;
s = r;
s.show();
Shape* ps = &r;
ps->show();
Shape& rs = r;
rs.show();
return 0;
}
虚函数说明:
(1)非类成员函数不能定义为虚函数。
(2)构造函数不能定义为虚函数,但析构函数可以。
(3)虚函数一般不声明为内联函数,因为对虚函数的调用采用的是动态绑定,而对内联函数采用的是静态绑定,即使虚函数在类体内定义,C++编译器也将它视为非内联函数。
(4)在声明函数时加"virtual",定义函数时不加"virtual"。
(5)当一个函数被声明为虚函数后,其派生类中的同名函数自动成为虚函数,在派生类声明该函数时可以加上 virtual,也可以不加,但习惯加virtual,使程序更加清晰。
(6)如果派生类没有对虚函数覆盖定义,则直接继承基类的虚函数。
有了虚函数动态才能实现动态多态.即传入什么类型的对象就调用它对应的函数。
cpp
#include <iostream>
using namespace std;
class Shape //图形类
{
public:
virtual void show()const //虚函数
{
cout << "我是一个图形" << endl;
}
};
class Rectangle:public Shape //长方形
{
public:
void show()const
{
cout << "我是一个长方形" << endl;
}
};
class Square :public Shape //正方形
{
public:
void show()const
{
cout << "我是正方形" << endl;
}
};
void test1(const Shape& s)//引用
{
s.show();
}
void test2(const Shape* ps)//指针
{
ps->show();
}
int main()
{
Shape s;
Rectangle r;
Square sq;
//引用
test1(s);//传入图形,调用图形的show
test1(r);//传入长方形,调用长方形的show
test1(sq);//传入正方形,调用正方形的show
cout << "--------------" << endl;//分割线
//指针
test2(&s);//传入图形,调用图形的show
test2(&r);//传入长方形,调用长方形的show
test2(&sq);//传入正方形,调用正方形的show
return 0;
}
总结:
动态多态满足条件
有继承关系
子类覆盖(override)父类中的虚函数
动态多态使用条件
父类指针或父类引用(更常用)指向子类对象
函数覆盖(override):在派生类中重新定义基类虚函数。要求返回值类型,函数名,参数列表与基类的虚函数相同。同名隐藏针对的是非虚函数.
C++11中的final和override说明符
派生类如果定义了一个函数与基类中虚函数的名字相同但是形参不同,这是合法的,编译器将认为新定义的这个函数与基类中原有的函数是相互独立的。这时,派生类的函数并没有覆盖掉基类中的函数。就实际的编程习惯而言,这往往意味着发生了错误,因为我们可能原本希望派生类能覆盖掉基类中的虚函数,但是一不小心把参数列表写错了。
要想调试并发现这样的错误显然非常困难。在 C++11 新标准中我们可以使用override 关键字来说明派生类中的虚函数。这么做的好处是使程序员的意图更加清晰的同时让编译器可以为我们发现一些错误,这在编程实践中显得非常重要。如果我们使用 override 标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错。
override:覆盖,它显式的告诉编译器,当前函数一定是覆盖基类中的虚函数.
cpp
class A
{
public:
virtual void f1(int) const;
virtual void f2();
void f3();
};
class B:public A
{
public:
void f1(int)const override; //正确,f1与基类中的f1匹配
void f2(int) override; //错误,A中的f2()和这个不一样
void f3() override; //错误,f3不是虚函数
void f4() override; //错误,A中没有f4函数
};
final:最终的,这个函数不允许再被子类覆盖
把函数定义成 final,则之后任何试图覆盖该函数的操作都是错误的。
cpp
class A
{
public:
virtual void f1(int) const;
};
class B:public A
{
public:
void f1(int)const final; //最终的,不允许后续的子类覆盖f1(int)
};
class C :public B
{
public:
void f1(int) const;//错误,B中已经将f1(int)声明为final
};
重点名称对比
函数重载:同一个函数名,不同的参数列表(参数个数或类型不同),构成函数重载
同名隐藏:在类继承中对于普通函数子类实现了和父类相同的函数(同名,同参数,同返回值)
函数覆盖:也称同名覆盖,在类继承中对于虚函数子类实现了和父类相同的函数(同名,同参数,同返回值)
本篇完!