C++ 继承

1. 继承的概念

在这里插入图片描述
在这里插入图片描述

  • 子类可以继承基类, 基类去派生子类
  • Human 是基类,Student和Teacher是子类

2. 继承的语法

在这里插入图片描述

  • 一个子类可以继承 多个基类,下面的代码都是只有一个基类。
#include <iostream>
using namespace std;
class Human {// 人类(基类)
public:
    // 常引用才能引用右值
    Human(const string& name, int age):m_name(name), m_age(age){};
    void eat(const string& food){
        cout << "我在吃" << food << endl;
    }
    void sleep(int hour){
        cout << "我睡了" << hour << "个小时" << endl;
    }
// protected属性下,在类的外部依然不能访问, 但是子类能访问基类这些成员变量
// private属性下类的外部和子类都不能访问
protected: 
    string m_name;
    int m_age;
};

class Student:public Human{// 学生类(人类派生的一个子类)
public:
    Student(const string& name, int age, int no):Human(name, age), m_no(no){};
    void learn(const string& course){
        cout << "我在学" << course << endl;
    }
    void who(void){
        cout << "我叫"<< m_name << ",今年" << m_age << "岁,"<< "学号是 " << m_no << endl; 
    }
private:
    int m_no;
};

class Teacher:public Human{// 教师类(人类派生的一个子类)
public:
    Teacher(const string& name, int age, int salary):Human(name, age), m_salary(salary){}
    void teach(const string& course){
        cout << "我在教" << course << endl;
    }
    void who(void){
        cout << "我叫"<< m_name << ",今年" << m_age << "岁,"<< "薪水是 " << m_salary << endl; 
    }
private:
    int m_salary;
};

int main(void){
    Student s("yue han", 18, 666);
    s.who();
    s.eat("炸酱面");
    s.sleep(8);
    s.learn("c++");

    Teacher t("jie ke", 30, 20000);
    t.who();
    t.eat("油泼面");
    t.sleep(9);
    t.teach("c++");
    return 0;
}
$ ./a.out 
我叫yue han,今年18岁,学号是 666
我在吃炸酱面
我睡了8个小时
我在学c++
我叫jie ke,今年30岁,薪水是 20000
我在吃油泼面
我睡了9个小时
我在教c++

3. 公有继承的特性

3.1 任何时候都可以把一个子类对象看作是是一个基类对象

在这里插入图片描述

3.2 向上造型、向下造型 (基类在上,子类在下)

在这里插入图片描述

  • 向上造型,通俗点讲就是,一个基类指针可以指向任何一个子类对象,但是只能访问子类对象中的基类子对象。
    • 向上造型的产物:指向子类对象的基类指针,引用用子类对象的基类引用。
  • 向下造型,要注意使用的合理性。
#include <iostream>
using namespace std;
class Human {// 人类(基类)
public:
    // 常引用才能引用右值
    Human(const string& name, int age):m_name(name), m_age(age){};
    void eat(const string& food){
        cout << "我在吃" << food << endl;
    }
    void sleep(int hour){
        cout << "我睡了" << hour << "个小时" << endl;
    }
// protected属性下,在类的外部依然不能访问, 但是子类能访问基类这些成员变量
// private属性下类的外部和子类都不能访问
protected: 
    string m_name;
    int m_age;
};

class Student:public Human{// 学生类(人类派生的一个子类)
public:
    Student(const string& name, int age, int no):Human(name, age), m_no(no){};
    void learn(const string& course){
        cout << "我在学" << course << endl;
    }
    void who(void){
        cout << "我叫"<< m_name << ",今年" << m_age << "岁,"<< "学号是 " << m_no << endl; 
    }
private:
    int m_no;
};

int main(void){
    Student s("yue han", 18, 666);
    s.who();
    s.eat("炸酱面");
    s.sleep(8);
    s.learn("c++");

    // Student* ==> Human*: 向上造型
    Human* ph = &s;
    ph->eat("苹果");
    ph->sleep(8);
    // ph->who(); // 编译Error

    // Human* ==> Student: 向下造型 (合理,因为ph本来就是指向的Student类对象的指针)
    // Student* = ph; // 编译Error
    Student* ps = static_cast<Student*>(ph);
    ps->who();

    // Human* ==> Student: 向下造型 (不合理)
    // Human创建的对象, 对应在内存里的数据只有m_name, m_age, 
    Human h("li ming", 18);
    // 通过向下造型得到的Student对象,是可以访问m_no的,但h对应内存中没有m_no的数据
    Student* ps2 = static_cast<Student*>(&h);
    // 所以通过 ps2 直接访问 m_no 是有异常的
    ps2->who();
    return 0;
}
$ ./a.out 
我叫yue han,今年18岁,学号是 666
我在吃炸酱面
我睡了8个小时
我在学c++
我在吃苹果
我睡了8个小时
我叫yue han,今年18岁,学号是 666
我叫li ming,今年18岁,学号是 0

3.3 子类 继承/隐藏 基类成员

在这里插入图片描述

  • 虽然子类和基类定义同名的函数,因为作用域不同,不会构成重载关系,但是可以在子类中 通过using 基类名:函数名, 把基类当中的函数声明到当前作用域,这样就可以构成函数重载关系了。但它并不具备通用性。
  • 推荐直接用 类名:: 显示指明
#include <iostream>
using namespace std;
class Base{
public:
    void func(void){
        cout << "Base:func(void)" << endl;
    }
};

