多态典型用例之virtual

多态典型用例之virtual

参考:https://www.cnblogs.com/dormant/p/5223215.html

1.虚函数(virtual)

(1)在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
实例:

#include "pch.h"
#include <iostream>
using namespace std;

class A
{
public:
    void print()
    {
        cout<<"class A"<<endl;
    }
};

class B:public A
{
public:
    void print()
    {
        cout<<"class B"<<endl;
    }
};

int main()
{
    A a;
    B b;
    a.print();
    b.print();
    return 0;
}

输出结果:

(2)通过class A和class B的print()这个接口,可看出两个class采用了不同的策略,但这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。改main函数为如下:

int main()
{
    A a;
    B b;
    A *P1=&a;
    A *p2=&b;
    p1->print();
    p2->print();
    return 0;
}

输出结果:

(3)p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数。

class A
{
public:
    virtual void print()
    {
        cout<<"class A"<<endl;
    }
};

class B:public A
{
public:
    void print()
    {
        cout<<"class B"<<endl;
    }
};

输出结果:

现在,class A的成员函数print()已经成了虚函数 class B的print()也成了虚函数了。我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。(对于在派生类的相应函数前是否需要用virtual关键字修饰,语法上可加可不加,不加的话编译器会自动加上,但为了阅读方便和规范性,建议加上)

总结:指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

2.虚析构函数

当一个类有子类时,该类的析构函数必须是虚函数,原因:会有资源释放不完全的情况。

#include "pch.h"
#include <iostream>
using namespace std;

class Base
{
public:
	~Base()
	{
		cout << "~Base()" << endl;
	}
};

class Derived :public Base
{
public:
	Derived()
	{
		p = new int(0);
	}
	~Derived()
	{
		cout << "~Derived()" << endl;
		delete p;
	}
private:
	int *p;
};

void fun(Base *b)
{
	delete b;
}

int main()
{
	Base *b = new Derived();
	fun(b);
	return 0;
}

输出结果:

这里可以看到,对象销毁时只调用了父类的析构函数。如果这时子类的析构函数中有关于内存释放的操作,将会造成内存泄露。所以需要给父类的析构函数加上virtual。

class Base
{
public:
	 virtual ~Base()
	{
		cout << "~Base()" << endl;
	}
};

输出结果:

3.纯虚函数(抽象函数)

纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。
纯虚函数就是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”。

class base1
{
public:
	virtual void display() const=0;
};
int main()
{
	base1 b1;//错误
	base2 b2;
	derived d;
	fun(&b1);
	fun(&b2);
	fun(&d);
	return 0;
}

图:

base1是一个抽象类,不能实例化。

例1:

#include "pch.h"
#include <iostream>
using namespace std;

class base1
{
public:
	virtual void display() const=0;
};


class base2 :public base1
{
public:
	virtual void display() const;
};

void base2::display() const
{
	cout << "base2::display()" << endl;
}

class derived :public base2
{
public:
	virtual void display() const;

private:

};

void derived::display() const
{
	cout << "derived::display()" << endl;
}

void fun(base1 *ptr)
{
	ptr->display();
}

int main()
{
	base2 base2;
	derived derived;
	fun(&base2);
	fun(&derived);
	return 0;
}

输出结果:

例2:

#include "pch.h"
#include<iostream>
using namespace std;

class Fish
{
public:
    virtual void water() = 0;
    virtual void eat() = 0;
};

class Shark : public Fish 
{
public:
    void water();
    void eat();
};
void Shark::eat(){cout<<"Shark eat. "<<endl;}
void Shark::water(){cout<<"Shark water. "<<endl;}
void fun(Fish *f)
{
    f->eat();
    f->water();
}

void main()
{
    Shark s;
    Fish *f = &s;
    fun(f);
}

输出结果:

a.定义纯虚函数时,不能定义纯虚函数的实现部分。即使是函数体为空也不可以,函数体为空就可以执行,只是什么也不做就返回。而纯虚函数不能调用。
(其实可以写纯虚函数的实现部分,编译器也可以通过,但是永远也无法调用。因为其为抽象类,不能产生自己的对象,而且子类中一定会重写纯虚函数,因此该类的虚表内函数一定会被替换掉,所以可以说永远也调用不到纯虚函数本身)
b."=0"表明程序将不定义该函数,函数声明是为派生类保留一个位置。“=0”的本质是将指向函数体的指针定为NULL。
c.在派生类中必须有重新定义的纯虚函数的函数体,这样的派生类才能用来定义对象。(如果不重写进行覆盖,程序会报错)

posted @ 2019-10-24 11:02  喃猫  阅读(253)  评论(0编辑  收藏  举报