课程《C++语言程序设计进阶》清华大学 郑莉老师)

基本概念

继承派生的区别:

  • 继承:保持已有类的特性而构造新类的过程称为继承。
  • 派生:在已有类的基础上新增自己的特性(函数方法、数据成员)而产生新类的过程称为派生

被继承的已有类称为基类派生出的新类称为派生类,直接参与派生出某类的基类称为直接基类,基类的基类甚至更高层的基类称为间接基类

继承与派生的目的

  • 继承的目的:实现设计与代码的重用。
  • 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对

    原有程序的基础上增加新的特性。

继承类的定义语法

单继承

单继承的直接基类只有一个,定义时需要指定基类的继承方式,继承方式包括:private、public、protected三种

class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...{
成员声明;
}

多继承

多继承的直接基类有多个,定义时需要为每个直接基类指定继承方式。

class 派生类名:继承方式 基类名{
成员声明;
}

派生类的构成

派生类的类成员包括三个部分:

  1. 吸收基类成员

    默认情况下派生类包含了全部基类中除构造和析构函数之外的所有成员。

  2. 改造基类成员

    如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或

    覆盖了外层同名成员

  3. 添加新的成员

    派生类中可以增加新数据成员与函数方法。

不同继承方式的区别

三种继承方法(private,protected,public)不同继承方式的影响主要体现在:

  • 派生类成员基类成员的访问权限
  • 通过派生类对象基类成员的访问权限

公有继承(public)

  • 继承的访问控制

    • 基类的public和protected成员:访问属性在派生类中保持不变;
    • 基类的private成员:不可以直接访问。
  • 访问权限

    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:只能访问public成员.

例如:

#include <iostream>
using namespace std;
class Point {
private:
float x, y;
public:
Point(float x = 0, float y = 0) :x(x), y(y) {}
void move(float offX, float offY) { x += offX; y += offY; }
float getX() const { return x; }
float getY() const { return y; }
}; class Rectangle :public Point {
private:
float w, h; //新增数据成员
public:
//新增函数成员
Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
float getH() const { return h; }
float getW() const { return w; }
}; int main() {
Rectangle rect(2, 3, 20, 10);
rect.move(-2, -3);
cout << "The date of rect(x,y,w,h):" << endl;
cout << rect.getX() << endl;
cout << rect.getY() << endl;
cout << rect.getW() << endl;
cout << rect.getH() << endl;
//cout << rect.x << endl; /*不可访问基类私有成员*/
}

私有继承(private)与保护继承(protected)

三类继承方式中,public:派生类以及派生类对象都可以访问public成员,private:只有基类自身函数可以访问,派生类以及派生类对象都不可以访问private成员,protected:派生类成员可以访问,但派生类对象不可以访问protected成员。

基类 派生类 派生类对象
public
protected ×
private × ×

私有继承(private)

  • 继承的访问控制

    • 基类的public和protected成员:都以private身份出现在派生类中;
    • 基类的private成员:不可直接访问。
  • 访问权限

    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:不能直接访问从基类继承的任何成员

例如:

#include <iostream>
using namespace std;
class Point {
private:
float x, y;
public:
Point(float x = 0, float y = 0) :x(x), y(y) {}
void move(float offX, float offY) { x += offX; y += offY; }
float getX() const { return x; }
float getY() const { return y; }
}; class Rectangle :private Point {
private:
float w, h;
public:
Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
float getH() const { return h; }
float getW() const { return w; }
}; int main() {
Rectangle rect(2, 3, 20, 10);
//rect.move(-2, -3); /*不可访问基类私有成员*/
cout << "The date of rect(x,y,w,h):" << endl;
//cout << rect.getX() << endl; /*不可访问基类私有成员*/
//cout << rect.getY() << endl; /*不可访问基类私有成员*/
cout << rect.getW() << endl;
cout << rect.getH() << endl;
/*不可访问基类私有成员*/
}

保护继承(protected)

  • 继承的访问控制

    • 基类的public和protected成员:都以protected身份出现在派生类中;
    • 基类的private成员:不可直接访问。
  • 访问权限
    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:不能直接访问从基类继承的任何成员
  • protected 成员的特点与作用
    • 对建立其所在类对象的模块来说,它与private成员的性质相同**。
    • 对于其派生类来说,它与 public 成员的性质相同
    • 既实现了数据隐藏,又方便继承,实现代码重用。

基类与派生类之间的类型转换

公有派生类对象可以被当作基类的对象使用。

  • 派生类的对象可以隐含转换为基类对象;

  • 派生类的对象可以初始化基类的引用

  • 派生类的指针可以隐含转换为基类的指针。

通过基类对象名、指针只能使用从基类继承的成员。

#include <iostream>
using namespace std;
class Base1 {
public:
void display() const { cout << "Base1::display()" << endl; }
};
class Base2 :public Base1 {
public:
void display() const { cout << "Base2::display()" << endl; }
};
class Derived :public Base2 {
public:
void display() const { cout << "Derived::display()" << endl; }
};
void fun(Base1* ptr) { ptr->display(); }
int main() {
Base1 base1;
Base2 base2;
Derived derived;
fun(&base1);
fun(&base2);
fun(&derived);
return 0;
}
/*
Base1::display()
Base1::display()
Base1::display()
*/

