C++程序设计——继承与派生

news2024/11/25 7:11:58

更多内容可以查看系列文章C++语言入门全教程(持续更新)

目录

前言

一、继承的概念

1.楔子

2.派生类的定义

3.继承和派生的意义 

4.案例1:派生类的定义

二、继承方式

1.公有继承(public)

2.保护继承(protected)

3.私有继承(private)

4.protected成员的特点与作用 

5.公有继承举例 

6.私有继承举例

三、继承中的构造与析构函数

1.例① 

2.例②

3.例③ 

四、其他 

1.组合与继承

2.基类与派生类之间的转化

3.派生类中重定义基类的函数

五、继承与派生案例 

总结 


前言

界 - 门 - 纲 - 目 - 科 - 属 - 种 ~

有一个笑话:医生让病人多吃水果,看着家人送来的梨、苹果和橘子,他不解的问,医生说的是“水果”啊,怎么是“梨”“苹果”“橘子”呢?

这就是哲学上的共性与个性,矛盾的普遍性与特殊性原理——当然,此篇博客并不是哲学的复习提纲,而本篇博客的内容,继承与派生,确乎与之密不可分。


一、继承的概念

1.楔子

虽然前言有一定程度的引入,然而详细地说明继承的概念以及使用继承的原因是非常必要的。

正如之前所说的,类似于我们所说的界门纲目科属种,对一种生物,从不同的层次我们可以进行大量的分类,两种动物,虽然不同,但从种往上,至少从一个层级开始(界、门、纲、目、科、属)都是一致的——至少都是动物界 。

换句话说,不同肯定存在但是,完全不同的两件事物也是不存在的——又哲学了。

继承的使用,可以形象地说,是抓住了某些对象,即类,特性中共同的部分。这些子类从母类中诞生而来,保留了母类中共同的部分,同时也灵活地接受各自的不同。——这些共同特性因为一些原因需要修改时,只需要修改母类中的定义即可,而不需要对所有类的共同部分进行冗余的修改。

进一步举例说明: 

现实世界中,实体之间普遍存在着”是(is_a)”的关系

  • 圆,三角形,长方形都是平面图形

对具有共性特征的类,如何避免对共性部分的重复定义

  • 圆,三角形,长方形都包含坐标位置数据,以及求面积函数

如何统一管理所有具有共性特征的对象

  • 如何统一管理所有的平面图形

这也就引申出继承和派生了。

2.派生类的定义

class 派生类名: 继承方式  基类名1, …
{
        成员定义;
};
  • 基类与派生类的关系是相对的
  • 多继承:一个派生类有多个直接基类
  • 单继承:一个派生类只有一个直接基类

就楔子部分的交通工具一例来说,具体程序可以这样设计:

Car 类只需要定义 Car::braking() 方法
Ship 类只需要定义 Ship::porting() 方法
Airplane 类只需要定义 Airplane::landing() 方法

3.继承和派生的意义 

  • 通过继承,可以从已有的类派生出新类,新类在已有类的基础上新增自己的特性
  • 被继承的已有类称为基类(父类),派生出的新类称为派生类(子类)
  • 继承可以减少代码的冗余性,实现代码的重用,并且通过作少量的修改,满足不断变化的具体应用要求,提高程序设计的灵活性 

4.案例1:派生类的定义

class Student
{
protected:
	int number;
	char name[20];
	float score;
public:
	Student(int num=0, char *p="no name")
	{
		number = num;
		strcpy(name, p);
	}
	void SetScore(float s=0.0) 
	{	score=s;	}
};

class GraduateStudent : public Student
{
protected:
	char advisor[20];
public:
	void SetAdvisor(char *p) {strcpy(advisor,p);}
	char* GetAdvisor() {return advisor;}
};

void main()
{
	Student s1(1001, "Lucy");
	GraduateStudent s2;
	s2.SetScore(85.0);
	s2.SetAdvisor("Mary");
}


二、继承方式

继承方式指定了派生类成员以及类外对象对继承来的成员的访问权限,有以下三种继承方式

  1. 公有继承(public)
  2. 保护继承(protected)
  3. 私有继承(private)

1.公有继承(public)

  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员
  • 将基类的protected区成员继承到派生类的protected区,基类的public区的成员继承到派生类的public区
  • 外部函数中,派生类的对象只能访问基类的public成员

