C++——6(多态&抽象类)

一、多态相关概念

  • 多态:一个接口,多种方法,在程序运行时才决定调用哪个函数,是面向对象编程的核心概念
  • 多态性:将接口与实现进行分离,实现以共同的方法,但因个体差异不同,而采取不同的策略
  • OOP(面向对象)的主要特征:
    • 1.封装:wrap,实现了细节的隐藏,让代码模块化。类体{}的定义体现了封装
    • 2.继承:Inherrit,扩展已存在的代码,实现了代码的复用。
    • 3.多态:polymorphism,目的是为了接口的重用,不论传递的是哪个类的对象,都能通过同一接口,调用到适应各自对象的实现方法。

二、为何要用多态

  • 1.有时候希望父类和子类,有相同的方法,但行为却有所不同。

三、多态如何实现

  • 1.用虚函数实现多态性

四、虚函数

  • 1.概念:

    用关键字virtual修饰的成员函数

  • 2.虚函数的限制:

    • (1)必须是类的成员函数,不能是静态的函数
    • (2)类的构造函数不能是虚函数,但析构函数可以(虚析构函数)。
    • (3)基类函数用 virtual关键字修饰,派生类的函数不需要修饰
    • (4)基类函数被声明为 virtual虚函数,派生类中的同名函数(返回值、函数名、参数列表完全一致)自动成为虚函数
  • 3.虚函数的作用:

    • 实现多态(晚绑定,派生类对虚函数进行重写)

五、覆盖&重载&隐藏

  • 1.覆盖:override

    派生类重新定义基类虚函数,也称为重写。

    • 特点:
      • (1)作用域不同:一个在基类,一个在派生类
      • (2)函数返回值和参数列表完全一致(同名函数)。
      • (3)访问权限可以不同
      • (4)基类函数必须有virtual关键字修饰。
    // 基类:含虚函数
    class Base {
    public:
        virtual void show() {  // 虚函数
            cout << "Base::show()" << endl;
        }
    };
    
    // 派生类:覆盖基类虚函数
    class Derived : public Base {
    public:
        void show(){  // 重写(覆盖)基类虚函数
            cout << "Derived::show()" << endl;
        }
    };
    
  • 2.重载:overload

    • 特点:
      • (1)作用域必须相同。
      • (2)函数名相同,返回值不管。
      • (3)参数列表不同。
      • (4)无论有无 virtual关键字修饰。
    class Demo
    {	
        public:
        //示例函数
        int func(int a)				//作用域:Demo::
        {
            return sum += a;
        }
        //重载func 参数列表必须不同  返回值可以不同
        void func(int a , int b)		//作用域:Demo::
        {
    		sum += a + b;
        }
        private:
        int sum;        
    };
    
  • 3.隐藏(重定义):

    • 特点:
      • (1)作用域不同。
      • (2)函数名必须相同,
      • (3)返回值可以不同。
      • (4*)参数不同时:无论基类函数有无 virtual修饰,基类函数都会被派生类函数隐藏。
      • (5*)参数相同时:基类函数没有 virtual修饰,基类的函数会被派生类的函数隐藏。
    class Base {
    public:
        // 基类非虚函数(无virtual)
        void func() {
            cout << "Base::func()" << endl;
        }
        void func(int x) {  // 基类重载版本
            cout << "Base::func(int) " << x << endl;
        }
    };
    
    class Derived : public Base {
    public:
        // 派生类同名函数(参数不同),隐藏基类所有func
        void func(double x) {
            cout << "Derived::func(double) " << x << endl;
        }
        // 派生类同名函数 (参数相同)  无virtual 隐藏
        void func()
        {
            cout << "Derived::func(void)" << endl;      
        }
    };
    
