11.继承和派生

目录介绍
  • 11.1 继承基础概念
    • 11.1.1 继承基本概念
    • 11.1.2 继承的语法
  • 11.2 继承的类型
    • 11.2.1 公有继承
    • 11.2.2 保护继承
    • 11.2.3 私有继承
    • 11.2.4 比较和总结
  • 11.3 继承中场景
    • 11.3.1 继承中对象模型
    • 11.3.2 继承中构造和析构顺序
    • 11.3.3 继承同名成员处理方式
    • 11.3.4 继承同名静态成员处理
  • 11.4 多重继承
    • 11.4.1 多重继承定义
    • 11.4.2 多重继承二义性
    • 11.4.3 多重继承的构造顺序
  • 11.5 其他继承
    • 11.5.1 菱形继承
    • 11.5.2 虚继承
  • 11.6 继承综合案例
    • 11.6.1 实现网页中内容

11.1 继承基础概念

11.1.1 继承基本概念

继承 是面向对象编程(OOP)的核心特性之一,它允许一个类(派生类)基于另一个类(基类)创建,从而复用基类的成员并扩展其功能。

  • 基类(父类):被继承的类。
  • 派生类(子类):继承基类的类。

派生类可以访问基类的成员(根据访问权限),并可以添加新的成员或重写基类的成员函数。

11.1.2 继承的语法

class 派生类名 : 访问修饰符 基类名 {
    // 派生类的成员
};

访问修饰符:可以是 publicprotectedprivate,决定基类成员在派生类中的访问权限。

11.2 继承的类型

继承方式一共有三种:

  1. 公共继承。使用public,子类不能访问父类私有属性,子类对象只能访问到公共权限父类属性
  2. 保护继承。使用protected,子类不能访问父类私有属性,子类对象不可访问所有父类属性
  3. 私有继承。使用private,这种很少用

继承后可访问性,继承后的可访问性是指派生类(子类)对基类(父类)成员的访问权限(public,protected,private)。

11.2.1 公有继承

  • 基类的 public 成员在派生类中仍然是 public
  • 基类的 protected 成员在派生类中仍然是 protected
  • 基类的 private 成员在派生类中不可访问。
#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    int a;
protected:
    int b;
private:
    int c;
public:
    void eat() {
        cout << "Animal is eating." << endl;
    }
};

// 派生类
class Dog : public Animal {
public:
    void bark() {
        a; //可访问 public权限
        b; //可访问 protected权限
        //c; //不可访问
        cout << "Dog is barking." << endl;
    }
};

int main() {
    Dog dog;
    dog.eat();  // 调用基类的成员函数
    dog.bark(); // 调用派生类的成员函数
    dog.a;  //其他类只能访问到公共权限
    return 0;
}

11.2.2 保护继承

在保护继承中,基类的 publicprotected 成员 在派生类中都变为 protected 成员,而基类的 private 成员 在派生类中仍然是不可访问的。

#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    int a;
protected:
    int b;
private:
    int c;
protected:
    void eat() {
        cout << "Animal is eating." << endl;
    }
};

// 派生类
class Dog : protected Animal {
public:
    void bark() {
        a; //可访问 protected权限
        b; //可访问 protected权限
        //c; //不可访问
        eat(); // 可以访问基类的 protected 成员
        cout << "Dog is barking." << endl;
    }
};

int main() {
    Dog dog;
    dog.bark(); // 调用派生类的成员函数
    // dog.eat(); // 错误:eat() 在派生类中是 protected,外部不可访问
    // dog.a; //不可访问
    return 0;
}

输出

Animal is eating.
Dog is barking.

保护继承通常用于以下场景:

  1. 限制基类成员的访问权限: 当希望基类的 public 成员在派生类中变为 protected,以避免外部直接访问时。
  2. 实现中间层类: 在多层继承中,保护继承可以用于实现中间层类,限制某些成员的访问权限。
  3. 设计类库或框架: 在设计类库或框架时,保护继承可以隐藏基类的实现细节,只允许派生类访问。

11.2.3 私有继承

  • 基类的 publicprotected 成员在派生类中都变为 private
  • 基类的 private 成员在派生类中不可访问。
