C++ 面向对象

继承

  • 继承顾名思义就是对长辈本有的东西进行获取与使用,即两个以及两个类以上的关系
  • 在获取与使用时会存在一些情况:
    • public:长辈对外公开的自身所有物,最终都会是后代的
    • protected:受保护的自身所有物,最终也是后代的,但外人不知道
    • private:长辈私人所有的隐私物,不想让任何人知道和获取,所以后代以及其他人都不可知
  • 长辈与后代必须有严格先后,长辈的下一代不能是长辈的下一代的下一代,即兄弟关系不能变成父子关系。用程序语言来说,子类继承时,不能继承和自己平级的类,如下例子 [1]:
class Shape
{
protected:
	float width_;
	float height_;
};

// 错误情况,在当前例子中,Rectangle类将会同时继承Shape和PaintCost两类
// 这样会导致无法区分所继承的成员变量是由哪一类传递来的
// class PaintCost: public Shape
// {
// };

// 正确情况
class PaintCost
{
};


class Rectangle : public Shape, public PaintCost
{
	public:
        int getArea()
        {
        	// 如果使用错误情况,此时无法明确width_和height_是继承自哪里
        	// 因为PaintCost类也继承了Shape类,所以也有width_和height_变量
            return (width_ * height_);
        }
};

重载和重写

区别

  • 重载与重写都是与类相关的技术,具体区别如下 [2]
重载重写
范围同一类不同类(基类与派生类之间,即多态概念)
声明样式函数名相同,参数不同函数名相同,参数可变,返回值相同或者协变
关键字可忽略不写基类的函数必须带有 virtual 关键字

重载

  • 同一类下,同一函数名,不同参数类型,返回值可同、可不同
  • 实例如下:
#include <iostream>

class Base
{
    public:
        void override_test(int a)
        {
            std::cout << "int_1 Base::override_test(): " << a << std::endl;
        }

        // 错误重载:参数必须不同,单独返回值不同无效
        // int  override_test(int a)
        // {
        //     std::cout << "int_2 Base::override_test(): " << a << std::endl;
        //     return a;
        // }
        
		char  override_test(char a)
        {
            std::cout << "char Base::override_test(): " << a << std::endl;
            return a;
        }

		// 正确重载
        void  override_test(float a)
        {
            std::cout << "float Base::override_test(): " << a << std::endl;
        }
};

int main()
{
    Base b;
    b.override_test(1);
    b.override_test(1.0f);
    return 0;
}

重写

  • 不同类(基类与派生类),同函数名,参数可同可不同
  • 实例如下:
#include <iostream>

class Base
{
    public:
    	// 必须带有 virtual 关键词
        virtual void test()
        {
            std::cout << "Base::test()" << std::endl;
        }

        virtual Base* getThis()
        {
            std::cout << "Base::test()" << std::endl;
            return this;
        }
        
};

class Derived : public Base
{
    public:
        virtual void test()
        {
            std::cout << "Derived::test()" << std::endl;
        }
        
        virtual void test(int a)
        {
            std::cout << "Derived::test(): int param " << a << std::endl;
        }

        // 协变: 指当类型 T 是类型 S 的子类型时,允许 T 的实例被用作 S 的实例。
        // 协变在泛型设计中较为常用
        virtual Derived* getThis()
        {
            std::cout << "Derived::getThis()" << std::endl;
            return this;
        }
};

int main()
{
    Base *pd = new Derived;
    // 假设派生类 Derived 类中不存在 virtual void test() 函数
    // 此时 pd->test()调用的是基类Base的test()函数
    // 是一种隐藏规则:覆盖
    pd->test();
    pd->test(1);
    delete pd;
    return 0;
}

虚析构函数

  • 在使用基类指针pd指向派生类对象后,在结束 delete pd 时,会有派生类析构函数无法调用的情况
  • 原因出在于指针声明的是Base基类,且基类的析构函数不是虚函数,所以不会考虑派生类的析构问题(即编译器会将析构函数直接解析为Base::~Base() ) [3] ,此时有可能出现内存泄露的情况,所以基类的析构函数最好是虚函数
  • 示例如下
#include <iostream>

class Base {
public:
    Base() { std::cout << "Base constructor" << std::endl; }
    virtual ~Base() { std::cout << "Base destructor" << std::endl; }  // 虚析构函数
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructor" << std::endl; }
    ~Derived() { std::cout << "Derived destructor" << std::endl; }  // 派生类析构函数
};

int main() {
    Base* base = new Derived();
    delete base;  // 通过基类指针删除派生类对象
    return 0;
}

