PoEdu培训 C++班 第十四课 继承(1)
文章类别: 培训笔记 0 评论

PoEdu培训 C++班 第十四课 继承(1)

文章类别: 培训笔记 0 评论

继承(1)

派生类 从 基类 中继承一些东西.

特性

继承的语法

class Base
{
private:
    int private_i_;
protected:
    int protected_i_;
public:
    int public_i_;
}

class A : Base
{

}

int main()
{
    A a1;
    a1.public_i_ = 1;   // 可以

    return 0;
}

如上代码, 并没有指定继承方式, 类的继承默认是_私有private_的!
另外, 结构体如果没有指定继承方式, 它的继承默认是_public_的!

Has-A关系

其实就是 在 class A 中, 有class B的对象.
也就是 A has B.

这其实就是 组合.

Is-A关系

其实就是在 class A 和 class B中存在相同的功能或属性
而我们将这些相同的功能或属性提出出来, 然后子类化
在子类中, 可以对这些相同属性或功能进行强化等等.

这就是 继承.

狼狗, 宠物狗, 都继承自狗
不管怎么样, 都是狗, 这就是 Is A关系

class Base
{
}

class A : Base
{
}

int main()
{
    Base base(100);
    A a(200);
    Base* pBase = &a;   // 可以, 因为 A Is Base
    A* pA = &base;  // 错误的

    return 0;
}

继承访问控制符

继承访问控制符出现在基类的前面.

class Base
{

};

class A : public Base
{

};

Private继承

class Base
{
private:
    int private_i_;
protected:
    int protected_i_;
public:
    int public_i_;
}

class A : private Base
{

}

int main()
{
    A a1;
    a1.public_i_ = 1;   // 可以

    return 0;
}

会修改基类中的public和protected属性改变为private属性, 外部无法访问, 内部可访问

Protect继承

class Base
{
private:
    int private_i_;
protected:
    int protected_i_;
public:
    int public_i_;
}

class A : protected Base
{

}

int main()
{
    A a1;
    a1.public_i_ = 1;   // 不可以

    return 0;
}

会修改基类中的public属性改变为protected属性, 外部无法访问, 内部可访问

Public继承

class Base
{
private:
    int private_i_;
protected:
    int protected_i_;
public:
    int public_i_;
}

class A : public Base
{

}

int main()
{
    A a1;
    a1.public_i_ = 1;   // 可以

    return 0;
}

基类中的属性的访问控制权限没有任何修改

using关键字

使用_using_关键字可以改变基类成员变量或成员函数在派生类中的访问权限
例如将 public 改为 private, 或者将 protected 改为 private
但是, 无法将 private 改为 public 或者 protected, 因为 private 成员不可访问

#include<iostream>
using namespace std;

class People
{
public:
    void Show()
    {
        cout << name_ << "的年龄是" << age_ << endl;
    }
protected:
    char* name_;
    int age_;
};

class Student: public People
{
public:
    void Learning()
    {
        cout << "我是" << name_ << ",今年" << age_ << "岁,这次考了" << score_ << "分!" << endl;
    }
public:
    using People::name_;  //将protect改为public
    float score_;
private:
    using People::age_;  //将protect改为private
    using People::Show;  //将public改为private
};

int main()
{
    Student stu;
    stu.name_ = "Hades";
    stu.age_ = 18;
    stu.score_ = 233.333f;
    stu.Show();  //报错 因为改成private了
    stu.Learning();

    return 0;
}

继承中的构造/析构函数

#include <iostream>
using namespace std;

class Base
{
private:
    int num_;
public:
    Base()
    {
        cout << "Base Ctor!" << endl;
    }
    ~Base()
    {
        cout << "Base Dtor!" << endl;
    }
    int GetNum() const
    {
        return num_;
    }
}

class A : public Base
{
public:
    A()
    {
        cout << "A Ctor!" << endl;
    }
    ~A()
    {
        cout << "A Dtor!" << endl;
    }
}

