C++学习笔记:07 类的继承与派生
基本概念
继承与派生的区别:
- 继承:保持已有类的特性而构造新类的过程称为继承。
- 派生:在已有类的基础上新增自己的特性(函数方法、数据成员)而产生新类的过程称为派生
被继承的已有类称为基类,派生出的新类称为派生类,直接参与派生出某类的基类称为直接基类,基类的基类甚至更高层的基类称为间接基类。
继承与派生的目的
- 继承的目的:实现设计与代码的重用。
- 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对
原有程序的基础上增加新的特性。
继承类的定义语法
单继承
单继承的直接基类只有一个,定义时需要指定基类的继承方式,继承方式包括:private、public、protected三种
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...{
成员声明;
}
多继承
多继承的直接基类有多个,定义时需要为每个直接基类指定继承方式。
class 派生类名:继承方式 基类名{
成员声明;
}
派生类的构成
派生类的类成员包括三个部分:
吸收基类成员
默认情况下派生类包含了全部基类中除构造和析构函数之外的所有成员。
改造基类成员
如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或
覆盖了外层同名成员添加新的成员
派生类中可以增加新数据成员与函数方法。
不同继承方式的区别
三种继承方法(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) {}
构造函数的执行顺序
调用基类构造函数。
顺序按照它们被继承时声明的顺序(从左向右)。
对初始化列表中的成员进行初始化。
顺序按照它们在类中定义的顺序。
对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。执行派生类的构造函数体中的内容。
派生类复制构造函数
派生类未定义复制构造函数的情况
编译器会在需要时生成一个隐含的复制构造函数先调用基类的复制构造函数
再为派生类新增的成员执行复制。
例如:
以下例子中的复制构造函数传参
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 类的继承与派生的更多相关文章
- Java学习笔记 07 接口、继承与多态
一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...
- C++学习笔记5——类的继承
简介: 通过继承联系在以前的类构成一种层次关系.通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承,这些继承得到的类称为类的派生类. 作用: 1.子类拥有父类的所有成员函数和成员变量. 2 ...
- 学习笔记 07 --- JUC集合
学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...
- python学习笔记4_类和更抽象
python学习笔记4_类和更抽象 一.对象 class 对象主要有三个特性,继承.封装.多态.python的核心. 1.多态.封装.继承 多态,就算不知道变量所引用的类型,还是可以操作对象,根据类型 ...
- C++ 学习笔记 (七)继承与多态 virtual关键字的使用场景
在上一篇 C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量 中说了当父类子类有同名函数时在外部调用时如果不加父类名则会默认调用子类的函数.C++有函数重写的功能需要添加virtual关键字 ...
- C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量
学习了类的继承,今天说一下当父类与子类中有同名函数和变量时那么程序将怎么执行.首先明确当基类和子类有同名函数或者变量时,子类依然从父类继承. 举例说明: 例程说明: 父类和子类有同名的成员 data: ...
- UML学习笔记:类图
UML学习笔记:类图 有些问题,不去解决,就永远都是问题! 类图 类图(Class Diagrame)是描述类.接口以及它们之间关系的图,用来显示系统中各个类的静态结构. 类图包含2种元素:类.接口, ...
- 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承
<Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...
- Java学习笔记——File类之文件管理和读写操作、下载图片
Java学习笔记——File类之文件管理和读写操作.下载图片 File类的总结: 1.文件和文件夹的创建 2.文件的读取 3.文件的写入 4.文件的复制(字符流.字节流.处理流) 5.以图片地址下载图 ...
随机推荐
- swagger2 注解说明文档
@Api:用于类上,说明该类的作用.可以标记一个Controller类做为swagger 文档资源 @Api(value = "xxx", description = " ...
- 【AE】多表的联合查询
多表的联合查询 // Create the query definition. IQueryDef queryDef = featureWorkspace.CreateQueryDef(); // P ...
- STM32+Air202+Air530+HXDZ-30102-ACC心率血氧GPS采集上传到阿里云
所有资料都在QQ群1121445919 主要功能 HXDZ-30102-ACC采集心率血氧数据 STM32通过串口将数据转发到air202模块 air202将数据上传到阿里云平台进行展示与处理 整合合 ...
- Java静态内部类——重点是精妙的demo
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/cd18333612683/article/details/79129503什么是内部类大部分时候,类 ...
- Map 综述(四):彻头彻尾理解 HashTable
摘要: Hashtable与HashMap都是Map族中较为常用的实现,也都是Java Collection Framework 的重要成员,它们的本质都是 链表数组.本文深入JDK源码并从定义.构造 ...
- 关于MYSQL5.7:Access denied for user 'root'@'localhost' (using password:YES)解决
这一类解决要提供远程服务,需要进入mysql的my.ini文件中进行修改,但是在win10系统中,my.ini不再放在MYSQL安装目录的根目录中了,需要到一类应用缓存目录中寻找MYSQL的详细配置文 ...
- JAVAWEB开发批量删除,SSM的几种情况
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 整理之BroadcaseReceiver
广播的分类 有序广播:按接收器优先级从高到低接受消息,一次只能有一个接收器处理消息.中途可以被截断. 无序广播:所有接收器同时接受消息并处理,无法拦截. 本地广播:只能在本应用内传播的无需广播.上面两 ...
- 从零开始实现简单 RPC 框架 8:网络通信之 Request-Response 模型
Netty 在服务端与客户端的网络通信中,使用的是异步双向通信(双工)的方式,即客户端和服务端可以相互主动发请求给对方,发消息后不会同步等响应.这样就会有一下问题: 如何识别消息是请求还是响应? 请求 ...
- shell运算方式
1.(())--整数运算 [root@m01 /server/scripts]# a=1 [root@m01 /server/scripts]# b=2 [root@m01 /server/scrip ...