C++入门11——详解C++继承(菱形继承与虚拟继承)-1

简介: C++入门11——详解C++继承(菱形继承与虚拟继承)-1

1.继承的概念与定义

在学校的作业中,我们大部分人似乎都遇到过同样的一个作业:设计学校人员管理系统。在这个作业中我们要分别设计不同身份的人员信息,

比如老师:

学生:

学校里不仅有教师、学生,还有职工、学校不同部门的领导、外聘人员......

这么多不同身份的人员,我都要设计这么多的人员类吗?可能一个学校的人员还不算很复杂,但是以后如果遇到给不同的图书设计信息管理系统,成百上千种不同类型的图书,难道我就要设计成百上千种图书类吗?针对类似的问题,C++给出了继承的概念:

1.1继承的概念:

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

比如上述问题,我们就可以为各个人员类设计出一个父类,让成员类复用父类:

(父类包含各个子类的公有属性,每个子类再单独列出自己的独有属性)

#include <iostream>
 
using namespace std;
 
class Person
{
public:
  void Print()
  {
    cout << "name:" << _name << endl;
    cout << "age" << _age << endl;
  }
protected:
  string _name = "张三";//姓名
  int _age = 18;//年龄
};
 
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。
 
class Student :public Person
{
protected:
  int _stuid;//学号
};
 
class Teacher :public Person
{
protected:
  int _jobid;//工号
};
 
int main()
{
  Student s;
  Teacher t;
  s.Print();
  t.Print();
  return 0;
}

我们打开监视窗口看一下s和t对象:

我们的Student和Teacher类中并没有定义_name和_age变量,可是在监视窗口却发现了这两个变量,这就说明子类复用了父类的变量。

结果运行如图:

Student和Teacher类中并没有Print函数,而结果却显示调用了Print函数,这就说明子类复用了父类的成员函数。


小结一下:子类可以复用父类的成员变量和成员函数。

1.2继承的定义  

1.定义格式

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

2.继承关系和访问限定符

我们在以往的文章C++入门2——类与对象1(类的定义和this指针)中介绍了类的访问,当时介绍了类的三个访问限定符

但是现在我们要知道类也存在三种继承方式:

3.基类成员在派生类中的访问方式变化

C++入门2——类与对象1(类的定义和this指针)我们介绍类的三个访问限定符时说:(现阶段认为protected与private相同,不同点会在后面详细探究),所以我们有很长一段时间都没有用到protected,但在C++继承这里我们就用到了protected。

类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected 成员 派生类的private 成员
基类的protected 成员 派生类的protected 成员 派生类的protected 成员 派生类的private 成员
基类的private成 员 在派生类中不可见 在派生类中不可见 在派生类中不可见

总结:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它;

2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的;

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private;

4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式;

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强。

把3拎出来通俗点说:

父类的public成员:public与三种继承方式相比“两者相比取其轻”:public继承,访问public成员;protected继承,访问protected成员;private继承,访问private成员。

父类的protected成员:同样的道理,protected与三种继承方式相比“两者相比取其轻”:public继承,访问protected成员;protected继承,访问protected成员;private继承,访问private成员。

父类的private成员:无论如何继承,子类都不能访问。

代码演示三种继承方式下父类成员的各类型成员访问关系的变化 :

给定父类:

class Person
{
public:
    void Print()
    {
        cout << _name << endl;
    }
protected:
    string _name = "张三"; // 姓名
private:
    int _age = 19;   // 年龄
};

①public继承:

class Student : public Person
{
 
protected:
    int _stunum = 2021060450; // 学号
};

即相当于:

②protected继承

class Student : protected Person
{
 
protected:
    int _stunum = 2021060450; // 学号
};

即相当于:

③private继承

class Student : private Person
{
 
protected:
    int _stunum = 2021060450; // 学号
};

即相当于:

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

派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。

但要注意基类对象不能赋值给派生类对象。

class Person
{
protected:
  string _name; // 姓名
  string _sex;  // 性别
  int _age;
  // 年龄
};
class Student : public Person
{
public:
  int stuid; // 学号
};
void Test()
{
  Student s;
  // 1.子类对象可以赋值给父类对象/指针/引用
  Person p = s;
  Person* pp = &s;
  Person& rp = s;
  //2.基类对象不能赋值给派生类对象
  s = p;//error
}

3.继承中的作用域

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

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

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

4. 注意在实际中在继承体系里面最好不要定义同名的成员。

class Person
{
protected:
    string _name = "张三"; // 姓名
private:
    int _age = 19;   // 年龄
};
 
class Student : public Person
{
public:
    void Print()
    {
        cout << "姓名:" << _name << endl;
        cout << "年龄:" << _age << endl;
        cout << "学号:" << _stuid << endl;
    }
protected:
    int _stuid = 2021060450; // 学号
    int _age = 22;
};
 
int main()
{
    Student s;
    s.Print();
    return 0;
}

运行结果年龄为22,这就是因为基类和派生类都有独立的作用域,当成员变量名字相同时,优先使用本作用域的变量。

#include <iostream>
 
using namespace std;
 
 
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
  void fun()
  {
    cout << "func()" << endl;
  }
};
class B :public A
{
public:
  void fun(int a)
  {
    A::fun();
    cout << "fun(int a)->" << a << endl;
  }
};
 
int main()
{
  B b;
  b.fun(10);
  b.A::fun();
  return 0;
}

相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
24 0
|
1月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
18 0
|
1月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
1月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
9天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
37 4
|
11天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
34 4