c++:多态性

c++三大特性:封装、继承、多态,其实这三者是相互联系的,相互包含的

1.概念

1.1编译时多态性(静态多态性)

  • 主要通过函数重载和运算符重载来实现
  • 在编译时,编译器根据函数调用的参数类型或运算符的操作数类型来确定应该调用哪个重载函数
  • 不涉及虚函数和继承

1.2运行时多态性(动态多态性)

  • 主要通过继承和虚函数来实现
  • 在运行时,程序根据对象的时间类型来确定应该调用哪个虚函数
  • 允许通过基类指针或引用来调用派生类的中的重写的虚函数

2.继承

2.1概念

派生类从基类继承,使得派生类对象可以被视为基类对象(在类型上兼容)

2.2子类继承父类

创建一个父类的指针指向子类的对象,该指针指向访问父类中继承的成员,不能访问子类自己拓展的成员

这里我们现在栈上申请一个son类型的对象,并用父类指针指向他

s.high = 90;//这里的high修改的是son类中的high
s.Father::high = 100;//这里的high修改的是father类中的high

cpp 复制代码
#include <iostream>
using namespace std;
// 父类
class Father
{
public:
    int age;     // 父类的私有成员
    double high; // 父类的公有成员
    void show();

protected:
    double fat; // 父类受保护的成员
};
// 子类
class Son : public Father
{
public:
    double high;
    void show();
};

void Son::show()
{
    cout << "Son中的high = " << high << endl;
}

void Father::show()
{
    cout << "Father中的high = " << high << endl;
}
int main()
{
    // Father *p = new Son(); // 定义了一个父类指针,指向子类对象(在堆上申请)
#if 1
    Son s;
    s.high = 90;//这里的high修改的是son类中的high
    s.Father::high = 100;//这里的high修改的是father类中的high
    Father *p = &s; // 在栈上申请一个父类指针指向子类对象
#endif
    s.show();
    cout << p->high << endl;
    p->show();
    return 0;
}

3.虚函数

3.1概念

在基类中生命虚函数,并在派生类中重写这些虚函数。虚函数表用于在运行时确定应该调用哪个函数实现。

3.2虚函数

3.2.1引入

上面说了,创建一个指向子类成员的父类指针,此时该指针只能访问父类的成员,而不能访问子类的成员

cpp 复制代码
#include <iostream>

class Father
{
public:
  void show()
    {
        std::cout << "Father show" << std::endl;
    }
};

class Son : public Father
{
public:
    void show()
    {
        std::cout << "Son show" << std::endl;
    }
};

int main()
{
    Son s1;
    Father *p = &s1;
    p->show(); 
    return 0;
}


这里访问的就是父类成员

3.2.2概念

在函数前加上virtual,只要基类中的成员是虚函数,后面所有派生类中的该函数都是虚函数
函数重写:派生类中对基类中的虚函数进行了重新定义
父类指针指向子类的对象,可以调用处重写后的虚函数
在show函数前加一个virtual关键字,就可以让父类指针访问子类中的成员

cpp 复制代码
#include <iostream>

class Father
{
public:
    virtual void show()
    {
        std::cout << "Father show" << std::endl;
    }
};

class Son : public Father
{
public:
    void show()
    {
        std::cout << "Son show" << std::endl;
    }
};

int main()
{
    Son s1;
    Father *p = &s1;
    p->show();
    return 0;
}


默认就是调用子类的中的重写函数
想调用父类中的show函数可以这样写
p->Father::show();

3.3虚析构函数

3.3.1引入

在上面2.2的例子中我们是在栈上申请的一个Son类型对象,那我们现在就在堆上申请一个Son对象,再用父类指针指向它
我们都知道堆上动态申请是要手动申请,手动释放的

cpp 复制代码
using namespace std;
// 父类
class Father
{
public:
    int age;     // 父类的私有成员
    double high; // 父类的公有成员
    void show();

protected:
    double fat; // 父类受保护的成员
};
// 子类
class Son : public Father
{
public:
    double high;
    void show();
};

void Son::show()
{
    cout << "Son中的high = " << high << endl;
}

void Father::show()
{
    cout << "Father中的high = " << high << endl;
}
int main()
{
    Father *p = new Son(); // 定义了一个父类指针,指向子类对象(在堆上申请)
    s.show();
    cout << p->high << endl;
    p->show();
    return 0;
}