2.保护继承(protected)

  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员
  • 将基类的protected区和public区的所有成员都继承到派生类的protected区
  • 外部函数中,派生类的对象不能访问基类中的任何成员

3.私有继承(private)

  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能访问基类的private成员
  • 将基类的protected区和public区的所有成员都继承到派生类的private区
  • 外部函数中,派生类的对象不能访问基类中的任何成员

4.protected成员的特点与作用 

class A 
{
 protected:
     int x;
};
int main()
{
      A a;
      a.x=5;    //错误
}
class A 
{
protected:
	int x;
};
class B : public A
{
public:
	void Function();
};
void B :: Function()
{
      x=5;   //正确
}

 派生类可访问基类protected成员,外部函数不可访问基类内部protected成员

5.公有继承举例 

class Location
{
public:
	void InitL(int xx,int yy) {X=xx; Y=yy;}
	void Move(int xOff,int yOff) {X+=xOff; Y+=yOff;}
	int GetX() {return X;}
	int GetY() {return Y;}
private:
	int X,Y;
};

class Rectangle : public Location  //派生类
{
public:
	void InitR(int x,int y,int w,int h);
	int GetH() {return H;}
	int GetW() {return W;}
private:
	int W,H;
};

inline void Rectangle::InitR(int x,int y,int w,int h)
{
 	InitL(x,y);   //派生类直接访问基类的公有成员
	W=w;
	H=h;
}

int main()
{
	Rectangle rect;
	rect.InitR(2,3,20,10);
	rect.Move(3,2); 		//对象访问基类的公有成员
	cout<<rect.GetX()<<','   //对象访问基类的公有成员
	      <<rect.GetY()<<','
		<<rect.GetH()<<','
		<<rect.GetW()<<endl;
	return 0;
}

6.私有继承举例

class Rectangle : private Location  
{
public:
	void InitR(int x,int y,int w,int h);
	void Move(int xOff,int yOff);
	int GetX();//因为基类对应函数继承到private类,因此需要重定义,也允许重定义
	int GetY();
	int GetH() {return H;}
	int GetW() {return W;}
private:
	int W,H;
};

inline void Rectangle::InitR(int x,int y,int w,int h)
{
  	InitL(x,y);   //派生类直接访问原公有成员
	W=w;
	H=h;
}

void Rectangle::Move(int xOff,int yOff) //!!!
{	Location::Move(xOff,yOff);	}

int Rectangle::GetX()                   //!!! 
{	return Location::GetX();	}  

int Rectangle::GetY()                   //!!!
{	return Location::GetY();		}

int main()
{
	Rectangle rect;
	rect.InitR(2,3,20,10);
	rect.Move(3,2); 		//对象访问派生类的函数
	cout<<rect.GetX()<<‘,‘	//对象访问派生类的函数
	      <<rect.GetY()<<','
		<<rect.GetH()<<','
		<<rect.GetW()<<endl;
	return 0;
}

三、继承中的构造与析构函数

  • 派生类不继承基类的构造函数和析构函数,但是能调用基类的构造函数和析构函数
  • 派生类的构造函数总是先调用基类的构造函数来初始化派生类中的基类成员,再进行派生类中成员的初始化
  • 派生类构造函数的定义中,要提供基类构造函数所需要的参数
  • 如果派生类没有用户自定义的构造函数,执行其默认构造函数时,首先调用基类的构造函数
  • 析构函数的调用顺序和构造函数的调用顺序相反
  • 子类的构造函数要有一个默认的父类的构造函数对应

下面用三个例子说明。 

1.例① 

//----------------------------------Base类声明
class Base
{				
public:
	Base(int i);
	~Base();
	void print();
private:
	int a;
};
//----------------------------------Derive类声明
class Derive : public Base
{
public:
	Derive(int i, int j);
	~Derive();
	void print();
private:
  int b;
};
//----------------------------------Base成员函数定义
Base::Base(int i)
{ 
	a=i;
	cout<< "Base constructor" <<endl;
}
Base::~Base()
{	
	cout<< "Base destructor" <<endl;
}
void Base::print()
{
	cout<< a << endl; 
}
//----------------------------------Derive成员函数定义
Derive::Derive(int i, int j) : Base(i)
{ 
	b=j;
	cout<< "Derive constructor" <<endl;
}
Derive::~Derive()
{	
	cout<< "Derive destructor" <<endl;
}
void Derive::print()
{ 
	Base::print();
	cout<< b << endl; 
}
//----------------------------------主函数
void main()
{
	Derive d(2,5);
	d.print();
}

