C++--第五课-继承

继承中的几种关系

1. is a的关系

比如说基类狗,派生了肉狗和宠物狗,我们就可以说肉狗是狗,也可以说宠物狗是狗,这就是is a的关系。但是需要注意的是,并非所有的继承都是is a的关系。

2. has a的关系

继承中的权限继承

class Base
{
public:
    int public_i_;      // 外部可访问

protected:
    int protected_i_;   // 内部可访问,外部无法访问,    可继承,子类可访问

private:
    int private_i_;     // 内部可访问,外部不可访问,    不可继承,子类不可访问
};

class A : public Base   // public继承所有的权限都没有发生改变
{
    void SetI()
    {
        protected_i_ = 100;
    }
};

class B : protected Base    // protected 继承会将基类中public的权限改为protected权限
{
    void SetI()
    {
        protected_i_ = 100;
    }
};

class C : private Base  // 将基类中的protectedpublic权限都改为private权限
{
    void SetI()
    {
        protected_i_ = 100;
    }
};
基类中有默认构造函数的继承中的构造和析构顺序
#include <iostream>

class Base
{
public:
    Base()
    {
        std::cout << "Base::Base()" << std::endl;
    }
    ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    int GetNum()
    {
        return num_;
    }

private:
    int num_;
};

class A : public Base
{
public:
    A()
    {
        std::cout << "A::A()" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }
};

int main()
{
    A demo;
    demo.GetNum();

    return 0;
}

这里写图片描述

基类中没有默认构造函数的继承中的构造和析构顺序
#include <iostream>

class Base
{
public:
    Base(int num) : num_(num)
    {
        std::cout << "Base::Base()" << std::endl;
    }
    ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    int GetNum()
    {
        return num_;
    }

private:
    int num_;
};

class A : public Base
{
public:
    A() : Base(10)  // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时,
        // 那么必须在派生类中的初始化列表中显示的来构造
    {
        std::cout << "A::A()" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }
};

int main()
{
    A demo;
    demo.GetNum();

    return 0;
}
继承中的函数,派生类中没有实现基类的函数方法
#include <iostream>

class Base
{
public:
    Base(int num) : num_(num)
    {
        std::cout << "Base::Base()" << std::endl;
    }
    ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    int GetNum() const
    {
        return num_;
    }

private:
    int num_;
};

class A : public Base
{
public:
    A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时,
        // 那么必须在派生类中的初始化列表中显示的来构造
    {
        std::cout << "A::A()" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }

    //int GetNum() const
    //{
    //  return num_;
    //}

private:
    int num_;
};

int main()
{
    A demo(100);
    demo.GetNum();
    std::cout << demo.GetNum() << std::endl;

    return 0;
}

结果是
这里写图片描述

另一种情况是,派生类中实现了基类的函数方法
#include <iostream>

class Base
{
public:
    Base(int num) : num_(num)
    {
        std::cout << "Base::Base()" << std::endl;
    }
    ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    int GetNum() const
    {
        return num_;
    }

private:
    int num_;
};

class A : public Base
{
public:
    A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时,
        // 那么必须在派生类中的初始化列表中显示的来构造
    {
        std::cout << "A::A()" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }

    int GetNum() const
    {
        return num_;
    }

private:
    int num_;
};

int main()
{
    A demo(100);
    demo.GetNum();
    std::cout << demo.GetNum() << std::endl;

    return 0;
}

输出结果是
这里写图片描述

基类指针指向派生类对象的函数调用情况
#include <iostream>

class Base
{
public:
    Base(int num) : num_(num)
    {
        std::cout << "Base::Base()" << std::endl;
    }
    ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    int GetNum() const
    {
        std::cout << "Base::GetNum()" << std::endl;
        return num_;
    }

private:
    int num_;
};

class A : public Base
{
public:
    A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时,
        // 那么必须在派生类中的初始化列表中显示的来构造
    {
        std::cout << "A::A()" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }

    int GetNum() const  // 这个函数被重写了,也就是把基类中的方法给覆盖了,基类中的方法就不存在了
    {
        std::cout << "A::GetNum()" << std::endl;
        return num_;
    }

private:
    int num_;
};

