C++(5)虚函数

203 阅读6分钟

多态

虚函数

虚函数就是在类的成员函数声明前加virtual,该成员函数就变成了虚函数。一旦一个类中有虚函数,编译器就会为该类生成虚函数表。

虚函数表中一个元素记录一个虚函数的地址,使用该类构造对象时,对象前4(8)个字节记录虚函数表的首地址。

/*02-虚函数和虚函数表*/
#include <iostream>

using namespace std;

class Animal{
public:
    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};

class Cat:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Cat show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Cat eat fish"<<endl;
    }

};

typedef void (*pfunc_t)();//为函数指针类型起别名

int main()
{
    
    //没有成员变量的类类型对象所占空间大小是1
    //cout<<sizeof(Animal)<<endl;
    Animal an;
    //long addr = *(long *)&an;//取出对象前8个字节的数据
    //pfunc_t *pt = (pfunc_t *)addr;//转化为虚函数表首地址类型

    pfunc_t *pt = *(pfunc_t **)&an;//取出对象前8个字节的数据 --- 虚函数表的地址
    //调用虚函数
    pt[0]();//an.show();
    pt[1]();//an.eat();

	       
	     return 0;
}

如果父类中有虚函数,子类中可以对虚函数进行重写(overwrite),子类会继承父类的虚函数表,但是重写的虚函数会覆盖父类中对应虚函数在虚函数表中的位置。


/*02-虚函数和虚函数表*/
#include <iostream>

using namespace std;

class Animal{
public:
    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};



typedef void (*pfunc_t)();//为函数指针类型起别名

int main()
{

    Dog dg;

    pt = *(pfunc_t **)&dg;//取出对象前8个字节的数据 --- 虚函数表的地址
    //调用虚函数
    pt[0]();//an===>show();
    pt[1]();//dg===>eat();
    return 0;
}

如果父类的虚函数在子类中被重写,则可以使用父类类型记录子类对象,此时调用虚函数会去调用子类中虚函数的实现,而不调用父类中的原虚函数,普通的成员函数没有这个特点。

/*02-虚函数和虚函数表*/
#include <iostream>

using namespace std;

class Animal{
public:
    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

class Dog:public Animal{
public:
    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};



typedef void (*pfunc_t)();//为函数指针类型起别名

int main()
{
    
    //有虚函数,父类类型记录子类对象
    Animal *pa = new Dog;   //可以会有指向问题,不建议这样使用
    pa->show();          //有虚函数就可以这样写
    pa->run();
    pa->eat();

    delete pa;
    return 0;
}

使用虚函数可以实现用父类类型记录子类对象,可以通过父类型的 指针/引用 访问子类中对应的接口,大大提高编程的灵活性(动态绑定),这种语法就叫多态。(简单的是父类指向子类)

/*03-多态*/
#include <iostream>

using namespace std;

class Animal{
public:
    //虚析构
    virtual ~Animal()
    {
        cout<<"~Animal"<<endl;
    }

    //成员函数
    virtual void show()//虚函数
    {
        cout<<"Animal show"<<endl;
    }

    void run()
    {
        cout<<"Animal run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Animal eat"<<endl;
    }

};

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

    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Dog show"<<endl;
    }

    void run()//名字隐藏
    {
        cout<<"Dog run"<<endl;
    }

    virtual void eat()
    {
        cout<<"Dog eat bones"<<endl;
    }

};

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

    //重写虚函数
    virtual void show()//虚函数
    {
        cout<<"Cat show"<<endl;
    }

    virtual void eat()
    {
        cout<<"Cat eat fish"<<endl;
    }

};

//传入一个Animal对象 ----- 多态
void animal_gogo(Animal *p)
{
    p->show();
    p->eat();
}

int main()
{
    Animal *pa = new Animal;
    //pa->show();
    //pa->run();
    //pa->eat();
    animal_gogo(pa);   //动态接口绑定 
    delete pa;

    //有虚函数,父类类型记录子类对象
    pa = new Dog;
    //pa->show();
    //pa->run();
    //pa->eat();
    animal_gogo(pa);
    delete pa;

    pa = new Cat;
    //pa->show();
    //pa->run();
    //pa->eat();
    animal_gogo(pa);
    delete pa;

    //Dog dg;   
    //Animal &ra = dg;
    //ra.show();   //这样也是多态
    //ra.Animal::show();

    return 0;
}

几种重名机制的处理 --------- 函数重载(overload) 名字隐藏(namehide) 虚函数重写(overwrite)

