C++中的继承机制

简介: C++中的继承机制

继承

面向对象三大特性:封装,继承,多态。继承这里时刻牢记一个类继承了父类时,这个类中==既有自己的成员,也有来自父类的成员!==如何统筹协调这两类成员就是继承的性质。


这些面向对象的特性是针对所有 面向对象程序语言 的!并不是特指C++,这里只是学习C++中面向对象特性的实现。


1. 再次理解封装

封装的第一层理解:封装成类后加上访问限定符,是一种==更严格的管理方式!==同时也可以很好的做到解耦。


封装的第二层理解:STL迭代器的设计。对容器的底层结构进行封装,在不暴露底层数据结构的情况下,给用户提供统一的访问方式,降低使用成本。


封装的第三层理解:STL中的适配器模式。通过对底层容器的封装适配得到需要的其他容器;通过对正向迭代器的封装得到反向迭代器。


2. 继承的基本知识

1. 基本概念

继承是面向对象程序设计中代码复用的重要手段!是 类 设计层次的复用。

// 每个人都有name和tel,就把这些共有的属性提取出来单独设计一个类,其他的类继承我!
class Person        // 父类/基类
{
protected:
  string name_;
  string tel_;
}
class Student : public Person   // 子类/派生类
{
public:
  int stuId_;
  // ...
}
class Teacher : public Person   // 子类/派生类
{
public:
  int TeachId_;
  // ...
}


2. 继承方式

继承方式也有三种:public,protected,private

基类成员/继承方式

public继承

protected继承

private继承

基类的public成员

派生类的public成员

派生类的protected成员

派生类的private成员

基类的protected成员

派生类的protected成员

派生类的protected成员

派生类的private成员

基类的private成员

在派生类中不可见

在派生类中不可见

在派生类中不可见


不可见是指基类的私有成员还是被继承到了派生类中,但是语法上限制==派生类对象不管在类里面还是类外面都无法访问基类的私有成员。==基类的private成员就是为了不让派生类访问修改。


一般而言,基类的访问限定符都是public/protected,继承方式都是public。


3. 基类和派生类对象的赋值转换

public继承中,派生类的对象可以赋值给基类的对象/基类的指针/基类的引用,并且中间不会产生临时变量。就是把派生类中基类的那部分赋给基类的对象,这叫做切割/切片。这个过程是天然支持的,没有类型转换。


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


但是当基类的指针是指向派生类时,这个基类的指针可以 强转后赋值给派生类的指针/引用。


4. 继承中的作用域

基类和派生类有自己独立的作用域。


当基类和派生类中有同名成员(包括成员变量和成员函数)时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)。对于成员函数也是一样,只要基类和派生类函数名相同就构成隐藏。


隐藏是在不同的作用域中,而函数重载是在同一作用域中。


3. 子类的默认成员函数

在调用子类的构造/拷贝构造函数时,会先自动调用父类的构造/拷贝构造函数对父类的成员进行初始化,然后调用子类的构造/拷贝构造函数进行初始化!


在调用子类的析构函数时,为了保证析构顺序会先调用子类的析构函数清理子类的资源,然后自动调用父类的析构函数清理父类的资源!不需要显示调用!(栈帧后进先出)



而对于赋值重载,则在子类的赋值重载函数中需要显示的调用父类的赋值重载函数。


注意一下:子类和父类的赋值重载函数构成了隐藏 (函数名相同),所以调用的时候要指定作用域!另外,析构也构成隐藏—实际析构函数是覆盖,参考多态章节。


4. 继承与友元

友元关系不能被继承!也就是说,父类的友元函数不是子类的友元函数,无法访问子类的私有和保护成员。


5. 继承中的静态成员

静态成员在整个继承体系中只有一份,基类和所有的派生类的对象共享,这些对象共用同一个静态成员。


6. 继承与组合的区别

继承是一种 **"is-a"**的关系,可以认为派生类就是一个基类,在基类的基础上多了派生类自己的成员。


而组合是指在一个类中有另一个类对象,这是一种 **"has-a" **的关系—我里面有一个你,也可以做到类的复用。