int main()
{
    A a1;
    // A中并没有num_, num_在基类中.
    a1.GetNum();    // 可以

    return 0;
}

派生类构造函数会先调用基类构造函数, 然后在调用自己的构造函数.
派生类构造函数会默认调用_基类默认的构造函数_
派生类析构函数调用完成后, 然后在调用基类析构函数.
派生类和基类现在是 IS A 关系.

#include <iostream>
using namespace std;

class Base
{
private:
    int num_;
public:
    Base(int num):num_(num)
    {
        cout << "Base Ctor!" << endl;
    }
    ~Base()
    {
        cout << "Base Dtor!" << endl;
    }
    int GetNum() const
    {
        return num_;
    }
}

class A : public Base
{
public:
    A()
    {
        cout << "A Ctor!" << endl;
    }
    ~A()
    {
        cout << "A Dtor!" << endl;
    }
}

int main()
{
    A a1; // 报错, 没有找到基类的默认构造函数

    return 0;
}

想要解决这个错误, 需要在派生类的构造函数中显示调用基类构造函数
修改如下:

    A():Base(0)
    {
        cout << "A Ctor!" << endl;
    }

我们在修改一下:

#include <iostream>
using namespace std;

class Base
{
private:
    int num_;
public:
    Base(int num):num_(num)
    {
        cout << "Base Ctor!" << endl;
    }
    ~Base()
    {
        cout << "Base Dtor!" << endl;
    }
    int GetNum() const
    {
        return num_;
    }
}

class A : public Base
{
private:
    int num_;
public:
    A(int num):Base(0), num_(num)
    {
        cout << "A Ctor!" << endl;
    }
    ~A()
    {
        cout << "A Dtor!" << endl;
    }
}

int main()
{
    A a1(100);
    cout << a1.GetNum() << endl;    // 结果是 0
    return 0;
}

如果我们在A中实现一个GetNum(), 改造如下:

class A : public Base
{
private:
    int num_;
public:
    A(int num):Base(0), num_(num)
    {
        cout << "A Ctor!" << endl;
    }
    ~A()
    {
        cout << "A Dtor!" << endl;
    }
    int GetNum() const
    {
        return num_;
    }
}

//....
    cout << a1.GetNum() << endl;    // 结果是 100
// ...

此时, 我们说派生类将基类中的方法_隐藏_了
而隐藏则指的是:

注意:
重写(覆盖)形成的条件:

重写(覆盖)后, 基类的方法被覆盖掉, 基类的方法就不存在了

根据以上例子中的类, 我们继续写如下代码:

int main()
{
    A a(200);
    Base* pBase = &a; // OK, 因为Is A关系
    cout << pBase->GetNum() << endl; // 结果是 0
    // 它其实调用的是a中的Base对象的GetNum方法, 
    // 调用的是基类的方法.
    // 结果是a中Base对象的num_的值

    return 0;
}

为了解决这种不正确的调用(我们希望输出200), 所以我们使用虚函数.

虚函数

父类实现虚函数, 派生类重写了之后, 就会调用派生类的函数
如果派生类没写, 会调用基类的

虚函数语法:
virtual int GetNum() const{}

根据上边的例子, 我们在来看:

int main()
{
    A a(200);
    Base* pBase = &a; // OK, 因为Is A关系
    // 现在, 我们将Base中的GetNum变成虚函数
    cout << pBase->GetNum() << endl; // 结果是 200

    return 0;
}

