C++基础12---基类指针、虚函数、多态性、纯虚函数、虚析构

1. 基类指针

在创建对象时,可以直接使用[类名] [对象名],也可以使用new关键字创建对象,即 [类名] 对象指针=new [类名]。其中前者对象创建在栈上,后者对象创建在堆上。

#include <iostream>
using namespace std;

class Animal
{

};

int main() {
    Animal an1;  //对象创建在栈上
    Animal *an2 = new Animal;//对象创建在堆上
}

2. 虚函数

2.1 父类指针指向子类对象

父类指针可以指向子类对象,而子类指针无法指向父类对象。若此时,父类和子类中存在同名的成员函数,父类指针调用的是父类的成员函数。

#include <iostream>
using namespace std;

class Animal
{
public:
    void eat()
    {
        cout << "动物吃各种食物" << endl;
    }
};

class Dog : public Animal
{
public:
    void eat()
    {
        cout << "狗吃骨头" << endl;
    }
};
int main() {
    Animal *an1 = new Dog;   //父类指针指向子类对象
//    Dog wangcai = new Animal; //错误,子类指针指向父类对象
    an1->eat(); //动物吃各种食物,执行的是父类成员函数
}
2.2 虚函数——使父类指针可以调用子类的同名成员函数

(1)虚函数的条件:
父类的成员函数声明前加上virtual关键字,则该成员函数被声明为虚函数。
(2)一旦某个函数在父类中被声明为虚函数,那么在所有子类中它都是虚函数。
(3)子类的虚函数声明前可以不用加virtual。
(4)调用哪个类的成员函数与你new的类对象有关。

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void eat()
    {
        cout << "动物吃各种食物" << endl;
    }
};

class Dog : public Animal
{
public:
    void eat()
    {
        cout << "狗吃骨头" << endl;
    }
};

class Cat : public Animal
{
public:
    void eat()
    {
        cout << "猫吃鱼" << endl;
    }
};

int main() {
    Animal *an1 = new Animal; //父类指针指向父类对象
    Animal *an2 = new Dog; //父类指针指向子类(Dog)对象
    Animal *an3 = new Cat; //父类指针指向子类(Cat)对象
    an1->eat(); //动物吃各种食物,调用父类的虚函数
    an2->eat(); //狗吃骨头, 调用子类Dog的虚函数
    an3->eat(); //猫吃鱼,调用子类Cat的虚函数
}
2.3 override与final关键字

1. override
为了避免在子类中写错虚函数,在C++11中,可以在虚函数声明中加上override关键字,此时,编译器会在父类中寻找与子类同名的虚函数,并用子类的虚函数将其覆盖,若在父类中没有找到同名的虚函数则报错。
(1)override主要是为了防止程序员在子类中写错虚函数名,此时会自动报错。
(2)override关键字用在子类中,且虚函数专用

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual int eat(int) 
    {
        cout << "动物吃各种食物" << endl;
    }
};

class Dog : public Animal
{
public:
    virtual void eat() override
    {
        cout << "狗吃骨头" << endl;
    }
};

class Cat : public Animal
{
public:
    virtual void eat() override
    {
        cout << "猫吃鱼" << endl;
    }
};

int main() {
    Animal *an1 = new Animal; //父类指针指向父类对象
    Animal *an2 = new Dog; //父类指针指向子类(Dog)对象
    Animal *an3 = new Cat; //父类指针指向子类(Cat)对象
    an1->eat(); //报错,父类中不存在与子类同名的成员函数供子类覆盖
    an2->eat(); //报错,父类中不存在与子类同名的成员函数供子类覆盖
    an3->eat(); //报错,父类中不存在与子类同名的成员函数供子类覆盖
}

2. final
final也是虚函数专用,但其用于父类,如果我们在父类的成员函数中增加了final,则任何尝试覆盖该函数的操作都将引发错误。

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void eat() final
    {
        cout << "动物吃各种食物" << endl;
    }

    virtual void eat(int)
    {
        cout << "动物吃各种食物" << endl;
    }
};

class Dog : public Animal
{
public:
    virtual void eat() override  //报错,父类的虚函数含final,不可被覆盖
    {
        cout << "狗吃骨头" << endl;
    }
    
    virtual void eat(int) override  //父类的虚函数没有final,可以被覆盖
    {
        cout << "狗吃骨头" << endl;
    }
};
2.4 动态绑定

调用虚函数执行的是动态绑定,即只有程序运行时,才知道调用了哪个子类的虚函数,动态绑定到Dog还是Cat,取决于new的是Dog还是Cat。

3. 多态性

多态性是针对虚函数来说的,当父类指针指向子类对象,系统内部通过查虚函数表来确定执行哪个类的虚函数,这就是运行时期的多态性。

4. 纯虚函数

(1)纯虚函数是在基类中声明的虚函数,但是其在基类中没有定义。
(2)纯虚函数要求任何子类都要定义该虚函数自己的实现方法。
(3)一旦一个类中有纯虚函数,则这个类变成了“抽象类”,不可以生成该类的对象。
(4)抽象类不用来生成对象,主要目的用于统一管理子类对象。

#include <iostream>
using namespace std;

class Animal //抽象类
{
public:
    virtual void eat() = 0;   //纯虚函数
};

class Dog : public Animal
{
public:
    virtual void eat();    //错误, 子类中需要实现父类的纯虚函数
};

class Cat : public Animal
{
public:
    virtual void eat() override   // 子类中实现父类的纯虚函数
    {
        cout << "猫吃鱼" << endl;
    }
};

int main() {
//    Animal *an1 = new Animal; //错误,父类Animal中含有纯虚函数,变为了抽象类,不可用来生成对象。
//    Animal an1; //错误,父类Animal中含有纯虚函数,变为了抽象类,不可用来生成对象。
//    Animal *an2 = new Dog; //错误,子类中没有实现父类的纯虚函数。
    Animal *an3 = new Cat;
//    an2->eat(); //狗吃骨头
    an3->eat(); //猫吃鱼
}

5. 虚析构

(1)在存在虚函数时,delete父类指针只析构了父类的对象而子类对象没有被析构,会造成内存泄漏。

#include <iostream>
using namespace std;

class Animal //抽象类
{
public:
    Animal()
    {
        cout << "执行Animal()"<< endl;
    }
    ~Animal()
    {
        cout << "执行~Animal()" << endl;
    }
};

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

int main() {
    Animal *an = new Dog; //执行Animal(), 执行Dog()
    delete an; //执行~Animal(), 此时只析构了父类的对象,子类未被析构
}

(2)为了在delete父类指针时能够同时调用父类的析构函数与子类的析构函数,需要把父类的析构函数声明为virtual。

#include <iostream>
using namespace std;

class Animal //抽象类
{
public:
    Animal()
    {
        cout << "执行Animal()"<< endl;
    }
    virtual ~Animal()  //父类中声明为虚析构函数
    {
        cout << "执行~Animal()" << endl;
    }
};

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

int main() {
    Animal *an = new Dog; //执行Animal(), 执行Dog()
    delete an; //执行~Dog(),执行~Animal(), 
}

总结:
(1)如果一个类,想要做基类,我们务必要把该类的析构函数写成virtual析构函数,此时delete基类指针时,也会执行子类的析构函数。
(2)虚函数会增加内存开销,类里面定义虚函数,编译器会给这个类增加虚函数表,在这个表里存放虚函数指针。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值