虚函数与多态

虚函数:

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。

多态缺陷

降低了程序运行效率(多态需要去找虚表的地址) 
空间浪费。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值