输出结果:

Base constructor

Derive constructor

2

5

Derive destructor

Base destructor

2.例②

在例①中进行相应的修改 

class Base
{				
public:
	Base();
	Base(int i);
......
};

class Derive : public Base
{
public:
	Derive(int i, int j){}
......
};

此时可选择不带参数的构造函数

3.例③ 

class Base
{					
public:
	Base(){ cout<< "Base constructor" <<endl;}
	~Base(){	cout<< "Base destructor" <<endl;}
	void print();
};

class Derive : public Base
{
public:
	void set(int i) { b=i; }
	void print(){ cout<< b << endl; }
private:
  int b;
};
void main()
{
	Derive d;
	d.set(2);
	d.print();
}

结果输出: 

Base constructor

2

Base destructor

说明调用了基类的构造函数和析构函数。 


四、其他 

1.组合与继承

  • 组合(has_a):一个类的数据成员是另一个类的对象,其实就是复合(详见类的复合)
  • 继承(is_a):一个类是另外一个类的特殊形式
  • 继承和组合都利用了已经定义的类,但是类之间关系上有差别

2.基类与派生类之间的转化

一个公有继承的派生类对象可以隐式转化为一个基类对象

  • 用派生类的对象给基类对象赋值
  • 用派生类的对象来初始化基类的引用
  • 把派生类对象的地址赋值给指向基类的指针
  • 把指向派生类对象的指针赋值给指向基类对象的指针

注意访问范围!
注意基类对象不能代替派生类对象!

class A		
{ public: 
		void print1(){}
};

class B: public A	
{ public: 
		void print2(){}
};

void main()
{
	B bp1, *pbp;
	A ap1 = bp1; 		//OK
	A &ap2 = bp1;		//OK
	A *ptr = &bp1;		//OK
	ptr = pbp;		    //OK
	ptr->print1();		//OK
	ptr->print2();		//Error
}

 再看下面这个例子:

class Base
{
public:
	void view()  {cout << "In Base!"<<endl;}
};

class Derive:public Base 
{
public:
	void view() {cout << "In Derive!"<<endl;}
};

void func(Base& b)
{ 
	b.view(); 
}

void main()
{
	Base b1;
	func(b1);
	Derive d1;
	func(d1);
}

 上面这个例子中,Derive类的对象d1已经隐式的转换为Base对象,因此输出:

In Base!

In Base!

3.派生类中重定义基类的函数

  • 派生类自动继承基类的数据成员和成员函数
  • 若基类中的某成员函数无法满足派生类需求,派生类中可以重定义该函数
  • 重定义基类的成员函数需要使用相同名字和参数
  • 重定义之后,通过派生类访问到的函数是重定义之后的函数
  • 如果需要访问基类中被重定义的函数,可以使用类名加作用域运算符 ::

在上面私有继承举例中,因为私有继承下来的成员函数为private区外部函数类对象无法通过原本的成员函数访问,此时就需要进行重定义,对外界提供一个接口。


五、继承与派生案例 

