初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。

1.c++多态的概念以及用途。

1.1虚函数实现多态
通过基类指针只能够访问派生类的成员变量,不能够访问派生类的成员函数。
解决问题的办法:使用虚函数(virtual function),只需要在函数声明前面增加virtual关键字。看下面的代码。
#include <iostream>
using namespace std;

class people{
public :
	people(char *name, int age);
	virtual	void show();
protected :
	char *m_name;
	int m_age;
};

people :: people (char *name, int age) : m_name(name), m_age(age){}

void people :: show()
{
	cout<<m_name<<", "<<m_age<<" old"<<endl;
}

class student : public people{
public : 
	student (char *name, int age, float score);
	virtual void show();
protected :
	float m_score;
};

student :: student (char *name, int age, float score) : people(name, age), m_score(score){}

void student :: show()
{
	cout<<m_name<<", "<<m_age<<" old, "<<m_score<<endl;
}

int main()
{
	people *p = new people("xiaoli", 22);
	p->show();
	
	p = new student("xiaozhang", 16, 95.6);
	p->show();
	
	delete p;
	
	return 0;
}
结果:
aoli, 22 old
xiaozhang, 16 old, 95.6
如果不加virtual关键字,则运行结果是:
xiaoli, 22 old
xiaozhang, 16 old

有了虚函数,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,这就是多态(Polymorphism)!

c++中虚函数的唯一用处就是构成多态。

1.2借用引用也可以实现多态
在之前的学习中,可以了解到,指针在本质上是通过指针的方式实现的(引用只是对指针进行了封装,底层还是通过指针实现的)。
把上面的例子改一下:
int main()
{
people p("xiaoli", 22);
    student q("xiaozhang", 16, 95.6)

people &rp = p;
people &rq = q;

rp.show();
rq.show();

return 0;
}
实现的功能和上面的例子是一样的。


1.3虚函数的要点
虚函数对于多态具有决定性的作用,有虚函数才能构成多态!


对于虚函数还有以下几点注意点:
1) 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。


2) 为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽(覆盖)关系的同名函数都将自动成为虚函数。


3) 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。


4) 只有派生类的虚函数遮蔽基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。


5) 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。


6) 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。


构成多态的条件:
1.必须存在继承关系!
2.继承关系中必须有同名的虚函数,并且它们是遮蔽(覆盖)关系!
3.存在基类的指针,通过该指针调用虚函数!
看下面的代码。
#include <iostream>
using namespace std;

class base{
public :
	virtual void func();
	virtual void func(int);
};

void base :: func()
{
	cout<<"base:func()"<<endl;
}

void base :: func(int a)
{
	cout<<"base:func(int)"<<endl;
}

class der : public base{
public :
	void func();
	void func(char *);
};

void der :: func()
{
	cout<<"der:func()"<<endl;
}

void der :: func(char *b)
{
	cout<<"der:func(char *)"<<endl;
}

int main()
{
	base *p = new der;
	p->func();          //调用的是派生类的虚函数,构成了多态。
	p->func(100);       //调用的是基类的虚函数,因为派生类中没有函数遮蔽它。
	//p_>func("hello"); //出现编译错误,因为通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员。
	
	return 0;
}
再来补充一下什么时候需要用到虚函数:
首先看成员函数所在的类是否会作为基类。
然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。
如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。

2.虚析构函数

首先要明白,构造函数不能是虚函数,因为派生类不能继承基类的构造函数,将构造函数声明为虚函数没有意义!
先看代码
#include <iostream>
using namespace std;

class base{
public :
	base();
	virtual ~base();
protected :
	char *ptr;
};

base :: base()
{
	ptr = new char[100];
	cout<<"base construct"<<endl;
}

base :: ~base()
{
	delete [] ptr;
	cout<<"~base deconstruct"<<endl;
}

class der : public base{
public :
	der();
	~der();
protected :
	char *name;
};

der :: der()
{
	name = new char[100];
	cout<<"der construct"<<endl;
}

der :: ~der()
{
	delete[] name;
	cout<<"~der deconstruct"<<endl;
}