虚继承

  • 使用该方式的缘由是,基类的两个派生类的派生类有相同的派生类,如下图所示:

使用虚继承的情况

  • 示例代码:
class A
{
    public:
        A() { std::cout << "A::A()" << std::endl; }
        virtual ~A() { std::cout << "A::~A()" << std::endl; }
};

class B : virtual public A	// 虚继承 A 类
{
    public:
        B() { std::cout << "B::B()" << std::endl; }
        ~B() { std::cout << "B::~B()" << std::endl; }
};

class C : virtual public A	// 虚继承 A 类
{
    public:
        C() { std::cout << "C::C()" << std::endl; }
        ~C() { std::cout << "C::~C()" << std::endl; }
};

class D : public B, public C	// 同时继承 B、C 类
{
    public:
        D() { std::cout << "D::D()" << std::endl; }
        ~D() { std::cout << "D::~D()" << std::endl; }
};


void virtual_inherit_test()
{
	// 直接类对象实例化
    D d;
    // 基类指针的派生类实例化
    A *pa = new D();
    delete pa;
}
  • 如果不使用虚继承,会出现两个问题:
    • 直接实例化D类对象时,会产生两份A类成员;
    • 使用指针实现A类指针的D类实例化,会出现基类不明确的错误。

虚函数

  • 每一个带有虚函数的类,都有对应的,有且只有一个虚函数表(类级别,不是对象级别)
  • 每一个实例化的类对象都会指向虚函数表的头指针,所以带有虚函数的类都至少占用一个指针的长度,即一个机器长度(64位为8字节,32位为4字节)

纯虚函数

  • 指无实现主体的虚函数,如下实例:
class Shape 
{
    public:
        int width_;
        int height_;

        Shape(int width, int height)
        {
            width_ = width;
            height_ = height;
        }

        virtual void area() = 0;	// 纯虚函数,无实现主体
};
  • 此时该基类也被称为抽象基类(至少包含有一个纯虚函数)
  • 该基类的派生类必须实现纯虚函数,否则不允许被实例化
class Rectangle : public Shape
{
    public:
        Rectangle(int width, int height) : Shape(width, height) {}

        void area()
        {
            std::cout << "Rectangle area: " << width_ * height_ << std::endl;
        }
};

多态

  • 以美术老师教学生画画为例:
    • 美术老师是基类,美术老师的绘画是一项技能(无具体实现方法,纯虚函数
    • 此时,他画了一条小狗(绘画技能的具体实现,虚函数),他让学生们也跟着画一条小狗
    • 学生是派生类,跟着老师老师学习绘画(继承
    • 此时,他们按照着自己的绘画方法、和自己对小狗的理解(多态),画出了不同的小狗画(重写
  • 代码实现此场景:
class ArtTeacher
{
    public:
        ArtTeacher(){}
        ArtTeacher(std::string name) { name_ = name; }
        
        virtual void drawDog()
        { std::cout << name_ << "老师 画了一条 小狗" << std::endl; }
        
    private:
        std::string name_;
};

class DrawKeJiStudents : public ArtTeacher
{
    public:
        DrawKeJiStudents(std::string name) { name_ = name; }
        
        void drawDog()
        { std::cout << name_ << " 同学 画了一条 柯基" << std::endl; }
        
    private:
        std::string name_;
};

class DrawZhongHuaTianYuanQuanStudents : public ArtTeacher
{
    public:
        DrawZhongHuaTianYuanQuanStudents(std::string name) { name_ = name; }

        void drawDog()
        { std::cout << name_ << " 同学 画了一条 中华田园犬" << std::endl; }
        
    private:
        std::string name_;
};

class DrawZangAoStudents : public ArtTeacher
{
    public:
        DrawZangAoStudents(std::string name) { name_ = name; }
        
        void drawDog()
        { std::cout << name_ << " 同学 画了一条 藏獒" << std::endl; }
        
    private:
        std::string name_;
};
  • 绘画结果展示方式有两种
    • 每位同学自己展示(直接实例化

      DrawKeJiStudents* xiao_ming = new DrawKeJiStudents("小明");
      xiao_ming->drawDog();
      delete xiao_ming;
      
    • 美术老师自己去查看每位同学的绘画(基类指针的派生类实例化

      ArtTeacher* wang_teacher = new DrawKeJiStudents("小明");
      wang_teacher->drawDog();
      delete wang_teacher;
      

参考文献

[1] https://www.w3cschool.cn/cpp/cpp-inheritance.html
[2] https://www.cnblogs.com/ybqjymy/p/16087574.html
[3] https://blog.csdn.net/weixin_45482816/article/details/107623904

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值