C++: 继承

目录

一、继承的概念及定义

1.1继承的概念

1.2为什么要有继承

1.3继承的权限

二、基类和派生类对象赋值转换

三、继承中的作用域

3.2隐藏

 四、派生类的默认成员函数

1.构造函数

2.析构函数

3.拷贝构造

4.赋值运算符重载

5.取地址重载

五、继承与友元

六、继承与静态成员

七、复杂的菱形继承和菱形虚拟继承

八、继承的总结和反思

8.2 继承和组合


一、继承的概念及定义

1.1继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在已有的类上进行扩展。被继承的类叫基类,扩展了功能后的类叫派生类,日常也称基类叫父类,派生类叫子类。

继承呈现了面向对象程序设计的层次结构,C++面向对象,体现在三个重要方面,封装,继承和多态。封装已经在前面学过了,这次的继承也很重要。

继承体现了由简单到复杂的认知过程。简单指基类,复杂指派生类,以及由于继承关系,而多出的一些联系和操作。以前我们接触的都是函数的复用(比如在类里写某个函数之前,加个模板,方便函数复用),继承是类设计层次的复用。

1.2为什么要有继承

我们在写类的时候,不可避免会遇到一种情况,A类和B类的重复函数和元素太多了。比如学生和老师,他们都是人,就有人的共同信息,名字,性别,年龄等。那么通过继承,学生可以继承人的类,老师也能继承人的类。

继承以后,这个类就不用再重复写了,只需要作为学生在人的类的基础上再扩展一些表示身份的信息,就可以了。

class Person
{
 public:
    size_t age;
    char name;
    char gender;
};

class  Student: public Person
{
   private:
    char Student_ID;
    int grade;
}

class Teacher: public Person
{
   private:
   char Teacher_TD;
   char position;
}
    

Person是被继承的类,Student和Teacher两个类继承了Person类,继承的方式是public。

继承后,父类的Person成员(成员函数和成员变量)都会变成子类的一部分。

1.3继承的权限

并不是父类所有成员都可以继承的,父类的成员可能有protected、public和private三种,继承关系也有protected、public和private三种。不同的继承方式和不同的成员都有不同的权限,继承以后父类的成员在子类中会变成什么?下表中给了答案,其中日常用的最多的还是public的继承方式。

类成员/继承方式 public继承 protected继承 private继承
public成员 派生类的public成员 派生类的protected成员 派生类的private成员
protected成员 派生类的protected成员 派生类的protected成员

派生类的private成员

private成员 在派生类不可见 在派生类不可见 在派生类不可见

总结:

1.基类的private成员在派生类不可见,无论是在派生类的类外还是类里都不能访问。

2.基类的protected在派生类的类里是可见的,它是可以被继承的,可以看到protected就是为了继承出现的,它在派生类中可以访问,派生类外是不能访问的。

3.类成员和继承方式一般是取更小的那个权限来决定派生类的继承后的成员是在哪种权限。

4.日常中用的最多的是public继承方式,因为其他两种只能在一个类里继承,复用性有限。

二、基类和派生类对象赋值转换

派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里又被称为切割,寓意把子类中存有父类的那一部分切掉赋值过去。

基类对象不能赋值给派生类对象。

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或引用,但是必须是基类的指针指向派生类对象时才是安全的。如果不是指向派生类对象,可能会存在越界访问的问题。

三、继承中的作用域

3.1在继承体系中,基类和派生类都有独立的作用域。

3.2隐藏

子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(但在子类成员中可以用基类::基类成员 显示访问)

3.3如果是隐藏,函数名相同就构成隐藏,参数怎么样无所谓的。这不是重载,因为不在同一个作用域里。

3.4实际中,最好不要在派生类中写和继承同名的成员(无论是变量还是函数)

class A
{
  protected:
  int _num;
  int name;
};

class B: public A
{
public:
   void Print()
   {
    cout<<_num<<endl;//虽然继承了A的_num,但是这里只会对A的_num隐藏
                    //而使用B自己的_num。
    cout<<A::_num<<endl;//如果要用B的,就要显式调用
   }
   private:
   int _num;
};

 四、派生类的默认成员函数

1.构造函数

在构造时先调用基类的构造,再调用父类的构造,如果基类没有默认的构造函数,就要在派生类的初始化列表显式调用基类的构造。

