本篇要学习的内容和知识结构概览

继承和派生的概念

派生

通过特殊化已有的类来建立新类的过程, 叫做”类的派生”, 原有的类叫做”基类”, 新建立的类叫做”派生类”. 从类的成员角度看, 派生类自动地将基类的所有成员作为自己的成员, 这叫做”继承”. 基类和派生类也可以叫做”父类”和”子类”, 也可以叫做”一般类”和”特殊类”.

继承

类的继承是指派生类继承基类的数据成员和成员函数. 继承用来表示类属关系, 不能将继承理解为构成关系

继承派生的作用

  • 增加新的成员(数据成员和成员函数)
  • 重新定义已有的成员函数
  • 改变基类成员的访问权限

单一继承

一般形式

class 派生类名: 访问控制 基类名 {	private:		成员声明列表	protected:		成员声明列表	public:		成员声明列表}复制代码

"冒号"表示新类是哪个基类的派生类

"访问控制"指继承方式. 三个方式: public, protected, private

派生类的构造函数和析构函数

// 基类
class Point {
int x;
int y; public:
Point(int a, int b) {
x = a;
y = b;
cout << "init Point" << endl;
}
void showPoint() {
cout << "x = " << x << ", y = " << y << endl;
}
~Point() {
cout << "delete Point" << endl;
}
}; // 派生类
class Rect: public Point {
int w;
int h; public:
// 调用基类的构造函数对基类成员进行初始化
Rect(int a, int b, int c, int d):Point(a, b) {
w = c;
h = d;
cout << "init Rect" << endl;
}
void showRect() {
cout << "w = " << w << ", h = " << h << endl;
}
~Rect() {
cout << "delete Rect" << endl;
}
}; int main() {
Rect r(3, 4, 5, 6);
r.showPoint();
r.showRect(); /** 输出结果
init Point // 当定义一个派生类的对象时, 首先调用基类的构造函数, 完成对基类成员的初始化
init Rect // 然后执行派生类的构造函数, 完成对派生类成员的初始化
x = 3, y = 4 // 调用基类成员函数showPoint();
w = 5, h = 6 // 调用派生类成员函数showRect();
delete Rect // 构造函数的执行顺序和构造函数的执行顺序相反, 首先调用派生类的析构函数
delete Point // 其次调用基类的析构函数
*/
}
复制代码

类的保护成员

如果希望Rect中的showRect()函数可以一次显示x, y , w, h. 我们直接修改showRect()函数是不行的.

void showRect() {
cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
}复制代码

报错 error: 'x' is a private member of ‘Point' 'y' is a private member of ‘Point', x, y为Point类的私有成员, 公有派生时, 在Rect类中是不可访问的

我们还需要将基类Point中的两个成员声明为protected的属性

像这样

// 基类
class Point {
// 公有数据成员
protected:
int x;
int y; public:
Point(int a, int b) {
x = a;
y = b;
cout << "init Point" << endl;
}
void showPoint() {
cout << "x = " << x << ", y = " << y << endl;
}
}; // 派生类
class Rect: public Point {
int w;
int h; public:
// 调用基类的构造函数对基类成员进行初始化
Rect(int a, int b, int c, int d):Point(a, b) {
w = c;
h = d;
cout << "init Rect" << endl;
} // 公有派生, Point类中的受保护数据成员, 在Rect类中也是受保护的, 所以可以访问 // 而通过公有继承的基类私有的成员, 在派生类中是不可被访问的 void showRect() {
cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
}
}; int main() {
Rect r(3, 4, 5, 6);
r.showPoint();
r.showRect();
}复制代码

访问权限和赋值兼容规则

在根类中, 对于成员的访问级别有三种, public, protected, private

在派生类中, 对于成员的访问级别有四种, public(公有), protected(受保护), private(私有), inaccessible(不可访问)

  • 公有派生和赋值兼容规则

在公有派生情况下, 基类成员的访问权限在派生类中基本保持不变

  1. 基类的公有成员在派生类中仍然是公有的
  2. 基类的保护成员在派生类中仍然是受保护的
  3. 基类的不可访问的成员在派生类中仍然是不可访问的
  4. 基类的私有成员在派生类中变成了不可访问的

总结: 在公有派生的情况下, 通过派生类自己的成员函数可以访问继承过来的公有和保护成员, 但是不能访问继承来的私有成员, 因为继承过程的私有成员, 变成了第四个级别, 不可访问的.

赋值兼容规则:

在公有派生的情况下, 一个派生类的对象可以作为基类的对象来使用的情况.

像这样

// 基类
class Point {
// 这里声明成员属性为受保护的
protected:
int x;
int y; public:
Point(int a, int b) {
x = a;
y = b;
} void show() {
cout << "x = " << x << ", y = " << y << endl;
}
}; // 派生类
class Rect: public Point {
int w;
int h; public:
// 调用基类的构造函数对基类成员进行初始化
Rect(int a, int b, int c, int d):Point(a, b) {
w = c;
h = d;
} void show() {
cout << "x = " << x << ", y = " << y << ", w = " << w << ", h = " << h << endl;
}
}; int main() {
Point a(1, 2);
Rect b(3, 4, 5, 6);
a.show();
b.show(); Point & pa = b; // 派生类对象初始化基类的引用
pa.show(); // 实际调用基类的show()函数 Point * p = &b; // 派生类对象的地址赋值给指向基类的指针
p -> show(); // 实际也是调用基类的show()函数 Rect * pb = &b; // 派生类指针
pb -> show(); // 调用派生类的show()函数 a = b; // 派生类对象的属性值, 更新基类对象的属性值
a.show(); // 调用基类的show()函数
/**
x = 1, y = 2
x = 3, y = 4, w = 5, h = 6
x = 3, y = 4
x = 3, y = 4
x = 3, y = 4, w = 5, h = 6
x = 3, y = 4
*/
}
复制代码
  • “isa”和”has-a“的区别

继承和派生 isa

比如一个Person类, 派生出一个Student类, 我们可以说Student就是Person, 也就是 Student isa Person, 而反过来则不行.

一个类用另一个类的对象作为自己的数据成员或者成员函数的参数 has-a

像这样

// 地址类
class Address {};
class PhoneNumber {}; // 职工类
class Worker {
String name;
Address address;
PhoneNumber voiceNumber;
};
复制代码

表示一个Worker对象有一个名字, 一个地址, 一个电话号码, has-a的关系, 包含的关系

  • 私有派生

通过私有派生, 基类的私有和不可访问成员在派生类中是不可访问的, 而公有和保护成员这里就成了派生类的私有成员

// 基类
class Point {
int x;
int y; public:
Point(int a, int b) {
x = a;
y = b;
} void show() {
cout << "x = " << x << ", y = " << y << endl;
}
}; // 派生类
class Rect: private Point {
int w;
int h; public:
Rect(int a, int b, int c, int d):Point(a, b) {
w = c;
h = d;
} void show() {
Point::show(); // 通过私有继承, Point类中的公有成员show(), 在Rect中为私有
cout << "w = " << w << ", h = " << h << endl;
}
}; class Test: public Rect { public:
Test(int a, int b, int c, int d):Rect(a, b, c, d) { }
void show() {
Rect::show();
// Point::show();
/** error: 'Point' is a private member of ‘Point’
标明: 不可访问基类Point中的成员
Rect类私有继承自Point类, 所以Point中的私有成员x, 私有成员y, 在Rect类中为不可访问: Point类中公有成员show(), 在Rect中变私有
Test类公有继承自Rect类, 所以在Rect中成员x, 成员y, 仍然是不可访问, Rect::show()还是public, 但是Point::show()不可访问 */
}
};
复制代码

因为私有派生不利于进一步派生, 因而实际中私有派生用得并不多

  • 保护派生

保护派生使原来的权限都降一级使用, 即private变为不可访问, protected变为private, public变为protected. 限制了数据成员和成员函数的访问权限, 因此在实际中保护派生用得也不多.

比如: 我们在上个例子中, Rect类保护派生于Point, 则在Test类中Point::show();就可以使用啦!

多重继承

一个类从多个基类派生

格式

class 派生类名: 访问控制 基类名1, 访问控制 基类名2, … {
定义派生类自己的成员
}复制代码

像这样