int main()
{
	base *p = new der;
	delete p;	
	
	return 0;
}
将基类的析构函数声明为虚函数后,派生类的析构函数也会自动成为虚函数。这个时候编译器会忽略指针的类型,而根据指针的指向来选择函数;
也就是说,指针指向哪个类的对象就调用哪个类的函数。
(补充!!:指针访问非虚函数时,编译器会根据指针的类型来确定要调用的函数。如果指针是派生类的指针,编译器会根据它的类型匹配到派生类的析构函数,
在执行派生类的析构函数的过程中,又会调用基类的析构函数。派生类析构函数始终会调用基类的析构函数,并且这个过程是隐式完成的)。
在实际开发中,一旦我们自己定义了析构函数,就是希望在对象销毁时用它来进行清理工作,比如释放内存、关闭文件等,如果这个类又是一个基类,
那么我们就必须将该析构函数声明为虚函数,否则就有内存泄露的风险。也就是说,大部分情况下都应该将基类的析构函数声明为虚函数。

3.虚函数表和多态实现机制。

强调!!!当通过指针访问类的成员函数时:
1.如果该函数是非虚函数,那么编译器会根据指针的类型找到该函数;也就是说,指针是哪个类的类型就调用哪个类的函数。
2.如果该函数是虚函数,并且派生类有同名的函数遮蔽它,那么编译器会根据指针的指向找到该函数;
也就是说,指针指向的对象属于哪个类就调用哪个类的函数。这就是多态。

编译器之所以能通过指针指向的对象找到虚函数,是因为在创建对象时额外地增加了虚函数表。

看一个例子:
#include <iostream>
#include <string>
using namespace std;
//People类
class People{
public:
    People(string name, int age);
public:
    virtual void display();
    virtual void eating();
protected:
    string m_name;
    int m_age;
};
People::People(string name, int age): m_name(name), m_age(age){ }
void People::display(){
    cout<<"Class People:"<<m_name<<"今年"<<m_age<<"岁了。"<<endl;
}
void People::eating(){
    cout<<"Class People:我正在吃饭,请不要跟我说话..."<<endl;
}
//Student类
class Student: public People{
public:
    Student(string name, int age, float score);
public:
    virtual void display();
    virtual void examing();
protected:
    float m_score;
};
Student::Student(string name, int age, float score):
    People(name, age), m_score(score){ }
void Student::display(){
    cout<<"Class Student:"<<m_name<<"今年"<<m_age<<"岁了,考了"<<m_score<<"分。"<<endl;
}
void Student::examing(){
    cout<<"Class Student:"<<m_name<<"正在考试,请不要打扰T啊!"<<endl;
}
//Senior类
class Senior: public Student{
public:
    Senior(string name, int age, float score, bool hasJob);
public:
    virtual void display();
    virtual void partying();
private:
    bool m_hasJob;
};
Senior::Senior(string name, int age, float score, bool hasJob):
    Student(name, age, score), m_hasJob(hasJob){ }
void Senior::display(){
    if(m_hasJob){
        cout<<"Class Senior:"<<m_name<<"以"<<m_score<<"的成绩从大学毕业了,并且顺利找到了工作,Ta今年"<<m_age<<"岁。"<<endl;
    }else{
        cout<<"Class Senior:"<<m_name<<"以"<<m_score<<"的成绩从大学毕业了,不过找工作不顺利,Ta今年"<<m_age<<"岁。"<<endl;
    }
}
void Senior::partying(){
    cout<<"Class Senior:快毕业了,大家都在吃散伙饭..."<<endl;
}
int main(){
    People *p = new People("赵红", 29);
    p -> display();
    p = new Student("王刚", 16, 84.5);
    p -> display();
    p = new Senior("李智", 22, 92.0, true);
    p -> display();
    return 0;
}
各个类的对象内存模型如下所示:


这里的东西及原理有些复杂,况且还是单继承。对于多继承,复杂程度不可想象,这里不做深究,有兴趣的同学自己研究吧。知道怎么用虚函数的我,先满足一下。代以后再深究。

4.纯虚函数

在C++中,可以将虚函数声明为纯虚函数,
语法格式为:
               virtual 返回值类型 函数名 (函数参数) = 0;
1.纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。
2. 最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。

包含纯虚函数的类称为抽象类(Abstract Class)。抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。看代码
#include <iostream>
using namespace std;

class line{
public:
	line(float len);
	virtual float area() = 0;
	virtual float volume() = 0;
protected:
	float m_len;
};