2.析构函数

先调用派生类的析构函数,再调用基类的析构函数。注意,这个基类的析构函数不用自己显式写,编译器会自动调用的。因为他怕你忘了。必须先调用父类再调用子类才可以保证数据不丢失。所以这个不需要自己写。

3.拷贝构造

必须先用基类的拷贝构造构造完以后再调用自己的。

4.赋值运算符重载

必须先调用基类的赋值运算符重载完成基类的赋值。

5.取地址重载

普通类和const类取地址重载,这个很少要自己写。

五、继承与友元

友元关系不能被继承。基类的友元,不能访问子类的私有和保护成员。

如果友元关系可以继承,这个友元函数也是子类的朋友,那么它是可以访问子类的私有和保护成员的,实际上不能。所以是不能被继承的。

六、继承与静态成员

基类定义了一个static静态成员,则整个继承体系里,只有一份static成员的实例。可以这么理解,static成员属于整个类,不属于某个实例。它是公有的,在每个实例化的类对象里,访问的地址都是相同的。

七、复杂的菱形继承和菱形虚拟继承

7.1多继承

一个子类有两个或两个以上的父类

7.2菱形继承

菱形继承是多继承的一种特殊情况,表示子类继承的父类中有同时继承一个父类的。

如图所示,D所继承的B和C有共同的A,造成了菱形继承。

那么这份A的成员在D中会有几份呢?

两份。

为了解决菱形继承的存储冗余问题,C++又发明了虚继承。

像这样,在直接继承A的时候,加个虚继承就可以解决二义性和冗余的问题。

如何找B中的A和C中的A?

B和C中存了两个指针,指向了一张表。这两个指针叫虚基表指针,这张表叫虚基表。虚基表中存的是偏移量。也就是B距离A的地址,通过这个偏移量,可以找到A。

八、继承的总结和反思

8.1C++的语法复杂,因为多继承和菱形继承复杂,还有了虚继承,虚基表,表中存偏移量。这些就一直在填多继承语法的坑。

8.2 继承和组合

1.public继承是一种is-a的关系,每一个派生类对象都是一个基类对象。

2.组合是一种has-a的关系。假设B组合了A,则每个B对象中都有一个A对象。(比如stack类和queue类用其他的标准容器类作为底层容器。)

3.优先使用对象组合,而不是类继承。

4.继承允许通过基类的实现来定义派生类的实现,这种通过生成派生类的复用叫白箱复用。在继承的方式中,继承后的派生类对基类可见,这在一定程度上破坏了封装。基类的改变,对派生类的影响很大。派生类和基类的依赖关系很强,耦合度很高。一般写代码避免高耦合度。

5.对象组合是类继承之外的另一种复用选择。对象组合要求对象的接口定义良好。对象的内部细节不可见,能用的只有接口,所以叫黑箱复用。组合依赖关系弱,耦合度低。优先使用对象组合,有利于每个类的封装。

6.实际上,能用组合尽量用组合,组合的代码方便维护。不像继承,一个改了,别的都得改。但实现多态必须用到继承,该用继承就用继承。可以用组合就用组合。

相关推荐

  1. <span style='color:red;'>C</span>++<span style='color:red;'>继承</span>

    C++继承

    2024-05-26 04:48:18      57 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-26 04:48:18       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-26 04:48:18       100 阅读
  3. 在Django里面运行非项目文件

    2024-05-26 04:48:18       82 阅读
  4. Python语言-面向对象

    2024-05-26 04:48:18       91 阅读

热门阅读

  1. RequestBodyAdvice和ResponseBodyAdvice是干什么的

    2024-05-26 04:48:18       34 阅读
  2. HTML5 游戏开发基础及流程

    2024-05-26 04:48:18       29 阅读
  3. Unity vscode在mac上的编译环境设置

    2024-05-26 04:48:18       32 阅读
  4. 算法设计与分析-回溯法

    2024-05-26 04:48:18       34 阅读
  5. TLS/SSL握手协议

    2024-05-26 04:48:18       31 阅读
  6. RabbitMQ

    RabbitMQ

    2024-05-26 04:48:18      35 阅读
  7. RabbitMQ02-RebbitMQ简介及交换器

    2024-05-26 04:48:18       25 阅读