// 基类A, 也叫根类
class A {
int a; public:
void setA(int x) {
a = x;
} void showA() {
cout << "a = " << a << endl;
}
}; // 基类B, 也叫根类
class B {
int b; public:
void setB(int x) {
b = x;
} void showB() {
cout << "b = " << b << endl;
}
}; // 多重继承, 公有继承自类A, 私有继承自类B
class C: public A, private B {
int c; public:
void setC(int x, int y) {
c = x;
setB(y);
} void showC() {
showB();
cout << "c = " << c << endl;
}
}; int main() {
C c;
c.setA(53); // 调用基类setA()函数
c.showA(); // 调用基类showA()函数 c.setC(55, 58); // 调用派生类C的setC()函数
c.showC(); // 调用派生类C的showC()函数 // 派生类C私有继承自基类B, 所以基类B中私有成员b, 在派生类C中不可访问, 基类B中公有成员setB(), showB()在派生类C中变私有. 在main()函数中不可访问
// c.setB(60); // error: 'setB' is a private member of 'B'
// c.showB(); // 'showB' is a private member of 'B'
/**
a = 53
b = 58
c = 55
*/
}
复制代码

二义性及其支配规则

对基类成员的访问必须是无二义性的, 如果一个表达式的含义可以解释为可以访问多个基类中的成员, 则这种对基类成员的访问就是不确定的, 称这种访问具有二义性

作用域分辨符和成员名限定

// 基类A, 也叫根类
class A {
public:
void func() {
cout << "A func" << endl;
}
}; // 基类B, 也叫根类
class B {
public:
void func() {
cout << "B func" << endl;
} void gunc() {
cout << "B gunc" << endl;
}
}; // 多重继承
class C: public A, public B {
public:
void gunc() {
cout << "C gunc" << endl;
} void hunc() {
/**
这里就具有二义性, 它即可以访问A类中的func(), 也可以访问类B中的func()
*/
// func(); // error: Member 'func' found in multiple base classes of different types
} void hunc1() {
A::func();
} void hunc2() {
B::func();
}
}; int main() {
C c;
// c.func(); // 具有二义性
c.A::func();
c.B::func();
c.B::gunc();
c.C::gunc(); c.gunc();
c.hunc1();
c.hunc2(); /** 输出结果
A func
B func
B gunc
C gunc C gunc // 如果基类中的名字在派生类中再次声明, 则基类中的名字就被隐藏. 如果我们想要访问被隐藏的基类中的成员则使用作用域分辨符B::gunc();
A func
B func
*/
}复制代码

格式: 类名::标识符

:: 为作用域分辨符, "类名"可以是任一基类或派生类名, “标识符”是该类中声明的任一成员名

派生类支配基类的同名函数

如果派生类定义了一个同基类成员函数同名的新成员函数(具有相同参数表的成员函数), 派生类的新成员函数就覆盖了基类的同名成员函数.

在这里, 直接使用成员名只能访问派生类中的成员函数, 使用作用域运算符, 才能访问基类的同名成员函数.

派生类中的成员函数名支配基类中的同名的成员函数名, 这称为名字支配规则.

如果一个名字支配另一个名字, 则二者之间不存在二义性, 当选择该名字时, 使用支配者的名字.

例如上个例子中

c.gunc() // 输出”C gunc”, 基类B中的gunc成员函数被支配了
c.B::gunc(); // 加上作用域分辨符, 来使用被支配的成员复制代码

来自一张表的总结

总结

C++中的多重继承可能更灵活, 并且支持三种派生方式, 我们在学习一门语言的时候, 更应该把精力放在它的特性上面, 不应该用什么语言, 都用自己所擅长语言的思考方式, 实现方式等, 要学会发挥该语言的优势所在.

自学C/C++编程难度很大,不妨和一些志同道合的小伙伴一起学习成长!

C语言C++编程学习交流圈子,【点击进入微信公众号:C语言编程学习基地

有一些源码和资料分享,欢迎转行也学习编程的伙伴,和大家一起交流成长会比自己琢磨更快哦!

