虚函数:
C++中的虚函数的作用主要是实现了多态的机制。
虚函数是一种在基类定义为virtual的函数,并在一个或多个派生类中再定义的函数。虚函数的特点是,只要定义一个基类的指针,就可以指向派生类的对象。
注:无虚函数时,遵循以下规则:C++规定,定义为基类的指针,也能作指向派生类的指针使用,并可以用这个指向派生类对象的指针访问继承来的基类成员;但不能用它访问派生类的成员。
使用虚函数实现运行时的多态性的关键在于:必须通过基类指针访问这些函数。
一旦一个函数定义为虚函数,无论它传下去多少层,一直保持为虚函数。
把虚函数的再定义称为过载(overriding)而不叫重载(overloading)。
纯虚函数:是定义在基类中的一种只给出函数原型,而没有任何与该基类有关的定义的函数。纯虚函数使得任何派生类都必须定义自己的函数版本。否则编译报错。
纯虚函数定义的一般形式:
Virtual type func_name(参数列表)=0;
含有纯虚函数的基类称为抽象基类。抽象基类又一个重要特性:抽象类不能建立对象。但是抽象基类可以有指向自己的指针,以支持运行时的多态性。
使用虚函数时,有两点要注意,如下所述 。
1 )只能用 virtual 关键宇声明类的成员函数,使它成为虚函数,而不能将类外的普通函
数声明为虚函数。 因为虚函数的作用是允许在派生类中对基类的虚函数重新定义 。 显然,它
只能用于类的继承层次结构中 。
2 ) 一个成员函数被声明为虚函数后,在同 一类族中的类就不能再定义一个非 virtua l 的
但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。
举个例子:
#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
virtual void who()
{
ECHOLN("我是基类!");
}
};
class deriv_1:public Base
{
public:
voidwho()
{
ECHOLN("我是子类deriv_1");
}
};
class deriv_2:public Base
{
public:
void who()
{
ECHOLN("我是子类deriv_2");
}
};
int main()
{
classBase *b,b0;
classderiv_1 d1;
classderiv_2 d2;
b= &b0;
b->who();
b= &d1;
b->who();
b= &d2;
b->who();
return0;
}
输出:
我是基类!
我是子类deriv_1
我是子类deriv_2
当一个类带有虚函数时 i 编译
系统会为该类构造一个虚函数表,它是一个指针数组,用于存放每个虚函数的人口地址 。 系
统在进行动态关联时的时间开销是很少的,因此,多态是高效的 。
多态
多态的概念
同一种东西在不同场景下的多种形态,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。比如举个例子:见人说人话,见鬼说鬼话
.动态多态的分类
静态多态
静态多态是编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型的转换),可推断出要调用哪个函数,如果有对应的函数就调用该函数,否则出现编译错误。
(1)动态多态的实现条件:
基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写
通过基类对象的指针或引用调用虚函数
所谓重写是:a.基类函数必须为虚函数,b.派生类函数必须与基类中的虚函数保持一致(包括返回类型、函数名称、参数列表) 但是有例外:即协变:
第一种是:基类虚函数必须返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用
第二种是:虚拟的析构函数(基类和派生类的析构函数名字不相同)
第三种是:派生类的虚函数可以与基类中的虚函数访问限定符不一样。
举个例子:
#include<bits/stdc++.h>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun()
{
printf("2\n");
}
};
class B : public A
{
public:
void foo()
{
printf("3\n");
}
void fun()
{
printf("4\n");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
return 0;
}
第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,因此输出的结果也会是子类的结果4。
多态缺陷
降低了程序运行效率(多态需要去找虚表的地址)
空间浪费。