void fun(Base1* ptr)函数中传入参数为基类指针,派生类的指针可以隐含转换为基类的指针。但是以上程序结果最终只调用基类display()函数。可以使用virtual定义虚函数,实现多态特性。

派生类的构造函数

继承时,默认规则

  • 基类的构造函数不被继承
  • 派生类需要定义自己的构造函数

如果派生类有自己新增的成员,且需要通过构造函数初始化,则派生类要自定义构造函数

派生类成员初始化:

派生类新增成员:派生类定义构造函数初始化

继承来的成员:自动调用基类构造函数进行初始化

派生类的构造函数需要给基类的构造函数传递参数

例如:

先前的Point、Rectangle例子中,Rectangle类的构造函数,传递参数至Point类构造函数Point(x, y),对基类成员进行初始化

Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}

构造函数的执行顺序

  1. 调用基类构造函数。

    顺序按照它们被继承时声明的顺序(从左向右)。

  2. 对初始化列表中的成员进行初始化。

    顺序按照它们在类中定义的顺序

    对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。

  3. 执行派生类的构造函数体中的内容。

派生类复制构造函数

派生类未定义复制构造函数的情况

编译器会在需要时生成一个隐含的复制构造函数先调用基类的复制构造函数

再为派生类新增的成员执行复制。

例如:

以下例子中的复制构造函数传参

Rectangle(const Rectangle& rect) :Point(rect) { this->w = rect.w; this->h = rect.h; }
#include <iostream>
using namespace std;
class Point {
private:
float x, y;
public:
Point(float x = 0, float y = 0) :x(x), y(y) {}
Point(const Point& p) { this->x = p.x; this->y = p.y; }//复制构造函数
void move(float offX, float offY) { x += offX; y += offY; }
float getX() const { return x; }
float getY() const { return y; }
}; class Rectangle :public Point {
private:
float w, h;
public:
Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
Rectangle(const Rectangle& rect) :Point(rect) { this->w = rect.w; this->h = rect.h; }//复制构造函数
float getH() const { return h; }
float getW() const { return w; }
friend ostream& operator << (ostream& os, const Rectangle& rect) {
os << "Center:(" << rect.getX() << "," << rect.getY() << ")" << endl;
os << "h=" << rect.getH() << endl
<< "w=" << rect.getW() << endl;
return os;
}
};
int main() {
Rectangle rect(2, 3, 20, 10);
Rectangle rect1(rect);
rect.move(-2, -3);
cout << "rect" << endl << rect;
cout << "rect1" << endl << rect1;
}
/*
rect
Center:(0,0)
h=10
w=20
rect1
Center:(2,3)
h=10
w=20
*/

派生类析构函数

析构函数不被继承,派生类如果需要,要自行声明析构函数。

声明方法与无继承关系时类的析构函数相同。

不需要显式地调用基类的析构函数,系统会自动隐式调用。

先执行派生类析构函数的函数体,再调用基类的析构函数。

例如:

以下例子中,派生类Derived的对象obj,按照继承顺序,依次调用构造函数,最后是自己的构造函数,而销毁对象按照相反的顺序执行析构函数。

#include <iostream>
using namespace std;
class Base1 {
public:
Base1(int i){cout << "Constructing Base1 " << endl;}
~Base1() { cout << "Destructing Base1" << endl; }
};
class Base2 {
public:
Base2(int j){cout << "Constructing Base2 " << endl;}
~Base2() { cout << "Destructing Base2" << endl; }
};
class Base3 {
public:
Base3() { cout << "Constructing Base3" << endl; }
~Base3() { cout << "Destructing Base3" << endl; }
};
class Derived : public Base2, public Base1, public Base3 {
public:
Derived(int a, int b, int c, int d) : Base1(a), member2(d), member1(c), Base2(b){ }
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main() {
Derived obj(1, 2, 3, 4);
return 0;
}
/*
Constructing Base2
Constructing Base1
Constructing Base3
Constructing Base1
Constructing Base2
Constructing Base3
Destructing Base3
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base1
Destructing Base2
*/

派生类成员二义性问题

二义性指的是在派生类中存在与基类拥有相同名字的成员。可以通过使用类名限定的方式进行特定访问

例如:

#include <iostream>
using namespace std;
class Base1 {
public:
int var;
void fun() { cout << "Member of Base1" << endl; }
};
class Base2 {
public:
int var;
void fun() { cout << "Member of Base2" << endl; }
};
class Derived : public Base1, public Base2 {
public:
int var;
void fun() { cout << "Member of Derived" << endl; }
};
int main() {
Derived d;
Derived* p = &d;
//访问Derived类成员
d.var = 1;
d.fun();
//访问Base1基类成员
d.Base1::var = 2;
d.Base1::fun();
//访问Base2基类成员
p->Base2::var = 3;
p->Base2::fun();
return 0;
}
/*
Member of Derived
Member of Base1
Member of Base2
*/

虚基类(个人一般不常用)

需要解决的问题

当派生类从多个基类派生,而这些基类又共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性。

虚基类声明

以virtual说明基类继承方式class B1:virtual public B

作用:主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题,为最远的派生类提供唯一的基类成员,而不重复产生多次复制

注意:在第一级继承时就要将共同基类设计为虚基类

类的存储

参考B站的一个视频:链接

#include <iostream>
using namespace std;
class Base {
public:
//int var; //4字节
float x; //4字节
double y;//8字节
static int a; //静态成员不占用类空间
void fun() { cout << "Member of Base1" << endl; } //函数不占字节
void virtual fun1() {};//虚函数,相当于一个指向虚表的指针 4字节
};
int main() {
Base base;
cout << sizeof(base);
return 0;
}
/*
24
*/

C++学习笔记:07 类的继承与派生的更多相关文章

