【C++进阶(八)】C++继承深度剖析

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++

🔝🔝


继承

  • [1. 前言](#1. 前言 "#1__15")
  • [2. 继承的基本概念](#2. 继承的基本概念 "#2__29")
  • [3. 继承关系和访问限定符](#3. 继承关系和访问限定符 "#3__87")
  • [4. 继承中的作用域](#4. 继承中的作用域 "#4__109")
  • [5. 父子类的对象赋值转换](#5. 父子类的对象赋值转换 "#5__150")
  • [6. 子类中的默认成员函数](#6. 子类中的默认成员函数 "#6__163")
  • [7. 继承与友元,继承与静态变量](#7. 继承与友元,继承与静态变量 "#7__262")
  • [8. 菱形继承和虚拟继承](#8. 菱形继承和虚拟继承 "#8__277")
  • [9. 总结以及拓展](#9. 总结以及拓展 "#9__341")

1. 前言

接下来的几篇博客会进入C++

继承和多态的学习,在校招笔试

和面试中这一章节考察的很多!

请同学们耐心学习!

本章重点:

本篇文章着重讲解继承的概念和定义,
父类和子类的对象赋值转换,
继承中的作用域以及子类的默认成员函数
以及继承和友元,继承和静态成员的关系
最后讲解菱形继承和虚继承概念


2. 继承的基本概念

继承,其实就是一种代码的复用手段

子类继承父类就能用父类中的变量!

举一个例子:

在师生管理系统中,有学生和老师两个
角色,学生和老师的共同信息有:姓名
性别,年龄和身高等等,然而学生又有一些
专有的信息,比如学号和所属学院
老师也有专属信息如:工号和所教学科

这样就可以将师生的共同信息提取出来:

cpp 复制代码
struct Person
{
	string name;
	string sex;
	int age;
	int height;
}

在实现student类和teacher类时

只需要继承上面的person类即可!

cpp 复制代码
class Student : public Person
{
protected:
	int _stuid; // 学号
};
cpp 复制代码
class Teacher : public Person
{
protected:
	int _jobid; // 工号
};

派生类也被称为子类
基类也被称为父类

父子类成员的使用:

cpp 复制代码
Student st;
st._stuid=123456;
st.name="张三";
st.age=20;

子类对象中课直接调用父类变量!


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

继承方式和访问限定符一样有三种:

继承的方式不同,那么子类中继承

到的父类的变量的访问权限就不同
可以用下面的表格来表示它们的关系:


我简单的总结以下几点:

  1. 无继承体系中,protected和private没有区别
  2. 在继承体系中,父类的protected成员在子类
    中也是protected或保护成员
  3. 父类的private成员在子类是不可见的!
    (继承下来了但不能使用)
  4. 实际中使用继承时一般都用public继承
  5. 使用关键字class时默认的继承方式是private
    使用struct时默认的继承方式是public

4. 继承中的作用域

先说以下结论:

  1. 继承体系中基类和子类有独立的作用域
  2. 子类和父类中有同名成员时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义(在子类成员函数中,可以使用基类::基类成员显示访问)
  3. 需要注意的是如果是成员函数的隐藏
    只需要函数名相同就构成隐藏
  4. 实际中在继承体系里面最好不要定义同名的成员
cpp 复制代码
class Person
{
protected :
	int _num = 111;   // 身份证号
};

class Student : public Person
{
protected:
	int _num = 999; // 学号
};

在main函数中定义student对象

后再打印_num默认为子类中的_num

若想打印父类中的_num,需要指定类域

cpp 复制代码
Student st;
cout<<st._num;
cout<<st.Person::_num;

有同学可能会疑惑:函数名相同的话
不应该是构成函数重载吗?是的,在同一
作用域下,函数名相同确实构成函数重载
但是父子类是不同作用域,这里是构成隐藏!


5. 父子类的对象赋值转换

老样子,先说结论:

  • 子类对象可以赋值给基类的
    -/对象/基类的指针/基类的引用
  • 基类对象不能赋值给派生类对象

注意这里能够赋值不是隐式类型转换!


6. 子类中的默认成员函数

还记得类的六个默认成员函数吗?

就是不显示写系统会自动生成的:

子类的默认成员函数有哪些特殊的行为?
下面我直接给出结论:

  1. 子类的构造函数必须显示调用父类的构造
    去初始化父类的那部分成员(拷贝构造也是)
  2. 子类的operator=中必须调用父类的
    operator=完成父类成员赋值
  3. 子类的析构函数不用显示调用父类的析构
    编译器会自动去调用
  4. 子类初始化对象时,先初始化父类的成员变量
    再初始化子类的成员变量
  5. 子类析构清理时先调用子类的析构函数
    再调用父类的析构函数(与构造反过来)

可以使用以下代码区验证此结论:

cpp 复制代码
class Person
{
public :
 Person(const char* name = "peter")
 : _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)
 	: Person(name )
 	, _num(num )
 	{
 	cout<<"Student()" <<endl;
 	}
 
 Student(const Student& s)
 	: Person(s)
 	, _num(s ._num)
 {
 	cout<<"Student(const Student& s)" <<endl ;
 }
 
 Student& operator = (const Student& s )
 {
 	cout<<"Student& operator= (const Student& s)"<< endl;
 	if (this != &s)
 	{
 		Person::operator =(s);
		_num = s ._num;
 	}
 return *this ;
 } 
 
 ~Student()
 {
 	cout<<"~Student()" <<endl;
 }
protected :
 	int _num ; //学号
};
void Test ()
{
 	Student s1 ("jack", 18);
 	Student s2 (s1);
 	Student s3 ("rose", 17);
 	s1 = s3 ;
}

7. 继承与友元,继承与静态变量

继承与友元的关系很简单一句话:

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

继承和静态成员的关系也很简单:

基类中定义的静态成员被整个继承体系共享
整个继承体系里面只有一个这样的成员
无论派生出多少个子类
都只有一个static成员实例


8. 菱形继承和虚拟继承

在使用继承时会遇见以下情况:

类B继承了类A,类C也继承了类A
然而类D继承了类B和C

此时会有一个问题,类D的实例化对象中

有类B和类C,然而B类和C类都有A类

所以说D类对象中的A类成员就重复了!

cpp 复制代码
class A
{
	int _a = 1;
};
class B :public A
{
	int _b = 2;
};
class C :public A
{
	int _c = 3;
};
class D :public B, A
{
	int _d = 4;
};

我们通过内存窗口观察一下:

D对象中有两个_a,一个在B类一个在C类

这就造成了数据冗余,于是可以使用虚拟继承

来解决这一问题:

虚拟继承:在继承前加上virtual关键字

cpp 复制代码
class A
{
	int _a = 1;
};
class B :virtual public A
{
	int _b = 2;
};
class C :virtual public A
{
	int _c = 3;
};
class D :public B, A
{
	int _d = 4;
};

注意,只用腰部的类加上virtual即可!

virtual这一关键字在多态还有大用处!


9. 总结以及拓展

继承是多态的基础,而笔试面试的时候

继承和多态是考察的很多的,希望同学们

把基础打扎实.当然关于继承的内容其实不止

这些,这些只是最重要的内容,关于继承问题

我们将在下一章节:多态时再展开叙述

对于is-a和has-a的拓展阅读:

拓展阅读


🔎 下期预告:C++继多态 🔍

相关推荐
2035去旅行5 小时前
bootcmd 详细讲解
linux·嵌入式·arm·soc
陈希1235 小时前
Linux 防火墙配置实战:firewalld、iptables 与云安全组
linux·运维·服务器
女神下凡6 小时前
芯参谋(95):嵌入式文件系统_开发指南
linux·服务器·arm开发·驱动开发·设计规范
且从容.10 小时前
Ubuntu安装flume
linux·ubuntu·flume
承渊政道10 小时前
Linux网络学习【Reactor反应堆模式】
linux·网络·学习·ubuntu·reactor
且从容.13 小时前
ubuntu安装flink
linux·ubuntu·flink
爱和冰阔落14 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨14 小时前
WSL2的安装与使用
linux·wsl
微小的鱼15 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng15 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器