class Derived:public Base{
public:
    void func(int i){
        cout << "Derived:func(int)" << endl;
    }
    using Base::func; //但是不具备通用型,因为如果两个函数参数也一样,就没法构成重载关系
};
int main(void){
    Derived d;
    d.func(4);      // Derived:func(int)
    // 子类中 有 using Base::func; 子类中的func(int)和 基类中的func(void)才能构成函数重载
    d.func();       // Base:func(void)
    d.Base::func(); // Base:func(void)
    return 0;
}

4. 继承方式和访问控制属性

4.1 访问控制属性 (public、protected、private)

在这里插入图片描述

4.2 继承方式 (public、protected、private)

在这里插入图片描述

  • 继承方式并不影响在子类内部对基类成员 原本 的 可访问性。
  • 不同的继承方式实质上影响的是 通过 子类 访问 基类中成员时 的 可访问性
    • 1. 通过子类对象在类的外部访问基类成员
    • 2. 在子类的子类中访问基类成员
#include <iostream>
using namespace std;
class A{// 基类
public:
    int m_public;
protected:
    int m_protected;
private:
    int m_private;
};

class B:public A{// 公有继承的子类
};
class C:protected A{// 保护继承的子类
};
class D:private A{// 私有继承的子类
};

// 2. 通过子类的子类访问基类成员
class X:public B{
    void func(void){
        m_public;   // 对基类成员的访问控制属性 public
        m_protected;  // protected
        // m_private; // Error, private

    }
};

class Y:public C{
        void func(void){
        m_public;   //   protected
        m_protected;  // protected
        // m_private; // Error, private

    }
};

class Z:public D{
        void func(void){
        // m_public;     // Error, private
        // m_protected;  // Error, private
        // m_private; // Error, private

    }
};

int main(void){
    // 1. 通过子类对象在类的外部访问基类成员
    B b;
    b.m_public; // 对基类成员的访问控制属性: public
    // b.m_protected;// Error, protected
    // b.m_private; // Error, private
    C c;
    // c.m_public;  // Error, protected
    // c.m_protected;// Error, protected
    // c.m_private; // Error,  private
    D d;
    // d.m_public;  // Error,  private
    // d.m_protected;// Error,  private
    // d.m_private; // Error,   private
    return 0;
}

4.3 向上造型在 私有/保护继承中不在适用

#include <iostream>
using namespace std;
class Base{
public:
    int m_data;
};

class Derived_public:public Base{};
class Derived_private:private Base{};

int main(void){
    Derived_public d1;
    Base* pb1 = &d1;
    pb1->m_data;

    // 向上造型在 私有/保护继承中不在适用
    // 通过d2对基类成员m_data的访问控制属性应该是private, 相当于访问属性变小
    Derived_private d2;
    // 这时再向上造型,转为Base基类类型,好像又可以访问m_data了,相当于访问属性变大,这时很矛盾的,不被允许的
    // Base* pb2 = &d2; // Error
    return 0;
}

5. 子类的的构造和析构

5.1 子类的构造

在这里插入图片描述

5.2 子类的析构

在这里插入图片描述

#include <iostream>
using namespace std;
class Base{
public:
    Base(void):m_data(0){
        cout << "Base 缺省构造" << endl;
    }
    Base(int i):m_data(i){
        cout << "Base 有参构造" << endl;
    }
    ~Base(void){
        cout << "Base 析构" << endl;
    }
    int m_data;
};

class Member{
public:
    Member(void):m_i(0){
        cout << "Member 缺省构造" << endl;
    }
    Member(int i):m_i(i){
        cout << "Member 有参构造" << endl;
    }
    ~Member(void){
        cout << "Member 析构" << endl;
    }
    int m_i;
};

class Derived:public Base{
public:
    // 如果没有显示指明,将调用基类的的无参构造函数来初始化基类子对象
    Derived(void){
        cout << "Derived 缺省构造" << endl;
    }
    // Base(i) 显示指明基类子对象的初始化方式
    // m_mem(i) 显示指明成员子对象的初始化方式
    Derived(int i):Base(i), m_mem(i){
        cout << "Derived 有参构造" << endl;
    }
    ~Derived(void){
        cout << "Derived 析构" << endl;
    }
    Member m_mem;
};


int main(void){
    Derived d1;
    Derived d2(100);
    return 0;
}
$ ./a.out 
Base 缺省构造
Member 缺省构造
Derived 缺省构造
Base 有参构造
Member 有参构造
Derived 有参构造
# 析构和构造的过程对称相反
Derived 析构
Member 析构
Base 析构
Derived 析构
Member 析构
Base 析构

5.3 一个内存泄露的风险

int main(void){
    // 注意下面的代码有内存泄露风险
    // 在堆区创建一个对象(1.malloc分配内存,2.调Derived的构造函数),然后向上造型
    Base* pb = new Derived;
    // 销毁堆区的对象(1.调基类Base的析构函数(因为pb是基类指针) 2.free内存)
    delete pb;
    // 上面只是调用了基类的析构函数,只是会把基类中维护的动态资源释放掉。
    // 没有调子类和成员对象的析构函数。而子类和成员对象当中的动态资源是没有被释放的。
    // 这将造成内存泄露
    pb = NULL;
    return 0;
}
$ ./a.out 
Base 缺省构造
Member 缺省构造
Derived 缺省构造
Base 析构
  • 这个风险可以用虚析构函数来解决掉
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值