C++之继承

简介: C++之继承



一、继承的概念

1、概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,

承是类设计层次的复用。

class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18;  // 年龄
};
class Student : public Person
{
protected:
    int _stuid; // 学号
};
class Teacher : public Person
{
protected:
    int _jobid; // 工号
};

继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。

2、继承的定义

下面我们看到Person父类,也称作基类Student子类,也称作派生类

3、继承关系和访问限定符

我们知道访问限定符有 public、protected 和 private三种,而继承方式也是有三种。他们之间重要的关系如下:

注:不可见表示子类无法访问父类的私有成员。因此如果父类里有不想让子类访问的成员,就可以设为私有权限。

基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。


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

派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片

或者切割。寓意把派生类中父类那部分切来赋值过去。基类对象不能赋值给派生类对象。

class Person
{
protected :
    string _name; // 姓名
    string _sex;  // 性别
    int _age; // 年龄
};
class Student : public Person
{
public :
    int _No ; // 学号
};
int main()
{
    Student sobj ;
 // 子类对象可以赋值给父类对象/指针/引用
    Person pobj = sobj ;
    Person* pp = &sobj;
    Person& rp = sobj;
}

三、继承下的子类的成员函数

父类

class Person
{
public:
  Person(const char* name = "ZDL")
    : _name(name)
  {
    cout << "Person()" << endl;
  }
  Person(const Person& p)
    : _name(p._name)
  {
    cout << "Person(const Person& p)" << endl;
  }
  Person& operator=(const Person& p)
  {
    cout << "Person operator=(const Person& p)" << endl;
    if (this != &p)
      _name = p._name;
    return *this;
  }
  ~Person()
  {
    cout << "~Person()" << endl;
  }
protected:
  string _name;
};

子类

class Student : public Person
{
public:
  Student(const char* name, int num);
    Student(const Student& s);
    Student& operator=(const Student& s);
  ~Student();
protected:
  int _num;
};

1、构造函数

子类自己的成员,跟类和对象一样。从父类继承过来的成员需要去调用父类的构造函数。

Student(const char* name,int num)
    :Person(name)  //_name是由父类继承而来,需要去调用父类的构造函数
  ,_num(num)   //子类自己的成员自己初始化
{}

2、拷贝构造函数

子类自己的成员,跟类和对象一样(内置类型完成值拷贝,自定义类型调用它自己的拷贝构造)。从父类继承过来的成员需要去调用父类的拷贝构造函数。

Student(const Student& s)
    :Person::Person(s)//调用父类的拷贝构造
  ,_num(s._num)
{}

3、赋值运算符

Student& operator=(const Student& s)
{
  if (this != &s)
  {
    Person::operator=(s);
    _num = s._num;
  }
  return *this;
}

4、析构函数

在继承的情况中,不需要显示调用父类的析构函数,编译器自动调用,先析构子类,再析构父类。只需要处理子类中的成员。

//子类的析构函数跟父类的析构函数构成隐藏
~Student()
{
  //处理子类自己的
}

四、继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。


五、继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子

类,都只有一个static成员实例 。


六、菱形继承问题

1、单继承

一个子类只有一个直接父类时称这个继承关系为单继承。

2、多继承

一个子类有两个或以上直接父类时称这个继承关系为多继承。

3、菱形继承

菱形继承是多继承的一种特殊情况。 如下图:

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。

在Assistant的对象中Person成员会有两份。

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和

Teacher的继承Person时使用虚拟继承,加上关键字 virtual 即可解决问题。

为了方便观察,我们使用下面的例子来看看菱形继承的问题:

class A
{
public:
  int _a;
};
// class B : virtual public A
class B : public A
{
public:
  int _b;
};
// class C : virtual public A
class C : public A
{
public:
  int _c;
};
class D : public B, public C
{
public:
  int _d;
};
int main()
{
  D d;
  d.B::_a = 1;
  d.C::_a = 2;
  d._b = 3;
  d._c = 4;
  d._d = 5;
  return 0;
}

从上面的图中我们就可以看出在D中A的成员_a有两份,有数据冗余的问题。接下来我们了看看虚拟继承下的D中的情况。

这下我们发现 A 的 _a 成员只有一份了(红色框起来的),而 B 类和 C类中也分别多出了一个指针。那么这指针是个什么的呢?下面我们来讲一讲。  

从内存的结构看去,我们发现指针指向的内容为它到取到 _a 的位置的偏移量,这样我们就可以找到共用的_a。这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A(虚基类) 。


七、继承与组合

1、public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

2、组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

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

4、实际尽量多去用组合。组合的耦合度低,代码维护性好。要实现多态,也必须要继承。

目录
相关文章
|
21天前
|
Java C++
C++的学习之路:21、继承(2)
C++的学习之路:21、继承(2)
18 0
|
2月前
|
C++
8. C++继承
8. C++继承
27 0
|
2月前
|
安全 程序员 编译器
C++之继承
C++之继承
|
4天前
|
安全 前端开发 Java
【C++】从零开始认识继承二)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
11 1
|
4天前
|
安全 程序员 编译器
【C++】从零开始认识继承(一)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
19 3
【C++】从零开始认识继承(一)
|
21天前
|
安全 编译器 程序员
c++的学习之路:20、继承(1)
c++的学习之路:20、继承(1)
29 0
|
2月前
|
安全 Java 编译器
C++:继承
C++:继承
32 0
|
2月前
|
安全 Java 程序员
【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)
【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)
|
2月前
|
安全 Java 编译器
C++:继承与派生
C++:继承与派生
|
3天前
|
存储 编译器 C++
C++中的继承
C++中的继承
10 0