#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    void eat() {
        cout << "Animal is eating." << endl;
    }
};

// 派生类
class Dog : private Animal {
public:
    void bark() {
        eat(); // 可以访问基类的 public 成员
        cout << "Dog is barking." << endl;
    }
};

int main() {
    Dog dog;
    dog.bark(); // 调用派生类的成员函数
    // dog.eat(); // 错误:eat() 在派生类中是 private,外部不可访问
    return 0;
}

输出

Animal is eating.
Dog is barking.

11.2.4 比较和总结

继承方式public 成员在派生类中protected 成员在派生类中private 成员在派生类中
公有继承publicprotected不可访问
保护继承protectedprotected不可访问
私有继承privateprivate不可访问

总结

  • 保护继承将基类的 publicprotected 成员在派生类中变为 protected 成员。
  • 保护继承适用于需要限制基类成员访问权限的场景。
  • 在实际开发中,保护继承的使用较少,通常优先考虑公有继承或组合(Composition)。

11.3 继承中场景

11.3.1 继承中对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

class Base {
public:
    int a;
protected:
    int b;
private:
    int c; //私有成员只是被隐藏了,但是还是会继承下去
};

//公共继承
class Son : public Base {
public:
    int d;
};

void test01() {
    cout << "sizeof int = " << sizeof(int) << endl;
    cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {
    test01();
    return 0;
}

然后打印一下结果,如下:

sizeof int = 4
sizeof Son = 16

由结果可知:单继承中,派生类对象包含基类部分和派生类部分。

打开命令工具窗口后,定位到当前CPP文件的盘符

然后输入: cl /d1 reportSingleClassLayout查看的类名 所属文件名

11.3.2 继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

class Base {
public:
    Base() {
        cout << "Base构造函数!" << endl;
    }
    ~Base() {
        cout << "Base析构函数!" << endl;
    }
};

class Son : public Base {
public:
    Son() {
        cout << "Son构造函数!" << endl;
    }
    ~Son() {
        cout << "Son析构函数!" << endl;
    }
};

void test01() {
    //继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
    Son s;
}

int main() {
    test01();
    return 0;
}

打印结果如下所示:

Base构造函数!
Son构造函数!
Son析构函数!
Base析构函数!

总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

11.3.3 继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
class Base {
public:
    Base() {
        a = 100;
    }
    void func() {
        cout << "Base - func()调用" << a << endl;
    }
    void func(int a) {
        cout << "Base - func(int a)调用" << a << endl;
    }
public:
    int a;
};


class Son : public Base {
public:
    Son() {
        a = 200;
    }
    //当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
    //如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
    void func() {
        cout << "Son - func()调用" << a << endl;
    }

public:
    int a;
};

void test01() {
    Son s;
    cout << "Son下的a = " << s.a << endl;
    cout << "Base下的a = " << s.Base::a << endl;
    s.func();
    s.Base::func();
    s.Base::func(10);
}

int main() {
    test01();
    return EXIT_SUCCESS;
}

打印结果如下所示:

Son下的a = 200
Base下的a = 100
Son - func()调用200
Base - func()调用100
Base - func(int a)调用10

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

11.3.4 继承同名静态成员处理

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

  1. 访问子类同名成员 直接访问即可
  2. 访问父类同名成员 需要加作用域
class Base {
public:
    static void func() {
        cout << "Base - static void func()" << a << endl;
    }
    static void func(int a) {
        cout << "Base - static void func(int a)" << a << endl;
    }
    static int a;
};

int Base::a = 100;

class Son : public Base {
public:
    static void func() {
        cout << "Son - static void func()" << a << endl;
    }
    static int a;
};

int Son::a = 200;

//同名成员属性
void test01() {
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    cout << "Son  下 a = " << s.a << endl;
    cout << "Base 下 a = " << s.Base::a << endl;

    //通过类名访问
    cout << "通过类名访问: " << endl;
    cout << "Son  下 a = " << Son::a << endl;
    cout << "Base 下 a = " << Son::Base::a << endl;
}

//同名成员函数
void test02() {
    //通过对象访问
    cout << "通过对象访问: " << endl;
    Son s;
    s.func();
    s.Base::func();

    cout << "通过类名访问: " << endl;
    Son::func();
    Son::Base::func();
    //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    Son::Base::func(100);
}

int main() {
    //test01();
    test02();
    return 0;
}

11.4 多重继承

11.4.1 多重继承定义

多重继承定义,多继承即一个子类可以有多个父类,它继承了多个父类的特性。C++允许一个类继承多个类

语法: class 子类 :继承方式 父类1 , 继承方式 父类2...

多继承可能会引发父类中有同名成员出现,需要加作用域区分。

C++实际开发中不建议用多继承

#include <iostream>
using namespace std;

// 基类 1
class Animal {
public:
    void eat() {
        cout << "Animal is eating." << endl;
    }
};

// 基类 2
class Mammal {
public:
    void breathe() {
        cout << "Mammal is breathing." << endl;
    }
};

// 派生类
class Dog : public Animal, public Mammal {
public:
    void bark() {
        cout << "Dog is barking." << endl;
    }
};

int main() {
    Dog dog;
    dog.eat();    // 调用基类 Animal 的成员函数
    dog.breathe(); // 调用基类 Mammal 的成员函数
    dog.bark();   // 调用派生类的成员函数
    return 0;
}

输出

Animal is eating.
Mammal is breathing.
Dog is barking.

11.4.2 多重继承二义性

在C++的多重继承中,当派生类从多个基类中继承相同的成员函数或成员变量时,可能会导致二义性问题。这种情况下,编译器无法确定应该使用哪个基类的成员,从而导致编译错误。

二义性问题主要有两种情况:

  1. 成员函数二义性:当派生类从多个基类中继承了相同的成员函数时,如果派生类直接调用该成员函数,编译器无法确定应该使用哪个基类的成员函数。这种情况下,可以使用作用域解析运算符(::)来指定要调用的基类成员函数。
  2. 成员变量二义性:当派生类从多个基类中继承了相同的成员变量时,如果派生类直接访问该成员变量,编译器无法确定应该使用哪个基类的成员变量。这种情况下,可以使用作用域解析运算符(::)来指定要访问的基类成员变量。
class Base1 {
public:
    void display() {
        std::cout << "Base1 display()" << std::endl;
    }
    Base1() {
        std::cout << "Base1 constructor" << std::endl;
    }
};

class Base2 {
public:
    void display() {
        std::cout << "Base2 display()" << std::endl;
    }
    Base2() {
        std::cout << "Base2 constructor" << std::endl;
    }
};

class DerivedYc : public Base1, public Base2 {
public:
    void display() {
        std::cout << "Derived display()" << std::endl;
    }
    DerivedYc() {
        std::cout << "Derived constructor" << std::endl;
    }
};

void test() {
    DerivedYc d;
    d.display();  // 编译错误,二义性调用
    d.Base1::display();  // 使用作用域解析运算符调用 Base1 类中的 display() 函数
    d.Base2::display();  // 使用作用域解析运算符调用 Base2 类中的 display() 函数
}

int main() {
    test();
    return 0;
}

在这个示例中,Derived类从Base1和Base2类中继承了相同的display()函数。

当通过Derived类的对象调用display()函数时,会导致编译错误,因为编译器无法确定应该使用哪个基类的成员函数。

为了解决这个问题,可以使用作用域解析运算符来指定要调用的基类成员函数。

11.4.3 多重继承的构造顺序

那么多重继承中,构造函数的调用顺序是什么样,一起通过下面案例来看看:

class Base1 {
public:
    Base1() {
        cout << "Base1构造函数" << endl;
    }
    ~Base1() {
        cout << "Base1析构函数!" << endl;
    }
};

class Base2 {
public:
    Base2() {
        cout << "Base2构造函数" << endl;
    }
    ~Base2() {
        cout << "Base2析构函数!" << endl;
    }
};

class Son1 : public Base1 , public Base2 {
public:
    Son1() {
        cout << "Son1构造函数" << endl;
    }
    ~Son1() {
        cout << "Son1析构函数!" << endl;
    }
};

class Son2 : public Base2 , public Base1 {
    Son2() {
        cout << "Son2构造函数" << endl;
    }
    ~Son2() {
        cout << "Son2析构函数!" << endl;
    }
};

int main() {
    Son1 son1;
    return 0;
}

打印结果如下所示:

Base1构造函数
Base2构造函数
Son1构造函数
Son1析构函数!
Base2析构函数!
Base1析构函数!

由此可知,多重继承的构造顺序,多重继承的构造函数的调用顺序是按照派生类中基类的声明顺序来确定的。析构函数调用顺序是先子类,后父类【有多重继承则按照声明顺序反向调用】

11.5 其他继承

11.5.1 菱形继承

菱形继承概念: 两个派生类继承同一个基类,又有某个类同时继承者两个派生类。这种继承被称为菱形继承,或者钻石继承。

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

这个时候类 Animal 中的成员变量和成员函数继承到类 SheepTuo 中变成了两份,一份来自 Animal-->Sheep-->SheepTuo 这条路径,另一份来自 Animal-->Tuo-->SheepTuo 这条路径。

示例:

class Animal {
public:
    int age;
};

//此时公共的父类Animal称为虚基类
class Sheep : public Animal {};
class Tuo   : public Animal {};
class SheepTuo : public Sheep, public Tuo {};

int main() {
    SheepTuo st;
    st.Sheep::age = 100;
    st.Tuo::age = 200;

    cout << "st.Sheep::age = " << st.Sheep::age << endl;
    cout << "st.Tuo::age = " <<  st.Tuo::age << endl;
    //cout << "st.age = " << st.age << endl; //因为SheepTuo的父类Sheep和Tuo都有age,编译器不知道选用哪一个,所以产生了歧义。
    return 0;
}

打印结果如下所示:

st.Sheep::age = 100
st.Tuo::age = 200

总结:菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。并且调用变量时存在二义性(编译器不知道选择那个)。

11.5.2 虚继承

虚继承(Virtual Inheritance)是一种继承方式,用于解决多继承中的菱形继承问题(Diamond Inheritance Problem)。

菱形继承问题是指当一个派生类同时继承自两个或多个基类,而这些基类又共同继承自同一个基类时,派生类中会存在多个对同一个基类成员的拷贝,导致二义性和冗余。

虚继承通过在继承关系中使用virtual关键字来解决菱形继承问题。在虚继承中,派生类对共同基类的继承是虚拟的,只会保留一个共同基类的实例,从而避免了多个拷贝和二义性。

class Animal {
public:
    int age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

int main() {
    SheepTuo st;
    st.Sheep::age = 100;
    st.Tuo::age = 200;

    cout << "st.Sheep::age = " << st.Sheep::age << endl;
    cout << "st.Tuo::age = " <<  st.Tuo::age << endl;
    cout << "st.age = " << st.age << endl;
    return 0;
}

打印结果如下所示:

st.Sheep::age = 200
st.Tuo::age = 200
st.age = 200

11.6 继承综合案例

11.6.1 实现网页中内容

例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同

接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处

普通实现:

class Cpp {
public:
    void header() {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }

    void footer() {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }

    void left() {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }

    void content() {
        cout << "C++学科视频" << endl;
    }
};

void test1() {
    cout << "C++页面如下: " << endl;
    Cpp cp;
    cp.header();
    cp.footer();
    cp.left();
    cp.content();
}

int main() {
    test1();
    return 0;
}

继承实现: 类的继承,继承允许依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易,达到了重用代码功能和提高执行效率的效果。

class BasePage {
public:
    void header() {
        cout << "首页、公开课、登录、注册...(公共头部)" << endl;
    }

    void footer() {
        cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
    }

    void left() {
        cout << "Java,Python,C++...(公共分类列表)" << endl;
    }
};

class Cpp1 : public BasePage {
public:
    void content(){
        cout << "C++学科视频1" << endl;
    }
};

void test2(){
    cout << "C++下载视频页面如下: " << endl;
    Cpp1 cp;
    cp.header();
    cp.footer();
    cp.left();
    cp.content();
}

int main() {
    test1();
    return 0;
}

总结: 继承的好处:==可以减少重复的代码==

class A : public B;

  1. A 类称为子类 或 派生类
  2. B 类称为父类 或 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过过来的表现其共性,而新增的成员体现了其个性。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]