int main()
{
    Base base(100);
    A a(200);

    Base *pBase = &a;   // 因为 A is Base
    // 宠物狗 Is 狗     对   狗 泛指 宠物狗
    // 狗 Is 宠物狗     错
    std::cout << pBase->GetNum() << std::endl;  // 调用的是 A 中的 Base中的GetNum方法,但是这样很显然不是我们想要的结果
                                                // 这种用基类的指针指向派生类的对象的情况很多

    return 0;
}

输出结果是
这里写图片描述

使用虚函数在继承中函数调用的好处
#include <iostream>

class Base
{
public:
    Base(int num) : num_(num)
    {
        std::cout << "Base::Base()" << std::endl;
    }
    ~Base()
    {
        std::cout << "Base::~Base()" << std::endl;
    }

    virtual int GetNum() const
    {
        std::cout << "Base::GetNum()" << std::endl;
        return num_;
    }

private:
    int num_;
};

class A : public Base
{
public:
    A(int num) : Base(0), num_(num) // 此时会默认的调用基类的默认构造函数,如果基类中没有默认的构造函数时,
        // 那么必须在派生类中的初始化列表中显示的来构造
    {
        std::cout << "A::A()" << std::endl;
    }
    ~A()
    {
        std::cout << "A::~A()" << std::endl;
    }

    int GetNum() const  // 这个函数被重写了,也就是把基类中的方法给覆盖了,基类中的方法就不存在了
    {
        std::cout << "A::GetNum()" << std::endl;
        return num_;
    }

private:
    int num_;
};

int main()
{
    Base base(100);
    A a(200);

    Base *pBase = &a;   // 因为 A is Base
    // 宠物狗 Is 狗     对   狗 泛指 宠物狗
    // 狗 Is 宠物狗     错
    std::cout << pBase->GetNum() << std::endl;  // 调用的是 A 中的 Base中的GetNum方法,但是这样很显然不是我们想要的结果
                                                // ,使用虚函数就可以满足我们的需求,我们在基类中的GetNum前面加上virtual关键字
                                                // 这种用基类的指针指向派生类的对象的情况很多

    return 0;
}

结果是
这里写图片描述
为什么会这样呢?因为使用了虚函数,虚函数用来指明派生类中需要调用的函数,也就是说会在派生类中优先寻找要调用的函数,而不是在基类中寻找这个函数。

虚函数的其它例子
派生类继承并操作了基类中的成员变量

继承了基类中的str_变量,并对其进行了操作,导致内存泄漏,并且在析构的时候重复析构。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

class String
{
public:
    String(const char *str = "")
    {
        unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);
    }
    ~String()
    {
        delete[] str_;
    }
    String &operator+=(const String &other)
    {
        unsigned int len = strlen(str_) + strlen(other.str_);
        char *temp = new char[len + sizeof(char)];
        strcpy(temp, str_);
        strcat(temp, other.str_);
        delete[] str_;
        str_ = temp;
        return *this;
    }
    String operator+(const String &other)
    {
        String demo;
        demo += other;
        return demo;
    }

protected:
    char *str_;
};

class MyString : public String
{
public:
    MyString(const char *str = "PoEdu")
    {
        unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);
    }
    MyString operator+(const MyString &other)
    {

    }

    ~MyString()
    {
        delete[] str_;
        str_ = nullptr;
    }
};



int main()
{
    MyString str;
    //MyString other("Hello");

    return 0;
}
调用基类的构造函数来操作基类的成员变量,而不是在派生类中直接操作基类只能怪的成员变量
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

class String
{
public:
    String(const char *str = "")
    {
        unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);
    }
    ~String()
    {
        delete[] str_;
    }
    String &operator+=(const String &other)
    {
        unsigned int len = strlen(str_) + strlen(other.str_);
        char *temp = new char[len + sizeof(char)];
        strcpy(temp, str_);
        strcat(temp, other.str_);
        delete[] str_;
        str_ = temp;
        return *this;
    }
    String operator+(const String &other)
    {
        String demo;
        demo += other;
        return demo;
    }

protected:
    char *str_;
};

class MyString : public String
{
public:
    MyString(const char *str = "PoEdu") : String(str)
    {
        /*unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);*/
    }
    MyString operator+(const MyString &other)
    {

    }

    ~MyString()
    {
        delete[] str_;
        str_ = nullptr;
    }
};