函数重载:在同一作用域,函数名相同,参数列表不同的函数构成重载关系 名字隐藏:子类中出现与父类中同名的成员,子类中父类的同名成员会被隐藏 虚函数重写:子类中重写父类的虚函数,父类的虚函数在子类的虚函数表中将被覆盖(覆盖是虚函数表)

多态的总结

通过父类 指针/引用 记录子类对象,调用虚函数时体现的是子类中虚函数的实现。

1.继承是多态的基础 2.虚函数是实现多态的关键 3.虚函数重写是实现多态的必要条件

练习:

实现一个类Phone,有成员price和虚函数show,func,继承产生SmartPhone,重写其虚函数,SmartPhone继承产生IPhone,重写其虚函数。

实现一个全局函数void Use_Phone(Phone &p),参数时Phone的引用,在函数中传递不同对象调用该函数。

/*03-多态*/
#include <iostream>

using namespace std;

class Phone {
public:
    //虚析构
    Phone(double p = 0.0) :price(p)
    {
       
    }

    //成员函数
    virtual void show()//虚函数
    {
        cout << "Phone show" << endl;
    }
    virtual void func()//虚函数
    {
        cout << "Phone 打电话" << endl;
    }
    double get_pricr() {
        return this->price;
    }
private:
    double price;
};

class SmartPhone :public Phone {
public:

    SmartPhone(double p = 0.0) :Phone(p)
    {
      
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "SmartPhone show" << endl;
    }
    virtual void func()//虚函数
    {
        cout << "SmartPhone 打电话,听音乐" << endl;
    }

};

class IPhone :public SmartPhone {
public:

    IPhone(double p = 0.0) :SmartPhone(p)
    {
      
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "IPhone show" << endl;
    }
    virtual void func()//虚函数
    {
        cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
    }

};


//传入一个Animal对象 ----- 多态
void usePhone(Phone &p){

    p.show();
    p.func();
}

int main()
{
    Phone nokia(300);
    SmartPhone xiaomi(2000);
    IPhone iphone13(6000);

    usePhone(nokia);
    usePhone(xiaomi);
    usePhone(iphone13);

    return 0;
}

另一种写法

但是这个方法在delate时会调用析构父类的方法,引入虚析构加上关键字

#include <iostream>

using namespace std;

class Phone {
public:



    Phone(double p = 0.0) :price(p)
    {

    }

    //成员函数
    virtual void show()//虚函数
    {
        cout << "Phone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "Phone 打电话" << endl;
    }
    double get_price() {
        return this->price;
    }
private:
    double price;
};

class SmartPhone :public Phone {
public:

    SmartPhone(double p = 0.0) :Phone(p)
    {

    }

  
       
            //重写虚函数
        virtual void show()//虚函数
        {
            cout << "SmartPhone show " << this->get_price() << endl;
        }
        virtual void func()//虚函数
        {
            cout << "SmartPhone 打电话,听音乐" << endl;
        }

};

class IPhone :public SmartPhone {
public:

    IPhone(double p = 0.0) :SmartPhone(p)
    {

    }



    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "IPhone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
    }

};


//传入一个Animal对象 ----- 多态
void usePhone(Phone* p) {

    p->show();
    p->func();
}

int main()
{

    Phone* nokia = new Phone(300);

    usePhone(nokia);   //动态接口绑定 
    delete nokia;


    SmartPhone* xiami = new SmartPhone(1000);

    usePhone(xiami);   //动态接口绑定 
    delete xiami;

    IPhone* iphone13 = new IPhone(6000);

    usePhone(iphone13);   //动态接口绑定 
    delete iphone13;   //这个方法会调用父类的方法


    return 0;
}

虚析构

如果父类类型指向子类对象,当释放对象时,默认调用父类的析构函数而不是子类的析构函数。如果在多态中希望根据对象的具体类型调用其析构函数,需要将析构函数写成虚析构(在析构函数前加virtual)。

类中本身有析构函数,同时也有虚函数时必须将析构函数写成虚析构。

#include <iostream>
/* employer  */

using namespace std;

class Phone {
public:

    //虚析构
    /*
     ~Phone()
    {
        cout<< " ~Phone show " << endl;
    }

    
    */
    virtual ~Phone()
    {
        cout << " ~Phone show " << endl;
    }

    Phone(double p = 0.0) :price(p)
    {

    }

    //成员函数
    virtual void show()//虚函数
    {
        cout << "Phone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "Phone 打电话" << endl;
    }
    double get_price() {
        return this->price;
    }
private:
    double price;
};

class SmartPhone :public Phone {
public:

    SmartPhone(double p = 0.0) :Phone(p)
    {

    }