看起来确实没什么问题,也出现了我们想要的结果,但我们好像忘了一件事,那就是释放这片空间,为了更加直观,我加上构造和析构时候的打印

cpp 复制代码
#include <iostream>
using namespace std;
// 父类
class Father
{
public:
    Father()
    {
        std::cout << "Father create\n";
    }
    ~Father()
    {
        std::cout << "Father destory\n";
    }

public:
    int age;     // 父类的私有成员
    double high; // 父类的公有成员
    void show();

protected:
    double fat; // 父类受保护的成员
};
// 子类
class Son : public Father
{
public:
    Son()
    {
        std::cout << "Father create\n";
    }
    ~Son()
    {
        std::cout << "Father destory\n";
    }

public:
    double high;
    void show();
};

void Son::show()
{
    cout << "Son中的high = " << high << endl;
}

void Father::show()
{
    cout << "Father中的high = " << high << endl;
}
int main()
{
    Father *f = new Father();
    delete f;
    std::cout << "--------\n";
    Son *s = new Son();
    delete s;
    std::cout << "--------\n";
    Father *p = new Son(); // 定义了一个父类指针,指向子类对象(在堆上申请)
    delete p;
    return 0;
}


相信眼尖的同学也已经发现了问题,那就是第三个, 指针p指向的son对象的资源没有释放掉,这就会导致内存泄漏
那该怎么解决呢?
这里就要引入我们所说的虚析构函数

3.3.2概念

多态的实现依赖于父类的指针指向子类的对象
父类指针的操作空间,只有父类自己的空间,所以当父类的指针被释放时,只会调用父类的析构函数,不会释放掉子类的空间,所以要使用虚析构函数(在析构函数前面加上virtual)
只要父类中是虚析构函数,子类中都是虚析构函数, 引导父类指针释放子类的空间

cpp 复制代码
#include <iostream>
using namespace std;
// 父类
class Father
{
public:
    Father()
    {
        std::cout << "Father create\n";
    }
    virtual ~Father()
    {
        std::cout << "Father destory\n";
    }

public:
    int age;     // 父类的私有成员
    double high; // 父类的公有成员
    void show();

protected:
    double fat; // 父类受保护的成员
};
// 子类
class Son : public Father
{
public:
    Son()
    {
        std::cout << "Son create\n";
    }
    ~Son()
    {
        std::cout << "Son destory\n";
    }

public:
    double high;
    void show();
};

void Son::show()
{
    cout << "Son中的high = " << high << endl;
}

void Father::show()
{
    cout << "Father中的high = " << high << endl;
}
int main()
{
    Father *f = new Father();
    delete f;
    std::cout << "--------\n";
    Son *s = new Son();
    delete s;
    std::cout << "--------\n";
    Father *p = new Son(); // 定义了一个父类指针,指向子类对象(在堆上申请)
    delete p;
#if 0
    Son s;
    s.high = 90;
    s.Father::high = 100;
    Father *p = &s; // 在栈上申请一个父类指针指向子类对象
#endif
    // p->high = 100;
    // cout << p->high << endl;
    // p->show();
    return 0;
}


现在显然就没什么问题了

相关推荐
数据小小爬虫3 分钟前
如何使用Python爬虫按关键字搜索AliExpress商品:代码示例与实践指南
开发语言·爬虫·python
Ritsu栗子9 分钟前
代码随想录算法训练营day35
c++·算法
好一点,更好一点19 分钟前
systemC示例
开发语言·c++·算法
不爱学英文的码字机器22 分钟前
[操作系统] 环境变量详解
开发语言·javascript·ecmascript
martian66526 分钟前
第17篇:python进阶:详解数据分析与处理
开发语言·python
五味香31 分钟前
Java学习,查找List最大最小值
android·java·开发语言·python·学习·golang·kotlin
时韵瑶36 分钟前
Scala语言的云计算
开发语言·后端·golang
卷卷的小趴菜学编程40 分钟前
c++之List容器的模拟实现
服务器·c语言·开发语言·数据结构·c++·算法·list
年轮不改40 分钟前
Qt基础项目篇——Qt版Word字处理软件
c++·qt
玉蜉蝣1 小时前
PAT甲级-1014 Waiting in Line
c++·算法·队列·pat甲·银行排队问题