特性 覆盖(override) 重载(overload) 隐藏(重定义)
作用域 基类和派生类(不同作用域) 同一作用域 基类和派生类(不同作用域)
函数名 必须相同 必须相同 必须相同
参数列表 必须完全一致 必须不同(参数个数 / 类型 / 顺序) 参数不同时:基类函数直接被隐藏; 参数相同时:需基类函数无 virtual 才会隐藏
返回值 必须完全一致 无要求(返回值可不同) 可以不同
基类函数 virtual 必须有 无要求(无论有无) 参数不同:无要求; 参数相同:基类函数没有 virtual 时,才会被隐藏
核心规则 派生类重写基类虚函数 同一作用域内同名不同参的函数重载 派生类隐藏基类同名函数; 是否隐藏与参数、virtual 修饰的组合有关
访问权限 可以不同 -(同一作用域内,按声明权限) -

六、联编

  • 1.联编:

    将程序代码需要的库或接口连接在一起

  • 2.分类:

    • 静态联编:静态编译,称为早绑定

      编译阶段将 函数的实现和使用 绑定在一起。会导致目标代码体积变大,但方便移植

    • 动态联编:动态编译,称为晚绑定

      程序运行时将函数的实现和使用 绑定在一起。目标代码体积不变,但不方便移植

七、虚函数表

C++引入虚函数表技术,来实现晚绑定,实现多态。

  • 1.虚函数表:

    存放虚函数首地址的内存区域,虚函数表占对象头8个字节的空间(64位)

对象空间---代码示例:

/*===============================================
*   文件名称:
*   创 建 者:青木莲华
*   创建日期:2025年09月02日
*   描    述:
================================================*/
#include <iostream>
using namespace std;

class Demo
{
    public:
        Demo(){}
        virtual int getA(){ 
            cout << __func__ << endl;
            return a; 
        }                           //virtual 虚函数表占8字节
        static int d;               //静态成员不占对象空间,属于类
    private:
        int a = 10;
        char b = 65;
};

int Demo::d = 20;

int main(int argc, char *argv[])
{

    cout << "Size of Demo class is " << sizeof(Demo) << endl;   //==> 16
    
    Demo obj;
    cout << *((int *)&obj + 2) << endl;         //==> a
    cout << *((char *)&obj + 12) << endl;       //==> b
#if 0
    int *out = (int *)&obj + 2;
    *out = 123;
    cout << *out << endl;
    cout << obj.getA() << endl;
#endif
    
    long long ptr = *(long long *)&obj;     //取对象头8字节(虚函数表首地址)
    long long *vtab = (long long *)ptr;     //数值转为 虚函数表首地址
    long long vfunc = *vtab;                //得到虚函数表 存放的虚函数地址的数值

    void (*func)() = (void (*)())vfunc;     //转函数指针
    func();                 // ===> getA()
    return 0; 
}

运行结果:

alt

alt

八、抽象类

  • 1.抽象类:

    含有纯虚函数的类,就是抽象类

  • 2.纯虚函数:

    //纯虚函数声明时,末尾赋值为0,没有定义
    virtual void func() = 0;
    
  • 3.抽象类的特点:

    • (1)抽象类没有完整的信息,因此不能有实例(对象)
    • (2)抽象类只能是派生类的基类,不能有static成员
    • (3)派生类应该实现抽象类的所有方法

练习

设计一个图形类,通过图形类计算三角形、圆、矩形的面积

/*===============================================
*   文件名称:4_abstract.cpp
*   创 建 者:青木莲华
*   创建日期:2025年09月02日
*   描    述:抽象类
================================================*/
#include <iostream>
using namespace std;

#define PI 3.1415926


class Graphic							//抽象类  图形
{
 	public:
        virtual ~Graphic() = 0;     //纯虚析构函数
    	virtual float getArea() = 0;	//纯虚函数
};

Graphic::~Graphic(){}   //纯虚析构函数定义

class Tranangle : public Graphic		//派生类 三角形
{
    public:
    	Tranangle(){}
    	Tranangle(int w ,int h):w(w) , h(h){}
    	float getArea()
        {
            return w*h/2;
        }
    private:
    	int w,h;
};

