美文网首页
C++ 继承

C++ 继承

作者: WhiteStruggle | 来源:发表于2020-12-09 22:07 被阅读0次

    继承 与 派生

    继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。

    派生(Derive)

    继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。

    被继承的类称为父类或基类,继承的类称为子类或派生类。

    class 派生类名:[继承方式] 基类名{
        // 成员
    };
    
    例:
    class Student : public People { };
    

    继承方式

    继承方式包括 public(公有的)private(私有的)protected(受保护的),此项是可选的,如果不写,那么默认为 private

    1. public继承方式
    • 基类中所有 public 成员在派生类中为 public 属性;
    • 基类中所有 protected 成员在派生类中为 protected 属性;
    • 基类中所有 private 成员在派生类中不能使用。
    1. protected继承方式
    • 基类中的所有 public 成员在派生类中为 protected 属性;
    • 基类中的所有 protected 成员在派生类中为 protected 属性;
    • 基类中的所有 private 成员在派生类中不能使用。
    1. private继承方式
    • 基类中的所有 public 成员在派生类中均为 private 属性;
    • 基类中的所有 protected 成员在派生类中均为 private 属性;
    • 基类中的所有 private 成员在派生类中不能使用。

    继承方式中的 publicprotectedprivate 是用来指明基类成员在派生类中的 最高访问权限

    基类中的 protected 成员 可以在 派生类 中使用,而基类中的 private 成员 不能在 派生类 中使用

    基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,只是在派生类中不可见,导致无法使用。private 成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。

    #include <iostream>
    #include <string>
    
    using namespace std;
    // 模板类
    class People
    {
    private:
        // 私有,无论是直接还是继承 都无法访问
        string m_name;
    public:
        // 共有,私有变量接口,访问并设置私有变量
        string get_name() const { return m_name; };
        void set_name(string name) { m_name = name; };
    protected:
        // 保护,无法直接访问,可以通过 继承 被 子类 调用
        string Identity;
        string get_Identity() const { return Identity; };
        void set_Identity(string Identity) { this->Identity = Identity; };
    };
    
    class Student : public People
    {
    // 不能访问 People 类的 private 变量,但可以使用 get函数 和 set函数
    private:
        // 私有变量
        string school_num;
    public:
        // 共有,私有变量接口,访问并设置私有变量
        string get_school_num() const { return school_num; };
        void set_school_num(string school_num) { this->school_num = school_num; };
        // 成员函数
        void message()
        {
            this->set_Identity("xxx xxx xxx xxx xxx xxx");      // 访问 保护 的成员函数
            cout
                << "姓名:" << this->get_name() << endl
                << "身份证:" << this->Identity << endl
                << "学号:" << this->school_num << endl;
        }
    };
    
    int main()
    {
        Student Sir;
        Sir.set_name("马保国");
        Sir.set_school_num("2020111401");
        // Sir 不能访问 protected 的成员函数
        Sir.message();
        return 0;
    }
    
    结果:
    姓名:马保国
    身份证:xxx xxx xxx xxx xxx xxx
    学号:2020111401
    

    在派生类中访问基类 private 成员的唯一方法就是借助基类的非 private 成员函数,如果基类没有非 private 成员函数,那么该成员在派生类中将无法访问。

    改变访问权限

    使用 using 关键字可以改变基类成员在派生类中的访问权限

    修饰符:
        using 基类 : :  成员名; 
    

    注意:using 只能改变基类中 publicprotected 成员的访问权限,不能改变 private 成员的访问权限

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class People
    {
    private:
        // 私有,无论是直接还是继承 都无法访问
        string m_name;
    public:
        // 共有,私有变量接口,访问并设置私有变量
        string get_name() const { return m_name; };
        void set_name(string name) { m_name = name; };
    protected:
        // 保护,无法直接访问,可以通过 继承 被 子类 调用
        string Identity;
        string get_Identity() const { return Identity; };
        void set_Identity(string Identity) { this->Identity = Identity; };
    };
    
    // 子类
    class Student : public People
    {
    // 修改权限,不能修改 基类定义的 private 成员
    private:
        // 私有变量
        string school_num;
        // 修改权限
        using People::Identity;
    public:
        // 修改权限,基类中为protected,继承过来依旧是 protected,外部无法访问,修改为 public ,可直接通过 类的实例对象访问
        using People::set_Identity;
        using People::get_Identity;
        // 共有,私有变量接口,访问并设置私有变量
        string get_school_num() const { return school_num; };
        void set_school_num(string school_num) { this->school_num = school_num; };
        // 成员函数
        void message()
        {
            cout
                << "姓名:" << this->get_name() << endl
                << "身份证:" << this->Identity << endl
                << "学号:" << this->school_num << endl;
        }
    };
    
    int main()
    {
        Student Sir;
        Sir.set_name("马保国");
        Sir.set_Identity("xxx xxx 1951xxxx xxxx");      // 修改权限,访问受保护的函数
        Sir.set_school_num("2020111401");
        Sir.message();
        
        return 0;
    }
    
    结果:
    姓名:马保国
    身份证:xxx xxx 1951xxxx xxxx
    学号:2020111401
    

    继承时的名字遮蔽问题

    如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class People
    {
    public:
        int sum(int a, int b)
        {
            return a + b;
        }
    };
    
    // 派生类
    class Student : public People
    {
    public:
        string sum(string a, string b)
        {
            return a + b;
        }
    };
    
    void Study()
    {
        Student Sir;
        //Sir.sum(6,9); 直接 error,强制类型转换
        cout << Sir.sum("浑圆形意太极门掌门人", "马保国");
    }
    

    如果成员被遮蔽,但仍要要访问, 则就要加上类名和域解析符 来访问

    Sir.People::sum(6,9);
    

    基类成员函数和派生类成员函数不构成重载

    成员函数,不管函数的参数如何,只要名字一样就会造成遮蔽

    基类和派生类的构造函数

    类的构造函数不能被继承

    在派生类的构造函数中调用基类的构造函数,对基类的 private变量 进行 初始化

    实现方式:

    • 派生类构造函数定义 时,对 派生类成员变量 初始化,以及 基类 构造函数 初始化
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class People
    {
    protected:
        string m_name;
    public:
        // 构造函数
        People(string name) : m_name(name) { cout << this->m_name << endl; };
        // 析构函数
        ~People() {  };
    };
    
    // 子类
    class Student : public People
    {
    private:
        string m_work;
    public:
        // 构造函数
        Student(string name, string work) : m_work(work), People(name) { cout << this->m_work << endl;  };
        // 析构函数
        ~Student() {  };
    };
    
    class Pupil : public Student
    {
    private:
        int m_age;
    public:
        // 构造函数
        Pupil(string name, string work,int age) : m_age(age), Student(name,work) { cout << m_age << endl;  };
        // 析构函数
        ~Pupil() {  };
    };
    int main()
    {
        Pupil Sir("马保国", "浑圆形意太极门掌门人",69);
        
        return 0;
    }
    
    结果:
    马保国
    浑圆形意太极门掌门人
    69
    

    注意:基类构造函数的调用放在函数头部,不能放在函数体中。 因为基类构造函数不会被继承,不能当做普通的成员函数来调用。

    基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数

    C++ 当存在多级继承,A -> B -> C , 其中禁止在 C 中显式地调用 A 的构造函数。

    注意:构造参数显式的定义,系统不会再生成默认的构造函数,就必须传参,若不传参,就需要手动定义一个空的构造函数

    Student Sir;    // 错误,创建对象,系统不会创建默认的构造函数
    

    基类和派生类的析构函数

    和构造函数类似,析构函数也不能被继承

    派生类的析构函数中不用显式地调用基类的析构函数

    析构函数的执行顺序:

    • 创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。
    • 而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class People
    {
    protected:
        string m_name;
    public:
        // 构造函数
        People(string name) : m_name(name) {  };
        // 析构函数
        ~People() { cout << this->m_name << endl; };
    };
    
    // 子类
    class Student : public People
    {
    private:
        string m_work;
    public:
        // 构造函数
        Student(string name, string work) : m_work(work), People(name) {   };
        // 析构函数
        ~Student() { cout << this->m_work << endl; };
    };
    
    // 子孙类
    class Pupil : public Student
    {
    private:
        int m_age;
    public:
        // 构造函数
        Pupil(string name, string work,int age) : m_age(age), Student(name,work) {   };
        // 析构函数
        ~Pupil() { cout << m_age << endl; };
    };
    int main()
    {
        Pupil Sir("马保国", "浑圆形意太极门掌门人",69);
        return 0;
    }
    
    结果:
    69
    浑圆形意太极门掌门人
    马保国
    

    多继承(多重继承)

    派生类都只有一个基类,称为单继承(Single Inheritance)。

    一个派生类可以有两个或多个基类, 称为多继承(Multiple Inheritance)。

    多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。

    格式:

    class 子类 : 修饰符 基类1, 修饰符 基类2 ·····{
        // 成员
    }
    
    例如:
    class Student : public School , protected Family, private Person{
        // 成员
    }
    

    构造函数:

    子类(形参列表): 基类1(实参列表), 基类2(实参列表) ···{
        // 其他
    }
    
    例如:
    Student (int num, string grade, int height , int weight) : School(grade) , Family(num), Person(height, weight){
        // 其他
    }
    

    基类构造函数的调用顺序 和它们在派生类构造函数中出现的顺序无关,而是 和声明派生类时基类出现的顺序相同

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类 1
    class School
    {
    private:
        string m_grade;
    public:
    // 构造函数
        School(string grade) : m_grade(grade) { cout << "班级:" << this->m_grade << endl; };
    };
    
    // 基类 2
    class Family
    {
    private:
        int m_num;
    public:
    // 构造函数
        Family(int num) : m_num(num) { cout << "家庭人口数:" << this->m_num << endl; };
    };
    
    // 基类 3
    class Person
    {
    private:
        int m_height;
        int m_weight;
    public:
    // 构造函数
        Person(int height,int weight) : m_height(height),m_weight(weight) {
            cout 
                << "身高:" << this->m_height << endl
                << "体重:" << this->m_height << endl;
        };
    };
    
    class Student : public School, protected Family, private Person {
    private: 
        string m_name;
    public:
    // 构造函数
        Student(string name,int num, string grade, int height, int weight) : m_name(name) ,Family(num), School(grade), Person(height, weight) {
            // 其他
            cout << "姓名:" <<this->m_name << endl;
        }
    
    };
    
    int main()
    {
        Student Sir("小明",5,"软件工程",168,120);
        return 0;
    }
    
    结果:
    班级:软件工程
    家庭人口数:5
    身高:168
    体重:168
    姓名:小明
    

    例子中,继承的基类顺序 school family person

    初始化顺序 成员变量familyschoolperson

    输出结果可以清晰的看到:初始化 基类优先,基类中先继承的优先

    命名冲突

    当两个或多个基类中有同名的成员时,如果直接访问该成员,就会产生命名冲突,编译器不知道使用哪个基类的成员。

    因此需要在成员名字前面加上 类名域解析符 ::,以显式地指明到底使用哪个类的成员,消除二义性。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类 1
    class Family
    {
    protected:
        string m_name;
    public:
        Family(string name) : m_name(name) {};
    };
    
    // 基类 2
    class Person
    {
    protected:
        string m_name;
    public:
        Person(string name) : m_name(name) {};
    };
    
    // 派生类
    class Student : protected Family, private Person {
    public:
        Student(string n1,string n2) : Family(n1), Person(n2)
        {
            // cout << m_name << endl;  // error,存在二义性,不确定是哪一个
            cout << Family::m_name << endl;
        }
    };
    
    int main()
    {
        Student Sir("小强","小亮");
        
        return 0;
    }
    
    结果:
    小强
    

    虚继承和虚基类详解

    多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。

    graph LR
    A-->B
    A-->C
    B-->D
    C-->D
    

    类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,此时类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D ,另一份来自 A-->C-->D 。

    在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还会产生命名冲突。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class Family
    {
    public:
        void say() { cout << "A类" << endl; }
    };
    // 子类 1
    class Person : public Family{};
    // 子类 2
    class Student : public Family{};
    // 派生类,多继承
    class Man : public Person, public Student{};
    
    int main()
    {
        Man Sir;
        // Sir.say(); error,错误。不明确,不知道来自哪
        Sir.Student::say();
        Sir.Person::say();
        
        return 0;
    }
    
    

    虚继承

    虚继承(Virtual Inheritance)使得在派生类中只保留一份间接基类的成员。

    虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。

    virtual 关键字 代表 虚继承

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class Family
    {
    public:
        void say() { cout << "A类" << endl; }
    };
    // 子类 1
    class Person : virtual public Family{};
    // 子类 1
    class Student : virtual public Family{};
    // 派生类,多继承
    class Man : public Person, public Student{};
    
    int main()
    {
        Man Sir;
        // 均可以访问
        Sir.say();
        Sir.Student::say();
        Sir.Person::say();
        
        return 0;
    }
    

    C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。

    在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性

    假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:

    • 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
    • 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class Family
    {
    public:
        void say() { cout << "A类" << endl; }
    };
    // 子类 1
    class Person : virtual public Family{
    public:
        void say() { cout << "Person类" << endl; }
    };
    // 子类 1
    class Student : virtual public Family{};
    // 派生类,多继承
    class Man : public Person, public Student{};
    
    int main()
    {
        Man Sir;
        Sir.say();
        Sir.Student::say();
        Sir.Person::say();
        
        return 0;
    }
    
    • 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class Family
    {
    public:
        void say() { cout << "A类" << endl; }
    };
    // 子类 1
    class Person : virtual public Family{
    public:
        void say() { cout << "Person类" << endl; }
    };
    // 子类 1
    class Student : virtual public Family{
    public:
        void say() { cout << "Student类" << endl; }
    };
    // 派生类,多继承
    class Man : public Person, public Student{};
    
    int main()
    {
        Man Sir;
        //Sir.say();            error,错误,存在二义性  
        Sir.Student::say();
        Sir.Person::say();
        
        return 0;
    }
    
    

    虚继承时的构造函数

    最终派生类的构造函数必须要调用虚基类的构造函数

    虚基类是间接基类(间接继承),而不是直接基类(继承)。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class Family
    {
    private:
        int m_a;
    public:
    // 构造函数
        Family(int a) : m_a(a){
            cout << "m_a=" << m_a << ";";
        };
    };
    // 子类 1
    class Person : virtual public Family{
    private:
        int m_b;
    public:
    // 构造函数
        Person(int a, int b): m_b(b) ,Family(a) { 
            cout << "m_b=" << m_b << ";";
        };
    };
    // 子类 2
    class Student : virtual public Family{
    private:
        int m_c;
    public:
    // 构造函数
        Student(int a,int c): m_c(c), Family(a) {
            cout << "m_c=" << m_c << ";";
        };
    };
    // 派生类,多继承
    class Man : public Person, public Student{
    private:
        int m_d;
    public:
    // 构造函数
        Man(int a, int b ,int c ,int d): m_d(d),Person(a,b),Family(a),Student(a,c) {
            cout << "m_d=" << m_d << ";";
        };
    };
    
    int main()
    {
        Man Sir(1,2,3,4);
        cout << endl;
        Person p(1, 2);
        cout << endl;
        Student s(1, 2);
        cout << endl;
        
        return 0;
    }
    
    结果:
    m_a=1;m_b=2;m_c=3;m_d=4;
    m_a=1;m_b=2;
    m_a=1;m_c=2;
    

    编译器总是先调用 虚基类的构造函数 ,再按照 出现的顺序 调用 其他的构造函数

    派生类赋值给基类

    是一种 数据类型 ,可以发生 数据类型转换 ,这种转换只有在 基类派生类 之间才有意义,并且只能将派生类赋值给基类 ,包括将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,称为向上转型(Upcasting)

    将基类赋值给派生类称为 向下转型(Downcasting)

    将派生类对象赋值给基类对象

    赋值的本质是将现有的数据写入已分配好的内存中,对象的内存只包含了成员变量,所以对象之间的赋值是成员变量的赋值,成员函数不存在赋值问题。

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class People
    {
    public:
        int m_num;
    public:
        // 构造函数
        People(int num) : m_num(num) {};
        // 成员变量
        void show()
        {
            cout << "num = " << this->m_num << endl;
        }
    };
    
    // 子类
    class Student : public People {
    public:
        int m_code;
    public:
        // 构造函数
        Student(int code, int num) :m_code(code), People(num) {};
        // 成员变量
        void show()
        {
            cout << "code = " << this->m_code << ";"
                 << "num = " << this->m_num << endl;
        }
    };
    
    
    int main()
    {
        People sir_1(10);
        sir_1.show();
    
        Student sir_2(996, 777);
        sir_2.show();
    
    // 将 子类 赋值 基类
        sir_1 = sir_2;
    
        sir_1.show();
        sir_2.show();
        
        return 0;
    }
    
    结果:
    num = 10
    code = 996;num = 777
    num = 777
    code = 996;num = 777
    

    子类 是由 基类 派生而来,因此将 子类对象赋值给基类对象,便可以修改基类相关的参数就会发生改变

    image

    只能用派生类对象给基类对象赋值,而不能用基类对象给派生类对象赋值 ,因为 基类不包含派生类的成员变量,无法对派生类的成员变量赋值。同理,同一基类的不同派生类对象之间也不能赋值。

    将派生类指针赋值给基类指针

    编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数。

    int main()
    {
        People *sir_1 = new People(10);
        sir_1->show();
    
        Student* sir_2 = new Student(996, 777);
        sir_2->show();
    
    // 将 子类 赋值 基类
        sir_1 = sir_2;
    
        sir_1->show();
        sir_2->show();
        
        return 0;
    }
        
    结果:
    num = 10
    code = 996;num = 777
    num = 777
    code = 996;num = 777
    

    将派生类引用赋值给基类引用

    引用和指针的类似,是因为引用和指针本质上区别不大,引用仅仅是对指针进行了简单封装

    int main()
    {
        Student sir_2(996, 777);
        sir_2.show();
    
        People &sir_1 = sir_2;
    
        sir_1.show();
        sir_2.show();
        
        return 0;
    }
    
    结果:
    code = 996;num = 777
    num = 777
    code = 996;num = 777
    

    向上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员

    相关文章

      网友评论

          本文标题:C++ 继承

          本文链接:https://www.haomeiwen.com/subject/aepkgktx.html