int main()
{
    MyString str;
    //MyString other("Hello");

    return 0;
}

这样虽然保证了只对基类中的成员变量操作一次,但是在析构的时候还是会重复析构。

虚析构函数的使用情形

没有虚析构函数的析构情形

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class A
{
public:
    ~A()
    {
        std::cout << "~A()" << std::endl;
    }
};

class B : public A
{
public:
    ~B()
    {
        std::cout << "~B(()" << std::endl;
    }
};

int main()
{
    A *pA = new B();
    delete pA;

    return 0;
}

运行结果如下图所示
这里写图片描述
这样只是调用了基类中的析构函数,并没有调用派生类中的析构函数

有虚析构函数的析构情形

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class A
{
public:
    virtual ~A()
    {
        std::cout << "~A()" << std::endl;
    }
};

class B : public A
{
public:
    ~B()
    {
        std::cout << "~B(()" << std::endl;
    }
};

int main()
{
    A *pA = new B();
    delete pA;

    return 0;
}

运行结果如下:
这里写图片描述
这样就会把派生类中的析构函数也调用了。

正确的做法是只需要管理好本类中的成员变量即可,无需管理继承下来的成员变量(其实我觉得在实际的应用中,成员变量应该都是private的,是不需要被子类继承的,只有函数方法才需要被继承下来)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

class String
{
public:
    String(const char *str = "")
    {
        unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);
    }
    ~String()
    {
        delete[] str_;
    }
    String &operator+=(const String &other)
    {
        unsigned int len = strlen(str_) + strlen(other.str_);
        char *temp = new char[len + sizeof(char)];
        strcpy(temp, str_);
        strcat(temp, other.str_);
        delete[] str_;
        str_ = temp;
        return *this;
    }
    String operator+(const String &other)
    {
        String demo;
        demo += other;
        return demo;
    }

protected:
    char *str_;
};

class MyString : public String
{
public:
    MyString(const char *str = "PoEdu") : String(str)
    {
        length_ = new int(0);
        /*unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);*/
    }
    MyString operator+(const MyString &other)
    {

    }

    ~MyString()
    {
        //delete[] str_;
        //str_ = nullptr;   // 正确的方法是不要去管理基类中的成员变量,只需要管理本类中的成员变量即可
        delete length_; // 只需要管理本类中的length变量就可以了
    }

private:
    int *length_;
};


int main()
{
    //MyString str;
    //MyString other("Hello");

    return 0;
}

这样就保证了程序的正确运行。

虚函数的调用
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

class String
{
public:
    String(const char *str = "")
    {
        unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);
    }
    String(const String &other)
    {
        unsigned int len = strlen(other.str_);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, other.str_);
    }

    ~String()
    {
        delete[] str_;
    }
    String &operator+=(const String &other)
    {
        unsigned int len = strlen(str_) + strlen(other.str_);
        char *temp = new char[len + sizeof(char)];
        strcpy(temp, str_);
        strcat(temp, other.str_);
        delete[] str_;
        str_ = temp;
        return *this;
    }
    String operator+(const String &other)
    {
        String demo(*this);
        demo += other;
        return demo;
    }

    friend std::ostream &operator<<(std::ostream &os, const String &other)
    {
        os << other.str_;
        return os;
    }

protected:
    char *str_;
};

class MyString : public String
{
public:
    MyString(const char *str = "PoEdu") : String(str)
    {

    }
    MyString operator+(const MyString &other)
    {
        MyString temp(*this);
        temp += other;  // 因为从基类中继承了一个 += 操作符
        temp += "---------PoEdu";
        return temp;    // 此时在返回的时候会调用拷贝构造函数,如果没有实现拷贝构造函数,
                        // 那么就会出现浅拷贝的情况,导致程序运行错误,所以我们必须手动实现拷贝构造函数
    }
};


int main()
{
    MyString str("I Love ");
    String *pString = &str;
    std::cout << str + ("Mark") << std::endl;
    std::cout << *pString + ("Mark") << std::endl;

    return 0;
}

运行结果
这里写图片描述

继承中的虚析构函数

基类中的析构函数不是虚析构函数,而是普通函数的情况:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

