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

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

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

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

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

🔝🔝


继承

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

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++继多态 🔍

相关推荐
@东辰3 分钟前
【golang-技巧】-自定义k8s-operator-by kubebuilder
开发语言·golang·kubernetes
乐悠小码10 分钟前
数据结构------队列(Java语言描述)
java·开发语言·数据结构·链表·队列
史努比.12 分钟前
Pod控制器
java·开发语言
敲敲敲-敲代码21 分钟前
游戏设计:推箱子【easyx图形界面/c语言】
c语言·开发语言·游戏
ROC_bird..29 分钟前
STL - vector的使用和模拟实现
开发语言·c++
机器视觉知识推荐、就业指导29 分钟前
C++中的栈(Stack)和堆(Heap)
c++
MavenTalk35 分钟前
Move开发语言在区块链的开发与应用
开发语言·python·rust·区块链·solidity·move
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】生产消费模型 & 阻塞队列
java·开发语言·java-ee
2401_840192271 小时前
python基础大杂烩
linux·开发语言·python
@东辰1 小时前
【golang-技巧】- 定时任务 - cron
开发语言·golang·cron