问题描述
1. 编写一个音像资料类( Media ),该类包含作品名字( name )和制作公司( company )两个成员变量,以及一个显示音像资料信息的成员函数( showInfo
2. 编写音频媒体类( AudioMedia ),继承音像资料类,并包含新的数据成员歌手( singer
3. 编写视频媒体类( VideoMedia ),继承音像资料类,并包含新的数据成员导演( director )和演员( actor
4. AudioMedia VideoMedia 中重定义 showInfo 函数,要求不但要输出 Media 类的成员信息,还要输出其特殊数据成员的信息
5. 编写 main 函数,测试上述三个类的正确性

下面提供完成到第二步,AudioMedia继承音像资料类的程序,其他部分还请读者自行补全 \吃瓜 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<limits.h>
#include<stack>
#include<cstdlib>
#include<stdlib.h>
#include<queue>
#include<map>
#include<vector>

#define INF 0x3f3f3f3f
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
//头文件、宏定义请忽略(OIer的不良嗜好)
using namespace std;

class Media{
	protected:
		char name[30];
		char company[50];
	public:
		Media(char* n,char* c)
		{
			strcpy(name,n);
			strcpy(company,c);
		}
		void showInfo()
		{
			cout<<"_________Info Of Media_________\n";
			cout<<name<<endl;
			cout<<company<<endl;
		}
			
}; 

class AudioMedia:public Media
{
	private:
		char singer[30];
	public:
		AudioMedia(char* n,char* company,char* s):Media(n,company)
		{
			strcpy(singer,s);
		}
		void showInfo()
		{
			Media::showInfo();
			cout<<singer<<endl;
		}
};

int main()
{
	Media m("《1111》","XX传媒");
	m.showInfo();
	AudioMedia n("《2222》","XX传播","XXX");
	n.showInfo(); 
 	return 0;
}

 


总结

继承与派生,一方面使得代码更简洁、去冗余,另一方面也使得“封闭内部信息”这一始终关注的要点在各个场合都有灵活的使用方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/116354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络拓扑结构可视化呈现方案

随着数字化进程的加速&#xff0c;企业网络中设备的数量日益快速增长&#xff0c;网络规模逐渐庞大&#xff0c;组网结构、IT 环境变的无比复杂&#xff0c;需要花费大量的时间和资源去监测网络运行状态&#xff0c;诊断解决故障问题。面对不断趋向复杂化和多样化的网络规模和结…

Mac M1使用Docker报错 Failed to get D-Bus connection: No such file or directory的解决方案

0x00 前言 最近在Mac上安装docker的CentOS7镜像&#xff0c;打算开个sshd服务&#xff0c;使用命令&#xff1a; $ systemctl start sshd结果在启动sshd服务的时候提示报错&#xff1a; Failed to get D-Bus connection: No such file or directory0x01 运行环境 版本MacOS…

《Qt开发》基于QwtPolar的极坐标图绘制

QwtPolar绘制极坐标图 该示例包含以下功能: 使用QwtPolarPlot绘制极坐标曲线实现曲线的缩放和平移调整极坐标为顺时针顺序1. 创建项目 创建项目名称为QwtPolarDemo1&#xff0c;并添加一个Qt5Class类&#xff0c;命名为myPlot。 2. 配置项目 在项目——属性——C/C——常规…

【C++】——初识C++(一)

文章目录1. 进入C1.1 main&#xff08;&#xff09;函数1.2 C注释1.3 C预处理器和iostream文件1.4 头文件名1.5 名称空间1.6 使用cout进行C输出1.6.1 控制符endl1.6.2 换行符1.6.3 使用cout进行拼接1.7 cin1.8 变量1.8.1 变量名1.9 常量1.10 关键字1. 进入C 第一个程序 // my…

构建数据大屏,塑造IT运维可视化核心竞争力

随着大数据、云计算等新兴技术的发展与运用&#xff0c;在金融、交通、教育、政府等行业的信息化在飞速发展。与此同时&#xff0c;各行业的IT建设与维护管理成本也在与日俱增&#xff0c;大量的运维工作下产生庞大的运维数据&#xff0c;如何进行运维数据可视化建设也逐渐成为…

qt之smtp-demo封装与测试

简介 SMTP是一种提供可靠且有效的电子邮件传输的协议&#xff0c;它建立在FTP文件传输服务上的一种邮件服务&#xff0c;主要用于系统之间的邮件信息传递&#xff0c;并提供有关来信的通知。 SMTP的工作过程是建立连接、邮件传送、连接释放。 SMTP的默认端口是25。…

游戏思考26:使用EASTL配合共享内存做自定义STL(未完待续12/27)

文章目录一、前置学习1&#xff09;萃取&#xff08;1&#xff09;迭代器所指对象的类型-value_type<1>第一个限制-返回参数需要指明迭代器的value_type<2>第二个限制坑点-不是所有迭代器都是class type&#xff0c;原生指针就不是<3>第三个限制坑点-如果针对…

2022-12版本的Rstudio它来了,它喊我升级了

1. Rstudio喊我升级 最近每一次打开Rstudio&#xff0c;总是推送给我最新版的Rstudio&#xff1a; 它之前不是这样的&#xff0c;那时候它所在的公司还叫Rstudio&#xff0c;现在改名叫Posit了&#xff0c;就开始推送了&#xff0c;也许它认为是重大的更新&#xff0c;也许他能…

【vue面试题-vuex】

vuex1.vuex是什么&#xff1f;怎么使用&#xff1f;哪种功能场景使用它&#xff1f;2.vuex有哪几种属性&#xff1f;3.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中&#xff1f;4.Vuex解决了什么问题&#xff1f;5.Vuex中状态储存在哪里&#xff0c;怎么改…

【LeetCode】1739. 放置盒子

1739. 放置盒子 题目描述 有一个立方体房间&#xff0c;其长度、宽度和高度都等于 n 个单位。请你在房间里放置 n 个盒子&#xff0c;每个盒子都是一个单位边长的立方体。放置规则如下&#xff1a; 你可以把盒子放在地板上的任何地方。 如果盒子 x 需要放置在盒子 y 的顶部&…

【webpack】cjs运行时分析

准备工作&#xff08;接上篇文章的示例也可以&#xff09;&#xff1a; 1. 在index.js文件中引入任一js文件 import sum from ./sum;const result sum(1,2); console.log(result);2. sum文件 const sum (a, b) > {return ab; }export default sum3. build.js文件 const…

离子交换法深度剖析

离子交换法 是一种借助于离子交换剂上的离子和污水中的离子进行交换反应而除去污水中有害离子的方法。 离子交换法的特点 离子交换过程是一种特殊的吸附过程&#xff0c;在许多方面与吸附过程类似。 与吸附法比较&#xff0c;其特点是:它主要吸附污水中的离子化物质&#xff…

怎样做一个不会被淘汰的车载诊断工程师

步入中年&#xff0c;不可避免会接触到所谓的中年危机&#xff0c;时刻在提醒自己提高自己的护城河&#xff0c;增强核心竞争力。但是这种事情也不是靠空想&#xff0c;还是要功夫下在平时。 自己是在2016年开始接触车载诊断方面&#xff0c;从事过诊断范畴的开发、测试、偏系…

程序员的圣诞节是怎么样的?

2022年圣诞节到来啦&#xff0c;很高兴这次我们又能一起度过~ 一、前言 圣诞节&#xff0c;先在这里祝大家圣诞节快乐~~&#xff0c;用英语来表达就是 Merry Christmas ~~&#xff0c; 为了贴合这个氛围&#xff0c;今天分享种用 Python 绘制圣诞树的方法。 最近用代码绘画圣诞…

非常易用的网络路径中的网络延迟测试和监控以及自动发送报告工具

非常易用的网络路径中的网络延迟测试和监控以及自动发送报告工具。 作为网络管理员或网络工程师&#xff0c;时刻关注网络的交付速度至关重要。不仅需要确保自己有良好的响应时间&#xff0c;还需要确保网络的速度足以满足用户通信所需的每一条路径。而手动测试每个路径将占用…

SpringBoot+Vue项目医院体检中心管理系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

电脑文件数据恢复方法是什么?未备份电脑文件数据恢复的七种方法

在日常生活中&#xff0c;我们总会遇到这样或那样的事情&#xff0c;比如在使用电脑过程中出现文件数据丢失情况&#xff0c;那么在未备份文件下电脑文件数据丢失怎么恢复&#xff1f;电脑文件数据恢复方法有哪些&#xff1f;这里将根据不同情况分享不同的电脑文件数据恢复方法…

L05_课后练习_波士顿房价预测

波士顿房价预测 1.导入所需要的库 import numpy as np import pandas as pd import matplotlib.pyplot as plt import torch import random from sklearn.model_selection import train_test_split2.读入数据 feature pd.read_csv("../data/boston.csv") feature…

Postgresql源码(95)优化器关键数据结构实例

1 测试数据 drop table student; create table student(sno int primary key, sname varchar(10), ssex int); insert into student values(1, stu1, 0); insert into student values(2, stu2, 1); insert into student values(3, stu3, 1); insert into student values(4, st…

Linux的环境变量

目录 什么是环境变量&#xff1f; 那么如何添加自己的程序到命令行上&#xff0c;可以直接执行&#xff1f; 如何查看环境变量&#xff1f; 如何定义环境变量&#xff1f; C语言如何获取环境变量&#xff1f; 什么是环境变量&#xff1f; 在回答这个问题之前&#xff0c;可…