【C++】继承(上) 继承 | 子类的默认成员函数

一、继承

概念

继承(inheritance)是一种面向对象编程的概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的特征和行为。子类可以获得父类的成员函数和变量,而不需要重新编写它们。子类还可以添加自己的成员函数和变量,以扩展其功能。

可以说,继承机制 是在面向对象的程序设计中,使代码可以复用的最重要的手段。以前我们接触的复用都是函数复用,而继承是类设计层次的复用。

我们通过下面的例子来说明什么是继承。假设要录入全校师生的信息,那要根据学校中的不同角色,涉及不同的类,如学生、老师、职工……

#include<iostream>
using namespace std;
class Student
{string name;int age;string tele;string StudentID;
};
class Teacher
{string name;int age;string tele;string TeacherID;
};
class Staff
{string name;int age;string tele;string StaffID;
};
int main() {return 0;
}

但实际上,我们会发现,这些类的基本成员(姓名、年龄、电话)是一致的,仅仅是ID不一样。那这样写,是否会造成代码的冗余呢?

Yes。为了避免这种冗余,C++中用“继承”的机制来复用类中的代码。

我们先定义出一个包含基本成员的Person类,然后复用这个Person类,让各个类都能继承Person类中的基本成员:

#include<iostream>
using namespace std;
class Person
{string name;int age;string tele;
};
class Student:public Person   //让Student继承Person类
{string StudentID;
};
class Teacher :public Person
{string TeacherID;
};
class Staff :public Person
{string StaffID;
};
int main() {Student s;return 0;
}

这就是继承,Student类继承了Person类中的所有成员。我们打开监视窗口看看s的当前成员:

可见,Perosn的成员已经全被Student对象包含进来了。

格式

格式:class 子类名 : 继承方式 父类名

在下面的例子中,Person是父类,也称作基类。Student是子类,也称作派生类

Student和Person是“子承父业”的关系,子类在获得父类全部成员的基础上,又拓展了自己的属性,添加了“学号”“专业”这种 自己的成员变量。

访问限定符protected

之前学访问限定符时,就剩了个protected没提。因为protected是为继承而设计的。

三种访问方式:

public:公有,类外可直接访问

private:私有,仅类里能访问,类外不可访问

protected:保护。通常用于父类中,子类能访问父类的protected成员,而类外不能访问。

举个栗子,如果我们把父类成员设为private,那类外和子类都被拦在类域之外,子类无法访问父类成员:

