这周跟着b站的黑马程序员的c++课程以及csdn提供的c++技术文档学了一下关于继承的知识点,写下这篇blog来梳理下相关知识点。如果看我的blog有不清楚的话,也可以参考这两个的内容
附上链接
b站:https://www.bilibili.com/video/BV1et411b73Z?p=134
c++手册:https://www.php.cn/cplusplus/cpp-inheritance.html
ide为vs 2019社区版。
如果涉及到侵权,请告知作者删改!
这两个又称为父类和子类。继承是指我们可以通过一个已有的类来对一个新类定义。
当定义一个新类时,不需要重新编写新的成员属性和成员函数,只需令新类继承一个已有的类成员即可。
这个新类叫派生类,已有的类称为基类。
通过前面的叙述我们知道,继承作为面向对象的一大特点,可以有效的减少代码冗余书写,提高代码的复用性,让代码看起来更加灵活,并且可以提升代码的性能。
通过上图我们,我们在定义派生类的时候,不需要再声明基类已有的属性A,属性B,只需要用一个继承语句,就可以从基类中的属性A和B,这在代码量特别多的时候,比如要继承一个类中几十上百个属性时,就会变得很好用。
class 派生类 : 继承方式 基类 {…派生类定义…}
注:此处的类定义内容类似于上图的属性C,即派生类除继承得到的其他的成员。
// 继承的基本语法
class Base
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void left()
{
cout << "Java、Pyhon、C++、...(公共分类列表)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
};
//继承的好处:减少重复代码
//语法 class 子类 : 继承方式 父类
class Java : public Base
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
class Python : public Base
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
class CPP : public Base
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
我个人的理解,继承的实质类似于宏定义和内联,编译器会在执行时根据继承方式在派生类添加相应的成员定义。
继承时是会继承到基类的所有成员,而不是如public继承只继承到public成员!!!
这是笔者此前的一个误区,认为除了private继承之外只能继承到private之外的成员,因为在之前的public继承的代码运行结果中,确实是只能看到类内只能访问基类的public和protected成员,但是其实无论哪种继承,都会继承基类的所有成员。这一点在笔者写这篇blog时亲自用vs开发工具验证才发现的。
下面是测试代码
#include
using namespace std;
// 继承的方式——3种
// 公共继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 : public Base1
{
public:
void func()
{
m_A = 10; // 父类中的公共权限成员,到子类中依然是公共权限
m_B = 20; // 父类中的保护权限成员,到子类中依然是保护权限
//m_C = 30; // 父类中的私有权限成员, 子类访问不到
}
};
void test01()
{
Son1 s1;
s1.m_A = 100;
/*s1.func();*/
//s1.m_B = 200; // 到Son1是m_B是保护权限,类外访问不到
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 : protected Base2
{
public:
void func()
{
m_A = 100; // 父类中公共成员,到子类变为保护权限
m_B = 200; // 父类中保护成员,到子类中变为保护权限
//m_C = 300; // 父类中私有权限成员,子类访问不到
}
};
void test02()
{
Son2 s2;
//s2.m_A; //保护继承,父类中公共权限继承后变为子类的保护权限成员,不类外不可访问
}
// 私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 : private Base3
{
public:
void func()
{
m_A = 10; // 父类中公共成员,到子类中变为 私有成员
m_B = 20; // 父类中保护成员,到子类中变为 私有成员
//m_C = 30; // 父类中私有权限成员在子类中访问不到
}
};
class grandSon : public Son3
{
public:
void fun1()
{
//m_A = 100; // 原来的公共成员,继承后变私有成员,再用一个子类去继承原子类就访问不到了
}
};
int main()
{
system("pause");
return 0;
}
笔者使用的是vs2019的开发工具,至于为什么没直接使用cmd,一个是因为笔者对cmd查看类的逻辑的操作并不了解(甚至不知道也没有这种操作),而vs开发工具则提供了这些操作(我怀疑开发工具就是对cmd进行了一定程度的封装,以后看看有无机会看看它的源码)
首先是打开工具,笔者和黑马不一样,黑马用的是2017版本,直接找到了,笔者的开发工具是英文的
打开win键输入develop会跳出开发者工具
打开后,发现界面是这样子的
这不就是cmd命令行的孪生兄弟嘛
应该是在cmd基础上做了一定封装,笔者曾试过相关操作在cmd中执行发现并没有相关命令
切盘操作—— “盘:”
由于笔者将要检测cpp文件的路径放在了D盘中所以不需要切盘
切换文件路径,我们可以找到要检测的文件路径并复制
切换路径—— “cd 文件路径”
输入dir看看是否存在文件
键入命令 “cl /d1 reportSingleClassLayout类名 文件名”
tps:在输入文件名时可以先输入前几个名字然后按tab键就会自动补全
结果如图
我们可以看见即使类Son1是用public方式继承了类Base,但是它的private成员m_C依旧被继承到了,只不过在操作的时候编译器会将基类的private成员设为派生类也不可访问的状态。
模型查看方法就是上面提到的开发者操作
当然,我们也可以直接用代码来验证确实继承到了所有成员
#include
using namespace std;
//继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
// 利用开发人员命令提示工具查看对象模型
// 跳转盘符 D:
// 跳转文件路径 cd 具体路径下
// 查看命名
// cl /d1 reportSingleClassLayout类名 文件名
class Son : public Base
{
public:
int m_D;
};
void test01()
{
// 16
// 说明子类公共继承了Base父类,虽然子类无法访问private权限的成员,但是确实继承了父类中所有的成员
// 包括private权限成员
// 父类中的私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承了下去
cout << "size of Son = " << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
从结果来看,确实继承到了private成员。相对来说,用开发者工具查看会更直观
构造顺序:先构造基类再构造派生类
析构顺序:先析构派生类再析构基类
#include
using namespace std;
//继承中的构造和析构顺序
class base
{
public:
base()
{
cout << "base的构造函数调用" << endl;
}
~base()
{
cout << "base的析构函数调用" << endl;
}
};
class son : public base
{
public:
son()
{
cout << "son的构造函数调用" << endl;
}
~son()
{
cout << "son的析构函数调用" << endl;
}
};
void test01()
{
// base b;
// 继承中的构造和析构顺序如下:
// 先构造父类,再构造子类,析构的顺序与构造的顺序相反
son s;
}
int main()
{
test01();
system("pause");
return 0;
}
这种顺序很好理解,可以类比为建房子和拆房子的过程,建房子时需要打地基,就是构造基类,然后再搭建房子(派生类),拆的时候,先拆房子(子类),再拆地基(父类)
就是子类继承到的成员名和自己再定义的成员名一样的情况,这时候就体现了成员的优先级关系
就在类内也好,类外也好,都会优先使用类内的成员。
那如何通过派生类如何访问父类中的同名成员呢,很简单加上作用域和作用域符 “::” (详见代码)
#include
using namespace std;
//继承中同名成员的处理
class Base
{
public:
int m_A;
Base()
{
m_A = 100;
}
void func()
{
cout << "Base-func()调用 " << endl;
}
void func(int)
{
cout << "Son-func(int)调用" << endl;
}
};
class Son : public Base
{
public:
int m_A;
Son()
{
m_A = 200;
}
void func()
{
cout << "Son-func调用" << endl;
}
/*void func(int)
{
cout << "Son-func(int)调用" << endl;
}*/
};
//同名属性成员处理
void test01()
{
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
// 如果通过子类对象 访问父类同名成员,需要加作用域
cout << "Base 下 m_A = " << s.Base::m_A << endl;
}
//同名成员函数处理
void test02()
{
Son s1;
s1.func();
// 父类中的同名函数会被编译器隐藏,如果要调用需要加作用域
s1.Base::func();
s1.Base::func(100);
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
相比一般的成员,静态成员是存储在全局区,在类外除了通过上述的通过创建类对象访问,还可以通过直接用子类名进行访问父类中的静态成员
#include
using namespace std;
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base-static-void func()调用" << endl;
}
static void func(int a)
{
cout << "Base-static-void func(int)调用" << endl;
}
};
int Base::m_A = 100;
class Son : public Base
{
public:
static int m_A;
static void func()
{
cout << "Son-staic-void func()调用" << endl;
}
};
int Son::m_A = 200;
void test01()
{
//1、通过对象访问
Son s;
cout << "通过对象访问" << endl;
cout << "Son下的 m_A = " << s.m_A << endl;
cout << "Base下的 m_A = " << s.Base::m_A << endl;
//2、通过类名访问
cout << "通过类名访问" << endl;
cout << "Son下的 m_A = " << Son::m_A << endl;
cout << "Base下的 m_A = " << Son::Base::m_A << endl;
}
void test02()
{
//1、通过对象调用
Son s;
cout << "通过对象访问" << endl;
s.func();
s.Base::func();
//2、通过类名调用
cout << "通过类名调用" << endl;
Son::func();
// 第一个:: 表示通过类名方式访问,第二个:: 代表访问父类作用域下
Son::Base::func();
Son::Base::func(100);
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
就是一个派生类可以有多个基类
class 派生类 : 继承方式 基类1 继承方式 基类2 …{派生类定义}
#include
using namespace std;
//多继承语法
class Base1
{
public:
int m_A;
public:
Base1()
{
m_A = 100;
}
};
class Base2
{
public:
int m_A;
public:
Base2()
{
m_A = 200;
}
};
class Son : public Base1, public Base2
{
public:
Son()
{
m_B = 300;
m_C = 400;
}
public:
int m_B;
int m_C;
};
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
// 当父类中出现同名成员,需要加作用域区分
cout << "Base1::m_A = " << s.Base1::m_A << endl;
cout << "Base2::m_A = " << s.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
就是多继承的一种特殊情况
如图类D多继承了类B和类C,而类B,C又继承了类A,如果继承方式不变,且类B和类C无额外定义,就相当于类D继承类A,这种菱形继承的语法并不推荐,因为很容易出现同名成员有多份(可能不同的)数据拷贝,虽然可以用作用域去访问对应成员,但是实际上我们只需要继承一份成员即可。
虚继承,解决从不同途径继承的同名成员在内存中有不同的拷贝数据的问题,将共同基类设置为虚基类,此时这些同名成员在内存中就一个拷贝,相当于引用。
通过虚继承解决了二义性问题和节省了内存。
继承前加上关键字 virtual即可
#include
using namespace std;
// 菱形继承
// 重复继承,继承了多个类的属性,实际上只需要继承一个即可
// 解决方法,虚继承: 继承之前加上关键字virtual
// 动物类
class Animal
{
public:
int m_Age;
};
// 羊类
class Sheep : virtual public Animal{};
// 驼类
class Camel : virtual public Animal{};
// 羊驼类
class Alpaca : public Sheep, public Camel
{};
void test01()
{
Alpaca al;
al.m_Age = 10;
cout << "羊驼的 m_Age = " << al.m_Age << endl;
}
// 虚继承产生的 vbptr 即virtual base pointer 虚拟基类指针 0->8, 4+4 = 8
int main()
{
test01();
system("pause");
return 0;
}
这个也是笔者此前的一个混淆的知识点,笔者对其的区分是在使用时,除开同名成员,继承可以直接访问继承到的成员,而类嵌套则需要从通过成员在访问该成员的成员,相对来说继承显得更加灵活。且使用保护继承和私有继承可以使继承到的成员变成派生类的保护或者私有成员。
但在内存上,二者本质上一样的,都是将一个类放到另一个类中,消耗的内存都是一样大的。
本篇提及继承的知识点有基本语法,继承方式,同名成员,多继承,继承的好处以及开发者工具的使用和继承与嵌套的区分。
接下来将会继续学习C++的多态,以及python的正则表达式学习和前端三件套的学习。