class Circle : public Graphic       //派生类    圆形
{
    public:
        Circle(){}
        Circle(int r):r(r){}
        float getArea()
        {
            return r*r*PI;
        }
    private:
        int r;

};

class Rectangle : public Graphic
{
    public:
        Rectangle(){}
        Rectangle(int l , int w):l(l) , w(w){}
        float getArea()
        {
            return l*w;
        }
    private:
        int l , w;
};   

int main()
{
    Graphic *gh = new Tranangle(6,8);
    cout << "Tranangle's area : " << gh->getArea() << endl;

    gh = new Circle(10);
    cout << "Circle's area : " << gh->getArea() << endl;

    gh = new Rectangle(5,6);
    cout << "Rectangle's area : " << gh->getArea() << endl;

}

九、虚析构函数

  • 1.目的:

    解决基类指针指向的派生类对象,delete基类指针的时候,导致派生类对象资源回收不完整的情况。

  • 2.在基类析构函数声明前,用 virtual修饰

纯虚析构函数

  • 1.一个抽象类的虚构函数,不清楚回收什么资源,而且也没有什么资源可以回收。为了保证解决:delete基类指针的时候,导致派生类对象资源回收不完整的情况 , 引入了纯虚析构函数

  • 2.用法声明:

    class Demo
    {
      	//声明在类体内
        virtual ~Demo() = 0;
    };
    //定义必须在类体外,不定义会报错
    Demo::~Demo(){}
    

十、限制构造

  • 1.限制构造:

    构造函数的权限为非 public 权限,不能通过该类直接定义对象,只能使用该类的特殊接口,或者通过该类派生子类来使用。

  • 2.目的:

    保护设计的类的安全性

  • 3.限制构造应用:

    C++设计模式——单例模式

    • (1)单例模式:

      当前程序中,该类的对象只会被构造一次

    • (2)分类:

      • 懒汉式:在第一次调用静态接口时,创建对象实例,线程中不安全(需用互斥锁)
      • 饿汉式:在程序运行的第一时间,就创建对象,而非调用静态接口,线程中安全,但是会造成资源浪费

示例代码:

/*===============================================
*   文件名称:6_singleton.cpp
*   创 建 者:青木莲华
*   创建日期:2025年09月02日
*   描    述:单例类
================================================*/
#include <iostream>
using namespace std;


/**
 * 懒汉式单例:
 *      1.构造函数私有化(限制构造)
 *      2.定义私有的静态的当前对象指针作为成员
 *      3.类体外对该对象指针初始化为nullptr
 *      4.定义一个公有的 静态成员函数,返回该对象指针
 */
class Singleton
{
    static Singleton *instance;     //静态指针

    Singleton(){}
    public:
        static Singleton *getInstance()
        {
            if(instance == nullptr)     //确保只初始化1次
                instance = new Singleton();
            return instance;
        }
};

//类体外定义
Singleton* Singleton::instance = nullptr;

/**
 * 饿汉式单例:
 *      1.构造函数私有化(限制构造)
 *      2.定义私有静态指针作为成员
 *      3.类体外为指针初始化为new Singleton_hungry()
 *      4.定义一个公有静态成员函数接口,返回该成员(静态指针)
 */
class Singleton_hungry
{
    static Singleton_hungry *instance_h;

    Singleton_hungry(){}

    public:
        static Singleton_hungry *getInstance_h()
        {
            return instance_h;
        }
};

Singleton_hungry* Singleton_hungry::instance_h = new Singleton_hungry();




int main(int argc, char *argv[])
{

    Singleton *s1 = Singleton::getInstance();
    Singleton *s2 = Singleton::getInstance();
    
    cout << s1 << endl;
    cout << s2 << endl;
    
    Singleton_hungry *sh1 = Singleton_hungry::getInstance_h();
    Singleton_hungry *sh2 = Singleton_hungry::getInstance_h();

    cout << sh1 << endl;
    cout << sh2 << endl;

    return 0;
}

运行截图:

alt