#include<iostream>
using namespace std;
class Person       //把Person成员设为private
{
private:string name="zhangsan";int age=20;string tele="1234567";
};
​
class Student:public Person
{
public:void Print() {cout << name << endl;  //访问父类成员cout << age << endl;cout << tele << endl;}string StudentID;
};
​
int main() {Student s;s.Print();return 0;
}

这种情况下,Student是访问不了Person成员的:

如果我们设父类成员为protected,那就对子类开放了访问窗口(类外仍不可访问):

class Person
{
protected:string name="zhangsan";int age=20string tele="1234567";
};
class Student:public Person
{……
};
int main() {Student s;s.Print();return 0;
}

这就是protected访问方式。

其实,public、protected、private不仅是三种访问方式,也可以是三种继承方式。

三种继承方式

继承方式,分public、protected、private这三种情况。

3种父类的访问方式,与3种子类的继承方式,结合一下就是9种情况:

这个表老师一般都会要求背诵,我们不要死记硬背,这张表其实是很有规律的:两个权限结合,我们取权限更小的那个。从权限来看,public>protected>private,如果是protected继承与基类的private成员,那继承到派生类中的权限就是private(小的那个)。

几点说明:

1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指:基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

2.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

3.在实际运用中,绝大部分情况都用public继承,很少使用protetced/private继承,也不提倡使用protetced/private继承,protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

子类和父类对象的赋值转换

1.子类对象可以赋值给父类的对象 / 指针 / 引用。这里有个形象的说法叫切片or切割。寓意把子类中父类那部分切来赋值过去。

#include<iostream>
using namespace std;
class Person
{
public:string name="zhangsan";int age=20;string tele="1234567";
};
class Student:public Person
{
public:string StudentID;
};
int main() {Student s;Person p;p = s;   //子类对象赋值给父类对象return 0;
}

2.父类对象不能赋值给子类对象。

3.(了解即可)父类的指针or引用可以通过强制类型转换赋值给子类的指针or引用。但是必须是父类的指针是指向子类对象时才是安全的。示例:

int main() {Student s;Person *p=&s;Student* ps;ps = (Student*)p;return 0;
}

隐藏(重定义)

在继承体系中子类和父类都有独立的作用域。

如果子类和父类中有同名成员,子类成员将屏蔽 父类的同名成员 的直接访问,这种情况叫隐藏,也叫重定义

例:

#include<iostream>
using namespace std;
class Person
{
public:string name="zhangsan";int age=20;string tele="Person中的tele";
};
class Student:public Person
{
public:void Print() {cout << tele << endl;}string tele="Student中的tele";
};
int main() {Student s;s.Print();  //此时从Person中继承来的tele被隐藏return 0;
}

隐藏并不是删除,从父类那里继承来的同名成员 依然存在于子类中。在子类的成员函数中,可以使用 “父类::父类成员”的方式访问。我们来访问一下看看:

class Person
{
public:……string tele="Person中的tele";
};
class Student:public Person
{
public:void Print() {cout << Person::tele << endl;}string tele="Student中的tele";
};
int main() {Student s;s.Print();  return 0;
}

如果是成员函数的隐藏,只需要函数名相同就构成隐藏。不过,在实际中,继承体系里面最好不要定义同名的成员。

区分隐藏与函数重载

小练习:A和B的func构成什么关系? A、重载 B、重定义 C、重写

class A {
public:void func() {cout << "func()" << endl;}
};class B : public A {
public:void func(int i) {A::func();cout << "func(int i) -> " << i << endl;}
};int main(void) {B b;b.func(10);
}

answer:B重定义,即隐藏。这里很容易误选A。函数重载的前提是,得在同一个作用域里。而子类和父类是两个不同的类域,所以这俩类域中相同的函数名构成隐藏,而非函数重载。

继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

class Student;
class Person
{friend void Print(Person& p, Student& s);
protected:string _name;
};
class Student :public Person
{
protected:string _ID;
};
void Print(Person& p, Student& s) {cout << p._name << endl;     //可以访问cout << s._ID << endl;     //不可访问
}
int main() {Person p;Student s;Print(p, s);return 0;
}

基类与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

#include<iostream>
using namespace std;
class Student;
class Person
{
public:static int count;Person():_name(""){count++;}
protected:string _name;
};
int Person::count = 0;
​
class Student :public Person
{
protected:string _ID;
};
int main() {Person p1;Student s;cout << Person::count <<" "<< Student::count << endl;   //原本count为2Person::count = 0;             //将Person的count置空cout << Student::count << endl;   //发现Student的count也被清空了,说明这俩count就是同一个return 0;
}

二、子类的默认成员函数

构造函数

当实例化出子类对象时,会先调用父类的构造函数,再调用子类的构造函数。

示例:

#include<iostream>
using namespace std;
class Person
{
public:Person(string n="",int a=0,string tel=""):name(n), age(a), tele(tel){cout << "调用了基类的构造" << endl;}
​string name;int age;string tele;
};
​
class Student :public Person
{
public:Student(string n="", int a=0, string tel="", string teleNum=""):tele(teleNum){cout << "调用了派生类的构造" << endl;}string tele;
};
​
int main() {Student s;return 0;
}

子类的构造函数必须调用 父类的构造函数 初始化父类的那一部分成员。那假如父类没有默认构造函数,要怎么办呢?

class Person
{
public:Person(string n,int a,string tel)  //父类的构造函数:name(n), age(a), tele(tel){cout << "调用了基类的构造" << endl;}string name;int age;string tele;
};

这时,得在子类的构造函数中,显示调用父类的构造函数:

class Student:public Person
{
public:Student(string n, int a, string tel,string teleNum):tele(teleNum),Person(n,a,tel)   //需显式调用{cout << "调用了派生类的构造" << endl;}string tele;
};

总之,父类的构造函数是一定要调用 并且是先调用的。假如你不想让某类被继承,那就把它的构造函数私有化。

析构函数

析构的顺序是固定的:先析构子类对象,再析构父类对象。为了保证一定是这个析构顺序,子类的析构函数在调用完成后,会自动调用父类的析构函数。

#include<iostream>
using namespace std;
class Person
{
public:~Person() {cout << "析构父类" << endl;}string name;int age;string tele;
};
class Student:public Person
{
public:~Student() {cout << "析构子类" << endl;}string tele;
};
int main() {Student s;return 0;
}

要注意,析构函数不要再显示调用了!来看下面这个错误示例:

class Person
{
public:~Person() {cout << "析构父类" << endl;}……
};
class Student:public Person
{
public:~Student() {Person::~Person();   //在析构函数中显示调用基类的析构cout << "析构子类" << endl;}string tele;
};
int main() {Student s;return 0;
}

可见,这样会导致基类对象被析构两次。

因为父类的析构会被自动调用,所以不要再显示调用父类的析构函数了。不然如果有指针变量,析构两次就造成释放野指针的问题。

拷贝构造函数

子类的拷贝构造函数中,初始化 从父类那儿继承来的成员 时,必须得调用父类的拷贝构造函数。

#include<iostream>
using namespace std;
class Person
{
public:Person(string n="",int a=0,string tel=""):name(n), age(a), tele(tel){cout << "调用了基类的构造" << endl;}
​//拷贝构造Person(const Person& p):name(p.name),age(p.age),tele(p.tele){cout << "调用了父类的拷贝构造" << endl;}string name;int age;string tele;
};
​
class Student :public Person
{
public:Student(string n = "", int a = 0, string tel = "",string teleNum =""):tele(teleNum),Person(n,a,tel){cout << "调用了派生类的构造" << endl;}
​//拷贝构造Student(const Student& s):Person(s)   //s中,属于Person的那部分会“切片”,拷贝过去,tele(""){cout << "调用了zi类的拷贝构造" << endl;}string tele;
};int main() {Student s1("zhangsan",20,"111","");Student s2(s1);return 0;
}

赋值重载operator=

子类的operator=必须调用父类的operator=,完成父类成员的赋值。

#include<iostream>
using namespace std;
class Person
{
public:Person(const char* name=""):_name(name){}
protected:string _name;
};
class Student :public Person
{
public:Student(const char* name="",string ID = ""):_ID(ID),Person(name){}Student& operator=(const Student& s) {if (this != &s) {Person::operator=(s);   //调用父类的operator=_ID = s._ID;}return *this;}
​
protected:string _ID;
};
int main() {Student s1("111","000");Student s2;s2 = s1;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

这是一棵适合搜索二叉树

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

宏集新闻 | 虹科传感器事业部正式更名为宏集科技

致一直支持“虹科传感器”的朋友们&#xff1a; 为进一步整合资源&#xff0c;给您带来更全面、更优质的服务&#xff0c;我们非常荣幸地宣布&#xff0c;虹科传感器事业部已正式更名为宏集科技。这一重要的改变代表了虹科持续发展进程中的新里程碑&#xff0c;也体现了我们在传…

安装gitlab

安装gitlab 环境 关闭防火墙以及selinux&#xff0c;起码4核8G 内存至少 3G 不然启动不了 下载环境 gitlab官网&#xff1a;GitLab下载安装_GitLab最新中文基础版下载安装-极狐GitLab rpm包下载地址&#xff1a; [Yum - Nexus Repository Manager (gitlab.cn)](https://pack…

Android studio run 手机或者模拟器安装失败,但是生成了debug.apk

错误信息如下&#xff1a;Error Installation did not succeed. The application could not be installed&#xff1a;List of apks 出现中文乱码&#xff1b; 我首先尝试了打包&#xff0c;能正常安装&#xff0c;再次尝试了debug的安装包&#xff0c;也正常安装&#xff1…

报错!Jupyter notebook 500 : Internal Server Error

Jupyter notebook 报错 500 : Internal Server Error 问题背景 tensorflow-gpu环境&#xff0c;为跑特定代码专门开了一个环境&#xff0c;使用conda安装了Jupyter notebook&#xff0c;能够在浏览器打开Jupyter notebook&#xff0c;但是notebook打开ipynb会报错。 问题分析…

抖音seo矩阵系统源代码部署及产品功能设计分析

一、引言 随着抖音等短视频平台的崛起&#xff0c;越来越多的企业和个人开始关注如何在这些平台上提升曝光量和用户流量。抖音SEO&#xff08;搜索引擎优化&#xff09;是一种有效的方法&#xff0c;通过优化短视频内容和关键词&#xff0c;让更多的人找到并点击你的视频。本文…

【css】Google第三方登录按钮样式修改

文章目录 场景前置准备修改样式官方属性修改样式CSS修改样式按钮的高度height和border-radiusLogo和文字布局 场景 需要用到谷歌的第三方登录&#xff0c;登录按钮有自己的样式。根据官方文档&#xff1a;概览 | Authentication | Google for Developers&#xff0c;提供两种第…

【Web】Ctfshow Nodejs刷题记录

目录 ①web334 ②web335 ③web336 ④web337 ⑤web338 ⑥web339 ⑦web340 ⑧web341 ⑨web342-343 ⑩web344 ①web334 进来是一个登录界面 下载附件&#xff0c;简单代码审计 表单传ctfshow 123456即可 ②web335 进来提示 get上传eval参数执行nodejs代码 payload: …

基于安卓android微信小程序的个人管理小程序

运行环境 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&a…

B站短视频如何去水印?一键解析下载B站视频!

在浏览B站视频时&#xff0c;我们有时会遇到带有水印的场景。这些水印可能会干扰我们对视频内容的观看体验&#xff0c;特别是在全屏观看时。此外&#xff0c;当我们想要保存或分享这些视频时&#xff0c;水印也会成为一种障碍。因此&#xff0c;去除水印的需求就变得非常迫切。…

035、目标检测-物体和数据集

之——物体检测和数据集 目录 之——物体检测和数据集 杂谈 正文 1.目标检测 2.目标检测数据集 3.目标检测和边界框 4.目标检测数据集示例 杂谈 目标检测是计算机视觉中应用最为广泛的&#xff0c;之前所研究的图片分类等都需要基于目标检测完成。 在图像分类任务中&am…

wsl安装ubuntu的问题点、处理及连接

WSL安装Ubuntu的参考链接 (41条消息) wsl报错&#xff1a;WslRegisterDistribution failed with error: 0x800701bc_yzpyzp的博客-CSDN博客_0x800701bc wsl (41条消息) 使用Ubuntu安装软件出现Unable to locate package错误解决办法_大灰狼学编程的博客-CSDN博客 手把手教你…

栈的生长方向不总是向下

据我了解&#xff0c;栈的生长方向向下&#xff0c;内存地址由高到低 测试 windows下&#xff1a; 符合上述情况 测试Linux下&#xff1a; 由此可见&#xff0c;栈在不同操作系统环境下&#xff0c;生长方向不总是向下

时序预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost时间序列预测

时序预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost时间序列预测 目录 时序预测 | MATLAB实现基于LSTM-AdaBoost长短期记忆网络结合AdaBoost时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 x 基本介绍 1.Matlab实现LSTM-Adaboost时间序列预测…

Android HAL学习 及 与BSP的区别

Android HAL学习 及 与BSP的区别 参考链接&#xff1a; 1、https://www.cnblogs.com/looner/articles/11579335.html 2、https://blog.csdn.net/leesan0802/article/details/124087630 3、https://zhuanlan.zhihu.com/p/336531442 在HAL的学习之前&#xff0c;我们来先了解…

京东数据分析(京东数据采集):2023年10月京东平板电视行业品牌销售排行榜

鲸参谋监测的京东平台10月份平板电视市场销售数据已出炉&#xff01; 根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;10月份&#xff0c;京东平台上平板电视的销量将近77万&#xff0c;环比增长约23%&#xff0c;同比则下降约30%&#xff1b;销售额为21亿&#xff0c;环…

数据库数据恢复—MongoDB数据库文件拷贝出现错误的数据恢复案例

MongoDB数据库数据恢复环境&#xff1a; 一台Windows Server操作系统的虚拟机&#xff0c;虚拟机上部署有MongoDB数据库。 MongoDB数据库故障&检测&#xff1a; 在未关闭MongoDB服务的情况下&#xff0c;工作人员将MongoDB数据库文件拷贝到其他分区&#xff0c;然后将原数…

双12电视盒子推荐:测评员解析目前电视盒子哪个最好

电视盒子不需要每月缴费&#xff0c;只需联网就可以收看海量视频资源&#xff0c;游戏、网课、投屏等功能让电视盒子的使用场景更丰富&#xff0c;我每年都会进行数十次电视盒子测评&#xff0c;本期要分享的是双十二电视盒子推荐&#xff0c;全面解析目前电视盒子哪个最好。 一…

UE4基础篇十七:分析性能

一、性能瓶颈定位 原文地址:小能猫吃牙膏:UE4 性能 - (一)瓶颈定位 P.S. 对于某个具体问题,我个人偏向于遵循 WHY → WHAT → HOW 的思考方法(重要性逐级递减) 加以理解。因为如果找不到做某件事情的意义(WHY)所在,或是对这件事情本身的定义(WHAT)都模棱两可,那么即便经…

计算机视觉与机器学习D1

计算机视觉简介 技术背景 了解人工智能方向、热点 目前人工智能的技术方向有&#xff1a; 1、计算机视觉——计算机视觉(CV)是指机器感知环境的能力&#xff1b;这一技术类别中的经典任务有图像形成、图像处理、图像提取和图像的三维推理。物体检测和人脸识别是其比较成功…