派生类和基类的成员内存管理

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(const String& other)
    {
        if (this != &other)
        {
            unsigned int len = strlen(str);
            str_ = new char[len + sizeof(char)];
            strcpy(str_, str);
        }
        return *this;
    }
    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;
        trrurn *this;
    }
    String& operator+(const String& other)
    {
        String tmp(str_);
        tmp += other;
        return tmp;
    }
    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")
    {
        unsigned int len = strlen(str);
        str_ = new char[len + sizeof(char)];
        strcpy(str_, str);
    }
    ~MyString()
    {
        delete[] str_;
        str_ = nullptr;
    }
    MyString operator+(const MyString& other)
    {
        MyString temp(str_);
        temp += other; // 继承了基类的+=
        temp += "-----PoEdu";
        return temp;
    }
};

int main()
{
    MyString str;

    return 0;
}

当前我们的程序代码下,

我们应该避免在派生类中管理基类的成员变量的内存
父类中继承下来的东西`不要在子类中进行显式`的操作.
父类的new让父类的析构来进行delete
由谁定义的变量由谁来管理

如何修改呢?
针对MyString改造如下:

class MyString : public String
{
public:
    MyString(const char* str = "PoEdu") : Base(str)
    {
    }
    ~MyString()
    {
    }
};

我们在来看:

int main()
{
    MyString str("Hello Hades");
    String *pStr = &str;
    cout << str + ("OOO") << endl;  // 这样会输出我们的 -----PoEdu
    cout << *pStr << endl;          // 这样却不会输出 -----PoEdu

    return 0;
}

这是因为, pStr调用的是父类的operator+, 而不是派生类的operator+
所以我们把父类的operator+做成虚函数, 改造如下:

virtual String& operator+(const String& other)
// .....

经过改造后, 输出就正确了!

虚析构函数

class A
{
public:
    virtual ~A() {}
}

class B : public A
{
public:
    B()
    {
        demo_ = new int(0);
    }
    ~B()
    {
        delete demo_;
    }

private:
    int *demo_;
}


int main()
{
    B(); // 肯定会调用B的析构
    A* pA = new B;
    delete *pA; // 会调用A的析构 如果A的析构函数不是虚的, 那么B的析构不会被调用
    // 需要把A的析构做成虚函数, 才会调用B的析构函数 

    return 0;
}

当基类的指针指向派生类时, 在析构的时候, 只会调用基类的析构函数
我们将基类的析构函数做成_虚析构函数_后, 在使基类的指针指向派生类
在析构的时候, 才会调用_派生类和基类_的析构函数

当派生类没有写析构函数时, 它_会调用基类的析构函数_

构造和析构的执行顺序

继承体系的类的调用方法

虚继承

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_ a_ b_ c_
    
}

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

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

class E : virtual public BV, virtual public CV
{
    // 现在有 a_ b_ c_
    
}

int main()
{
    D d;
    d.a_ = 1;// 访问不明确
    d.B::a_ = 1;
    d.C::a_ = 2;

    // 要解决这种问题 使用虚继承
    E e;
    e.a_ = 100; // OK

    return 0;
}

尽量避免菱形继承的使用

纯虚函数

我们不对它来进行实现的函数, 叫纯虚函数.

class Animal
{
public:
    // 纯虚函数 无需实现 需要派生类来实现(强制性的)
    // 基类可以实现该函数, 但是无意义
    virtual void Say() = 0;
}

class Dog : public Animal
{
public:
    // 必须实现这个函数
    // 否则无法实例化
    // void Say()
    // {}
    // 它只要不新建对象 就可以不实现纯虚函数
    // 可以交由Dog的派生类实现
}

class A : public Dog
{
public:
    void Say()
    {}
}

int main()
{
    Dog dog(); // 错误的 不能实例化抽象类
    A a(); //可以
    Dog* dog = new A(); // 可以
    dog->Say(); // 不管什么狗 都要叫出来
    return 0;
}

但凡_有纯虚函数_的类, 我们称之为_抽象类_.
抽象类_不能够被创建对象_, 它是其他类的基类
它可以作为接口来用(接口是用来解耦的)

接口

接口是在设计的时候进行统一调配, 它是一种抽象.

如有错误,请提出指正!谢谢.

回复