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

继承和派生的概念

派生

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

继承

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

继承派生的作用

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

单一继承

一般形式

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. c# 操作Redis的五种基本类型总结

    在我们的项目中,通常会把数据存储到关系型数据库中,比如Oracle,SQL Server,Mysql等,但是关系型数据库对于并发的支持并不是很强大,这样就会造成系统的性能不佳,而且存储的数据多为结构化 ...

  2. 使用Python编写的对拍程序

    简介 支持数据生成程序模式, 只要有RE或者WA的数据点, 就会停止 支持数据文件模式, 使用通配符指定输入文件, 将会对拍所有文件 结束后将会打印统计信息 第一次在某目录执行,将会通过交互方式获取配 ...

  3. web 基础(二) HTML5

    web 基础(二) HTML5 一.HTML5 HTML5 是最新的 HTML 标准.是专门为承载丰富的 web 内容而设计的,并且无需额外插件.它拥有新的语义.图形以及多媒体元素.并提供的新元素和新 ...

  4. day45 如何完全删除mysql服务

    卸载mysql之后,mysql的服务无法删除 解决方案 在我们在卸载mysql后会有一些东西没有删除干净,当我们把这些内容清除干净后,服务自然就消失了 步骤一: 如果是默认安装的话 在这三个文件内都有 ...

  5. 谈谈你对 TCP 三次握手和四次挥手的理解

    TCP三次握手: 1.客户端发送syn包到服务器,等待服务器确认接收. 2.服务器确认接收syn包并确认客户的syn,并发送回来一个syn+ack的包给客户端. 3.客户端确认接收服务器的syn+ac ...

  6. 通过Windows Visual Studio远程调试WSL2中的.NET Core Linux应用程序

    最近两天在Linux中调试.NET Core应用程序,同时我发现在Linux中调试.NET Core应用程序并不容易.一直习惯在Visual Studio中进行编码和调试.现在我想的是可以简单快速的测 ...

  7. Flask 基础组件(九):请求扩展

    #!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, Request, render_template app = ...

  8. 微信小程序接口封装、原生接口封装、request、promise封装

    相信大家在做微信小程序的时候会有很多地方需要调用接口,就和pc以及手机端一样,多个页面多次调用会有很多状态,那为了节省大家的开发时间就会需要给请求的接口做一些简单封装,便于开发,在这里我用了两个js, ...

  9. IOS中input键盘事件keyup 的兼容解决办法

    用input监听键盘keyup事件,在安卓手机浏览器中是可以的,但是在ios手机浏览器中很慢,用输入法输入之后,并未立刻相应keyup事件. 解决办法: 在ios设备上可以用html5的input事件 ...

  10. git只操作某个文件夹

    在我们的工作中,可能会有这样的情况发生:我只想提交某一个文件夹,而另外的文件夹我并不想提交. 遇到上述情况,我们再git中这样解决: 1.查看某个文件夹的状态(这里我用log文件夹做实验). 我们可以 ...