// 组合示例
class A
{
  // ...
protected:
  int a_;
};
class B : public A
{
  // ...
protected:
  int b_;
  // B类里有一个A类。
  A classA_;
};


这两种方式都是代码复用的做法。但是继承破坏了封装—public继承下 子类也可以访问修改父类的protected成员!所以认为继承是一种白盒复用;而组合就不会破坏代码的封装—B类不能访问修改A类的protected成员,所以认为组合是一种黑盒复用。


而且继承机制中基类和派生类的耦合度更强,组合机制中两个类之间的耦合度就要低一些。


在实际中能用组合就尽量用组合!但是有些情况下只能用继承那就用继承,而且继承配合多态可以使代码的复用性,灵活性更好!


组合的典型应用:STL中的容器适配器,如stack和queue中就有一个deque类,还配合了模板达到泛型编程。


7. 菱形继承与菱形虚拟继承

1. 菱形继承的产生与带来的问题

首先有多继承,比如D类继承B,C两个类!这时若B类和C类都继承自同一个类,就会导致菱形继承。见下图:


// 菱形继承示例
class A
{
  // ...
protected:
  int a_;
};
class B : public A
{
  // ...
protected:
  int b_;
};
class C : public A
{
  // ...
protected:
  int c_;
};
class D : public B,public C
{
  // ...
protected:
  int d_;
};


菱形继承带来的问题:D类中又两份A类的数据—一份是继承B类得到的,一份是继承C类得到的!这就造成了数据冗余和二义性的问题!(有两份A类的数据—数据冗余,D类访问A类的数据时必须指明访问的是哪一个A类的数据—二义性)



2. 菱形继承问题的解决—菱形虚拟继承

采用菱形虚拟继承就可以解决上述问题!

// 菱形虚拟继承示例
class A
{
  // ...
protected:
  int a_;
};
// 虚继承后B对象的存储结构就变了!不直接存A类的数据,而是存虚基表指针
// 当然A类的数据还在B类中,但是是通过虚基表访问的,不是直接访问的!!
class B : virtual public A   // 在这两个位置进行虚继承
{
  // ...
protected:
  int b_;
};
class C : virtual public A   // 在这两个位置进行虚继承
{
  // ...
protected:
  int c_;
};
class D : public B,public C
{
  // ...
protected:
  int d_;
};


在虚继承中,D类里的B类和C类不再存A类的数据,而是存一个虚基表指针,指向虚基表,虚基表里存放A类数据的偏移量,通过偏移量就可以找到同一份A类的数据!!这就解决了数据冗余和二义性的问题!


简单来说,就是把冗余的基类数据放到一个公共的区域(还在D类里),第一代派生类继承时不存基类的具体数据,而是存一个指针可以找到这个公共区域。其实这个基类的数据变成了临界资源!



在VS下观察如下:



思路:多继承–>菱形继承–>数据冗余和二义性问题–>菱形虚拟继承解决–>具体解决思路(虚基表指针,虚基表存偏移量)


**虚继承后,B对象和C对象的存储结构就已经改变了!!**使用虚基表的方式找A对象。


由于虚继承的复杂,所以实际中尽量避免使用多继承(多继承是菱形继承的根本原因)!!C++标准库中的I/O流就是菱形继承!!


相关文章
|
2天前
|
存储 安全 编译器
【c++】深入理解别名机制--引用
本文介绍了C++中的引用概念及其定义、特性、实用性和与指针的区别。引用是C++中的一种别名机制,通过引用可以实现类似于指针的功能,但更安全、简洁。文章详细解释了引用的定义方式、引用传参和返回值的应用场景,以及常引用的使用方法。最后,对比了引用和指针的异同,强调了引用在编程中的重要性和优势。
9 1
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
77 11
|
1月前
|
安全 测试技术 C++
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化2
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
57 6
|
1月前
|
安全 测试技术 C++
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化1
【C++篇】从零实现 C++ Vector:深度剖析 STL 的核心机制与优化
51 7
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1
|
1月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
36 1
|
27天前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
15 0
|
1月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
27 0
|
1月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
31 0
|
2月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。