line :: line(float len): m_len(len){}

class rec: public line{
public:
	rec(float len, float width);
	float area();
protected:
	float m_width;
};

rec :: rec(float len, float width): line(len), m_width(width){}

float rec :: area()
{
	return m_len * m_width;
	float area();
protected:
    float m_width;
}

Rec::Rec(float len, float width): Line(len), m_width(width){}
float Rec::area()
{
	return m_len * m_width;
}

class cuboid: public rec{
public:
	cuboid(float len, float width, float height);
    float area();
    float volume();
protected:
    float m_height;
};

Cuboid::Cuboid(float len, float width, float height): Rec(len, width), m_height(height){}

float Cuboid :: area()
{ 
	return 2 * ( m_len*m_width + m_len*m_height + m_width*m_height);
}

float Cuboid :: volume(){
	return m_len * m_width * m_height;
}

class Cube: public Cuboid{
public:
    Cube(float len);
    float area();
    float volume();
};

Cube::Cube(float len): Cuboid(len, len, len){}

float Cube::area(){
	return 6 * m_len * m_len; 
}

float Cube::volume(){
	return m_len * m_len * m_len; 
}

int main()
{
    Line *p = new Cuboid(10, 20, 30);
    cout<<"The area of Cuboid is "<<p->area()<<endl;
    cout<<"The volume of Cuboid is "<<p->volume()<<endl;
  
    p = new Cube(15);
    cout<<"The area of Cube is "<<p->area()<<endl;
    cout<<"The volume of Cube is "<<p->volume()<<endl;
	
	return 0;
} 
在实际开发中,你可以定义一个抽象基类,只完成部分功能,未完成的功能交给派生类去实现(谁派生谁实现)。这部分未完成的功能,往往是基类不需要的,或者在基类中无法实现的。虽然抽象基类没有完成,但是却强制要求派生类完成,这就是抽象基类的“霸王条款”。

注意:
1) 一个纯虚函数就可以使类成为抽象基类,但是抽象基类中除了包含纯虚函数外,还可以包含其它的成员函数(虚函数或普通函数)和成员变量。

2) 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中,虚函数是一种特殊的函数,它可以被子类重写并通过父类指针或引用进行调用。虚函数通过虚函数(vtable)实现多态。 1. 虚函数 虚函数是在基类中使用 virtual 关键字声明的函数。当子类继承基类并重写虚函数时,可以通过父类指针或引用调用子类的虚函数虚函数的用法如下: ```c++ class Animal { public: virtual void eat() { std::cout << "Animal is eating." << std::endl; } }; class Cat : public Animal { public: void eat() override { std::cout << "Cat is eating." << std::endl; } }; int main() { Animal* animal = new Cat(); animal->eat(); // 输出 Cat is eating. delete animal; return 0; } ``` 2. 纯虚函数 纯虚函数是没有实现虚函数,它的声明使用 "= 0" 标记。纯虚函数必须在子类中被重写才能实现多态纯虚函数的用法如下: ```c++ class Animal { public: virtual void eat() = 0; }; class Cat : public Animal { public: void eat() override { std::cout << "Cat is eating." << std::endl; } }; int main() { Animal* animal = new Cat(); animal->eat(); // 输出 Cat is eating. delete animal; return 0; } ``` 3. 虚析构函数 虚析构函数是在基类中使用 virtual 关键字声明的析构函数。在使用基类指针或引用删除子类对象时,需要使用虚析构函数以确保正确地释放资源。 虚析构函数的用法如下: ```c++ class Animal { public: virtual ~Animal() { std::cout << "Animal is destroyed." << std::endl; } }; class Cat : public Animal { public: ~Cat() override { std::cout << "Cat is destroyed." << std::endl; } }; int main() { Animal* animal = new Cat(); delete animal; // 输出 Cat is destroyed. Animal is destroyed. return 0; } ``` 4. 虚函数 虚函数是一个存储虚函数指针的数组,它被用来实现多态。每个包含虚函数的类都有一个虚函数,存储在对象的内存布局中。 虚函数的内部实现是编译器依据虚函数的重载情况生成的,开发者无法直接操作虚函数虚函数的用法不需要手动编写,它是由编译器自动生成的。 以上是虚函数纯虚函数虚析构函数虚函数的简要介绍和用法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值