    /*
    
    SmartPhone() {
        cout << " ~SmartPhone show " << endl;
    }
    
    
    */
    virtual ~SmartPhone() {
        cout << " ~SmartPhone show " << endl;
    }
       
            //重写虚函数
        virtual void show()//虚函数
        {
            cout << "SmartPhone show " << this->get_price() << endl;
        }
        virtual void func()//虚函数
        {
            cout << "SmartPhone 打电话,听音乐" << endl;
        }

};

class IPhone :public SmartPhone {
public:

    IPhone(double p = 0.0) :SmartPhone(p)
    {

    }

    /*
    
    ~IPhone()
    {
        cout << " ~IPhone show " << endl;
    }
    
    */
    virtual ~IPhone()
    {
        cout << " ~IPhone show " << endl;
    }

    //重写虚函数
    virtual void show()//虚函数
    {
        cout << "IPhone show " << this->get_price() << endl;
    }
    virtual void func()//虚函数
    {
        cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
    }

};


//传入一个Animal对象 ----- 多态
void usePhone(Phone* p) {

    p->show();
    p->func();
}

int main()
{

    Phone* nokia = new Phone(300);

    usePhone(nokia);   //动态接口绑定 
    delete nokia;


    SmartPhone* xiami = new SmartPhone(1000);

    usePhone(xiami);   //动态接口绑定 
    delete xiami;

    IPhone* iphone13 = new IPhone(6000);

    usePhone(iphone13);   //动态接口绑定 
    delete iphone13;   //这个方法会调用父类的方法


    return 0;
}

纯虚函数和抽象类

纯虚函数

用virtual修饰,没有语句体,只有声明和(=0)的成员函数

语法:

class A{ //父类 public: virtual void show()=0;//纯虚函数 }

如果类中有纯虚函数,该类不能实例化对象,这种类叫抽象类。

抽象类的应用

抽象类的作用不是用来构造对象,而是用作父类(基类)继承产生子类

子类在继承抽象父类时,如果没有实现父类中所有的纯虚函数,那么子类仍然是一个抽象类。

如果一个类中所有的成员函数都是纯虚函数,那么该类就叫纯抽象类。(只有实现父类,才能用,不然会报错)

一个项目中的抽象类的设计属于框架设计的一部分。用来写实现

C++中类和实现的分离

C++中使用类来组织代码,类的声明写在头文件,类的声明包括成员变量的声明,成员函数的声明,成员变量的声明无需修改,类中的函数只保留声明语句。

类的函数实现写在配对的源文件中,实现类中的函数时应该指定该函数属于哪个类。

头文件(xxx.h xxx.hpp) class 类名{ 成员变量的声明; 成员函数的声明; 构造函数,析构函数,拷贝构造函数的声明; }; 源文件(xxx.cpp) 成员函数的实现; 构造函数,析构函数,拷贝构造函数的实现;

//函数参数的默认值要写到声明中(头文件) //初始化参数列表写到实现中(源文件)

练习:

将employer.cpp拆分为源文件和头文件的分离形式。

#include <cstring>
#include "employer.hpp"
​
​
Object::Object(const char *s)
{
    cout<<"Object()"<<endl;
​
    if(s){
        this->p = new char[strlen(s)+1];
        strcpy(this->p, s);
    }
    else{
        this->p = new char[10];
        memset(this->p,0,10);
    }
}
​
//拷贝构造
Object::Object(const Object &o)
{
    cout<<"Object(const Object &o)"<<endl;
    if(strlen(o.p)){
        this->p = new char[strlen(o.p)+1];
        strcpy(this->p, o.p);
    }
    else{//空串
        this->p = new char[10];
        memset(this->p,0,10);
    }
}
​
Object::~Object()
{
    cout<<"~Object()"<<endl;
    delete[] this->p;
}
​
//获取
const char *Object::get_p()
{
    return this->p;
}
​
​
//普通人类
Person::Person(const char *s,string name):Object(s),p_name(name)
{
    cout<<"Person()"<<endl;
}
​
//获取
string Person::get_pname()
{
    return this->p_name;
}
​
​
//公司类
Comp::Comp(const char *s,string name):Object(s),c_name(name)
{
    cout<<"Comp()"<<endl;
}
​
//获取
string Comp::get_cname()
{
    return this->c_name;
}
​
​
//员工类
Employer::Employer(const char *s,string pname,string cname,double salary):
    Object(s),Person(s,pname),Comp(s,cname),salary(salary)
{
    cout<<"Employer()"<<endl;
}
​
void Employer::show()
{
    cout<<this->get_pname()<<":"<<this->get_cname()<<":"<<
          this->get_p()<<":"<<this->salary<<endl;
}
​
​
/* mian  */