C++ 继承

继承 与 派生

继承(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 
#include 

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 
#include 

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 
#include 

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 
#include 

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 
#include 

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 
#include 

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 << "姓名:" <m_name << endl;
    }

};

int main()
{
    Student Sir("小明",5,"软件工程",168,120);
    return 0;
}

结果:
班级:软件工程
家庭人口数:5
身高:168
体重:168
姓名:小明

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

初始化顺序 成员变量familyschoolperson

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

命名冲突

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

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

#include 
#include 

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 
#include 

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 
#include 

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 
#include 

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 
#include 

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 
#include 

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 
#include 

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++ 继承)