class A
{
public:
    ~A()
    {
        std::cout << "~A()" << std::endl;
    }
};
class B : public A
{
public:
    B() : A()
    {
        demo_ = new int(0);
    }
    ~B()
    {
        std::cout << "~B()" << std::endl;
        delete demo_;
    }

private:
    int *demo_;
};

int main()
{
    A * pA = new B();
    delete pA;

    return 0;
}

运行结果是:
这里写图片描述
从结果上可以看出,在delete的时候,只是调用了基类中的析构函数,并没有调用派生类中的析构函数,那么,如何能让派生类中的析构函数也被调用呢?请看下面的情况!!!
基类中的析构函数是虚析构函数的情况:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

class A
{
public:
    virtual ~A()
    {
        std::cout << "~A()" << std::endl;
    }
};
class B : public A
{
public:
    B() : A()
    {
        demo_ = new int(0);
    }
    ~B()
    {
        std::cout << "~B()" << std::endl;
        delete demo_;
    }

private:
    int *demo_;
};

int main()
{
    A * pA = new B();
    delete pA;

    return 0;
}

运行结果:
这里写图片描述
关于继承的构造和析构顺序的总结:
一般的继承体系的类
基类构造 基类成员构造 子类构造
子类析构 基类成员析构 基类析构

继承中的虚继承

著名的菱形继承

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class A
{
public:
    int a_;
};

class B : public A
{
public:
    int b_;
};

class C : public A
{
public:
    int c_;
};

class D : public B, public C
{
    // a_ b_ c_,并且a_是两个
    // 我们称这种继承方式为菱形继承
};

int main()
{
    D d;
    d.a_;

    return 0;
}

此时在编译的时候就会出错,如下所示:
这里写图片描述
但是,我们也是可以访问的

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class A
{
public:
    int a_;
};

class B : public A
{
public:
    int b_;
};

class C : public A
{
public:
    int c_;
};

class D : public B, public C
{
    // a_ b_ c_,并且a_是两个
    // 我们称这种继承方式为菱形继承
};

int main()
{
    D d;
    d.B::a_;
    d.C::a_;

    return 0;
}

指明要访问哪个基类中的变量,就不会出现编译错误了。
这样虽然解决了编译错误的问题,但是我们不需要这样的情况,我们只需要一份变量就可以了,那么这样该怎么解决呢?我们可以通过虚继承来解决这个问题!!!

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class A
{
public:
    int a_;
};

class B : virtual public A
{
public:
    int b_;
};

class C : virtual public A
{
public:
    int c_;
};

class D : virtual public B, virtual public C
{
    // a_ b_ c_,并且a_是两个
    // 我们称这种继承方式为菱形继承
};

int main()
{
    D d;
    d.a_;

    return 0;
}

此时编译也就可以通过了。

继承中的纯虚函数
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class Animal
{
public:
    virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现
};

class Dog : public Animal
{
public:

};

int main()
{
    Dog dog;    // 此时编译不能通过,因为没有实现基类中的纯虚函数

    return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>


class Animal
{
public:
    virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现
};

class Dog : public Animal
{
public:
    void Cry()
    {

    }
};

int main()
{
    Dog dog;    // 当实现了基类中的纯虚函数后,就能实例化对象了


    return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

// 纯虚函数 不能实例化
class Animal
{
public:
    virtual void Cry() = 0; // 纯虚函数,无需实现,需要子类来实现(强制性的进行实现)
};

class Dog : public Animal
{
public:
    void Cry()
    {

    }
};

class A : public Dog
{

};

int main()
{
    Dog dog;    // 当实现了基类中的纯虚函数后,就能实例化对象了
                // 只要有纯虚函数的类,我们称之为抽象类,抽象类是无法实例化的
    A a;        // 此时A类就可以实例化,因为Dog类中已经实现了纯虚函数

    return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

// 纯虚函数 不能实例化 是其它类的基类 经常用来作为接口使用 接口是用来进行解耦的
// 接口
// 没有任何的数据成员,只是把函数名列出来
class Animal
{
public:
    virtual void Cry() = 0;     // 纯虚函数,无需实现,需要子类来实现(强制性的进行实现)
    virtual void Jump() = 0;    // 跳的接口
};

class Dog : public Animal
{
public:
    void Cry()
    {

    }
};

class A : public Dog
{

};

int main()
{
    Dog *dog = new A(); // 用来指向子类
    dog->Cry();

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值