  1. Java学习笔记 07 接口、继承与多态

    一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...

  2. C++学习笔记5——类的继承

    简介: 通过继承联系在以前的类构成一种层次关系.通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承,这些继承得到的类称为类的派生类. 作用: 1.子类拥有父类的所有成员函数和成员变量. 2 ...

  3. 学习笔记 07 --- JUC集合

    学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...

  4. python学习笔记4_类和更抽象

    python学习笔记4_类和更抽象 一.对象 class 对象主要有三个特性,继承.封装.多态.python的核心. 1.多态.封装.继承 多态,就算不知道变量所引用的类型,还是可以操作对象,根据类型 ...

  5. C++ 学习笔记 (七)继承与多态 virtual关键字的使用场景

    在上一篇 C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量 中说了当父类子类有同名函数时在外部调用时如果不加父类名则会默认调用子类的函数.C++有函数重写的功能需要添加virtual关键字 ...

  6. C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量

    学习了类的继承,今天说一下当父类与子类中有同名函数和变量时那么程序将怎么执行.首先明确当基类和子类有同名函数或者变量时,子类依然从父类继承. 举例说明: 例程说明: 父类和子类有同名的成员 data: ...

  7. UML学习笔记:类图

    UML学习笔记:类图 有些问题,不去解决,就永远都是问题! 类图 类图(Class Diagrame)是描述类.接口以及它们之间关系的图,用来显示系统中各个类的静态结构. 类图包含2种元素:类.接口, ...

  8. 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承

    <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...

  9. Java学习笔记——File类之文件管理和读写操作、下载图片

    Java学习笔记——File类之文件管理和读写操作.下载图片 File类的总结: 1.文件和文件夹的创建 2.文件的读取 3.文件的写入 4.文件的复制(字符流.字节流.处理流) 5.以图片地址下载图 ...

随机推荐

  1. 【转】互联网项目中mysql应该选什么事务隔离级别

    作者:孤独烟 转自:https://www.cnblogs.com/rjzheng/p/10510174.html 摘要 企业千万家,靠谱没几家.社招选错家,亲人两行泪. 祝大家金三银四跳槽顺利! 引 ...

  2. Saruman's Army

    直线上有N个点. 点i的位置是Xi.从这N个点中选择若干个,给它们加上标记. 对每一个点,其距离为R以内的区域里必须有带有标记的点(自己本身带有标记的点, 可以认为与其距离为 0 的地方有一个带有标记 ...

  3. Spring parent 属性

    Spring Framework Reference Documentation 6.7. Bean definition inheritance 注:本文中bean和definition意思等同 该 ...

  4. servlet中servletContext的五大作用(二)

    1.    获取web的上下文路径 2.    获取全局的参数 3.    作为域对象使用 4.    请求转发 5.    读取web项目的资源文件 package day10.about_serv ...

  5. linux centos7 模拟垃圾回收站功能以及 crontab 定时任务的设置

    2021-08-04 1. 安装 环境:CentOS Linux release 7.5.1804 (Core) # 将 saferm.sh 拷贝到 /bin 目录下面 git clone git:/ ...

  6. vue 引入 echarts 图表 并且展示柱状图

    npm i echarts -S 下载 echarts 图表 mian.js 文件 引入图表并且全局挂载 //echarts 图表 import echarts from 'echarts' Vue. ...

  7. Spring系列之JDBC对不同数据库异常如何抽象的?

    前言 使用Spring-Jdbc的情况下,在有些场景中,我们需要根据数据库报的异常类型的不同,来编写我们的业务代码.比如说,我们有这样一段逻辑,如果我们新插入的记录,存在唯一约束冲突,就会返回给客户端 ...

  8. 实例_ Java中的代理模式

    静态代理 我们定义一个接口,并且使用代理模式,想要做到的就是在调用这个接口的实现类时在此方法上添加功能. public interface HelloInterface { void sayHello ...

  9. python使用UTF-8写入CSV中文乱码

    使用encoding='utf-8',写入的文档是乱码. 解决办法: 修改encoding='utf-8-sig' 关于文件open()函数: open(path,'-模式-',encoding='u ...

  10. 《通过刷leetcode学习Go语言》之(1):序言

    Author       : Email         : vip_13031075266@163.com Date          : 2021.03.07 Version     : 北京 C ...