C/C++编程笔记:C++入门知识丨继承和派生的更多相关文章

  1. C/C++编程笔记:C++入门知识丨从结构到类的演变

    先来看看本节知识的结构图吧! 接下来我们就逐步来看一下所有的知识点: 结构的演化 C++中的类是从结构演变而来的, 所以我们可以称C++为”带类的C”. 结构发生质的演变 C++结构中可以定义函数, ...

  2. C/C++编程笔记:C++入门知识丨运算符重载

    本篇要学习的内容和知识结构概览 运算符重载使用场景 常规赋值操作 我们现在有一个类 想要实现这种赋值操作 具体实现如下: 所以说呢,我们在使用运算符进行运算的时候, 实际上也是通过函数来实现运算的. ...

  3. C/C++编程笔记:C++入门知识丨多态性和虚函数

    本篇要学习的内容和知识结构概览 多态性 编译时的多态性称为静态联编. 当调用重载函数时, 在编译期就确定下来调用哪个函数. 运行时的多态性称为动态联编. 在运行时才能确定调用哪个函数, 由虚函数来支持 ...

  4. C/C++编程笔记:C++入门知识丨类和对象

    本篇要学习的内容和知识结构概览 类及其实例化 类的定义 将一组对象的共同特征抽象出来, 从而形成类的概念. 类包括数据成员和成员函数, 不能在类的声明中对数据成员进行初始化 声明类 形式为: clas ...

  5. C/C++编程笔记:C++入门知识丨函数和函数模板

    本篇要学习的内容和知识结构概览 函数的参数及其传递方式 1. 函数参数传递方式 传值: 传变量值: 将实参内存中的内容拷贝一份给形参, 两者是不同的两块内存 传地址值: 将实参所对应的内存空间的地址值 ...

  6. C/C++编程笔记:C++入门知识丨认识C++面向过程编程的特点

    一. 本篇要学习的内容和知识结构概览 二. 知识点逐条分析 1. 使用函数重载 C++允许为同一个函数定义几个版本, 从而使一个函数名具有多种功能, 这称之为函数重载. 像这样: 虽然函数名一样, 但 ...

  7. C/C++编程笔记:C++入门知识丨认识C++的函数和对象

    一. 本篇要学习的内容和知识结构概览 二. 知识点逐条分析 1. 混合型语言 C++源文件的文件扩展名为.cpp, 也就是c plus plus的简写, 在该文件里有且只能有一个名为main的主函数, ...

  8. UnityShader学习笔记1 — — 入门知识整理

    注:资料整理自<Unity Shader入门精要>一书 一.渲染流程概念阶段:  应用阶段:(1)准备好场景数据:(如摄像机位置,物体以及光源等)   (2)粗粒度剔除(Culling): ...

  9. AngularJs学习笔记1——入门知识

    1.什么是AngularJs          AngularJs 诞生于2009年,由Misko Hevery 等人创建,后被Google收购,是一个优秀的Js框架,用于SPA(single pag ...

随机推荐

  1. TensorFlow中的显存管理器——BFC Allocator

    背景 作者:DeepLearningStack,阿里巴巴算法工程师,开源TensorFlow Contributor] 使用GPU训练时,一次训练任务无论是模型参数还是中间结果都需要占用大量显存.为了 ...

  2. swiper的自适应高度问题

    #swiper的自适应高度问题 ​ 众所周知,swiper组件的元素swiper-item是设置了绝对定位的,所以里面的内容是无法撑开swiper的,并且给swiper盒子设置overflow:vis ...

  3. React当中的路由使用

    React 当中的路由 使用React构建的单页面应用,要想实现页面间的跳转,首先想到的就是使用路由.在React中,常用的有两个包可以实现这个需求,那就是react-router和react-rou ...

  4. 一文搞懂Python函数(匿名函数、嵌套函数、闭包、装饰器)!

    Python函数定义.匿名函数.嵌套函数.闭包.装饰器 目录 Python函数定义.匿名函数.嵌套函数.闭包.装饰器 函数核心理解 1. 函数定义 2. 嵌套函数 2.1 作用 2.2 函数变量作用域 ...

  5. MISC学习记录 (一)

    A记录 题目 他在看什么视频,好像很好看,不知道是什么网站的. 还好我截取了他的数据包,找呀找. key就是网站名称.格式ctf{key} tip:A记录的第一条. 解题链接 过程 下载解题链接中的文 ...

  6. Apache Avro & Avro Schema简介

    为什么需要schema registry? 首先我们知道: Kafka将字节作为输入并发布 没有数据验证 但是: 如果Producer发送了bad data怎么办? 如果字段被重命名怎么办? 如果数据 ...

  7. ScheduledThreadPoolExecutor源码主要部分解析

    ScheduledThreadPoolExecutor继承与基础线程池类ThreadPoolExecutor并实现ScheduledExecutorService接口. 其中ScheduledExec ...

  8. As 布局文件太多很乱的问题

    //添加自定义文件整理文件夹的方法,没有之一在build.gradle(Module: app)里加入布局需要放入的路径代码>>>>>> sourceSets { ...

  9. CSS上划线、下划线、删除线等方法

    text-decoration:underline;     下划线 text-decoration:overline;    顶划线 text-decoration:line-through;  删 ...

  10. OSCP Learning Notes - Buffer Overflows(4)

    Finding the Right Module(mona) Mona Module Project website: https://github.com/corelan/mona 1. Downl ...