初识C++ · 继承(1)

目录

前言:

1 继承的概念和定义

2 基类与子类的赋值转换

3 继承中的作用域

4 派生类的默认成员函数

4.1 构造函数

4.2 拷贝构造

4.3 赋值重载

4.4 析构函数


前言:

对于面向对象这门语言的三大特性 -> 封装 继承 多态,我们已经学习了封装,这里简单理解一下封装,在面向过程的时候,数据和方法(函数)的分离开来的,所以C语言干什么事情都是要自己造轮子,比较麻烦,对于C++ 来说,有了类和对象这个概念,就可以把数据和方法放在一起,那么访问数据就更容易,不需要自己造轮子,这是一种封装,比如不同的数据结构,顺序表链表等,C++有专门的头文件,这也是一种封装,对于反向迭代器来说,是对迭代器的一种封装。

那么今天,就进入到继承这一特点来。


1 继承的概念和定义

继承,顾名思义,从上一代传下来的,比如家中的传家宝可以继承下来给你使用,比如你父亲的财产也可以继承给你,再比如说,某种情况下,私房钱明面上不能继承给你,但是可以间接的继承给你。

在C++中的继承,比如有两个类,他们有着一样的成员变量,比如说人和学生,都有名字,年龄,性别,身高这些概念,我们定义两个类的时候,重复的元素太多,写起来就没那么舒服,那么使用继承,即学生有人的特点,也有属于自己的特点,比如学号等等,就不需要重复定义许多东西了。

继承的定义如下:

class Person
{
public:

private:
	
};

class student : public Person
{
public:

private:
	
};

定义的格式就是class 类名 :继承方式 类名.

其中继承方式一共有3种,public protected private。

在类和对象章节protected private是没有什么区别的,在继承这里,就有区别了,我们先看不同的继承方式对于访问权限的区别:

不难发现一个规律就是 两两权限继承之后,权限都是变成两两中权限小的那个,比如public 继承基类 ,也就是父类中的protected成员后,权限变成了protected,这里protected 和 private的区别就出来了:

权限大小 public > protected > private。

但是问题来了,protected private的成员都访问不了,继承下来有什么用处呢?实际上protected的成员变量我们可以间接的访问:

class Person
{
public:
	void Func()
	{
		cout << _num << endl;
	}
protected:
	int _num = 18;
private:
	string _name = "zhangsan";
};
class student : public Person
{
public:

private:
	int _id = 232323;
};

int main()
{
	student s1;
	s1.Func();
	return 0;
}

比如这里的打印就是一种间接的访问,通过基类继承下来的成员函数等,进行修改访问打印都是没有问题的。

C++的基础是C语言,那么C语言常用的是struct,C++里面可不可以使用struct来继承呢?

答案是可以,但是这里的继承默认是public,使用class默认的是private继承:

struct St
{
	void Func()
	{
		cout << "Func()" << endl;
		cout << _num << endl;
	}
	int _num = 0;
	string _name = "xx";
	int _age = 18;
	string address = "earth";
};

struct Ss : St
{
	void Func()
	{
		cout << _age << endl;
	}
};

2 基类与子类的赋值转换

对于内置类型来说,直接赋值是没有问题的,对于自定义类型来说,直接赋值一般也是没有问题的,大不了就是涉及到拷贝临时对象然后赋值而已:

int a = 1;
int b = a;
string s1 = "xxxx";

那么基类和子类直接能否赋值呢?答案是可以,但是也不完全可以。

子类可以赋值给基类,但是基类不能赋值给子类,这里引入一个切片的概念,假如基类有5个成员变量,继承给子类后,子类加上自己的成员变量,就有7个成员变量了,那么子类赋值过去的时候,就会把自己的成员变量给切掉,赋值的部分是继承的部分,那么相反的,如果是基类赋值给子类,怎么赋值?子类有那么多成员变量,基类没办法赋值:

class Person
{
public:

protected:
	int _num = 18;
private:
	string _name = "zhangsan";
};

class student : public Person
{
public:

private:
	int _id = 232323;
};

int main()
{
	student s1("aaa");
	Person p1;
	p1 = s1;
	return 0;
}

3 继承中的作用域

截止到现在,我们学习了局部域 全局域 类域 命名空间域,加上今天继承中的作用域,就有5个域了。

当局部域和全局域中都有一个整型a的时候,打印往往是打印的局部域中的a,那么同理,如果基类和子类都有一个相同名字的变量,打印的往往是最近的那个,比如:

class Person
{
public:

protected:
	int _num = 18;
};

class student : public Person
{
public:
	void Func()
	{
		cout << _num << endl;
	}

private:
	int _id = 232323;
	int _num = 0;
};

int main()
{
	student s1;
	s1.Func();
}

这段代码的打印结果就是0,那么我们就是想要打印基类中的_num怎么办呢?这时候就需要域名访问限定符:

class Person
{
public:

protected:
	int _num = 18;
};

class student : public Person
{
public:
	void Func()
	{
		cout << Person::_num << endl;
	}

private:
	int _id = 232323;
	int _num = 0;
};

这时候的打印结果就是18。

这种现象叫做隐藏,因为两个变量名是一样的,但是继承下来之后,基类的就被隐藏了,我们需要加点手段才能访问得到。

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

函数也是同理可得。


4 派生类的默认成员函数

默认成员函数有6个,取地址重载那两个不用管,我们需要注意构造函数,析构函数,拷贝构造函数以及赋值重载。

4.1 构造函数

class Person
{
public:
	Person(const char* name)
		:_name(name)
	{}

private:
	string _name = "zhangsan";
};
class student : public Person
{
public:
	student()
		:_id(1)
	{}

private:
	int _id = 232323;
};

类的构造函数对于自定义类型来说会调用它自己的构造函数,那么student继承了Person,Person相当于在student里面了,所以我们应该把student看成两部分,一部分是student,一部分是Person,那么构造函数调用的时候,就需要初始化两部分,一部分是student自己的成员变量,一部分是Person的成员变量。

按照上面的写法,是有问题的,student的对象创建好了后,_id的初始化没问题,但是Person的初始化就有问题了,因为没有默认构造函数,name是什么编译器也不知道,就会报错,那么如果加上:

class Person
{
public:
	Person(const char* name = "hhh")
		:_name(name)
	{}

private:
	string _name = "zhangsan";
};

就不会报错了。

那么问题来了,如果我们不想提供默认构造函数怎么办,就是想要传参,这时候需要用到student的初始化列表了:

class Person
{
public:
	Person(const char* name)
		:_name(name)
	{}

private:
	string _name = "zhangsan";
};
class student : public Person
{
public:
	Person(const char* name)
		:Person(name)
        ,_id(1)
	{}

private:
	int _id = 232323;
};

这里的语法看起来有点怪?

我们可以这样理解,构造的时候,调用的有两个构造函数,一个是子类自己的,一个是基类的,但是基类的如果没有默认的拷贝构造函数,我们就需要自己显式的去调用基类的构造函数。

4.2 拷贝构造

拷贝构造和构造函数一样,都要为基类考虑,这里有个问题就是,应该怎么调用它自己的拷贝构造?

class student : public Person
{
public:
	student(const char* name)
		:_id(1)
		,Person(name)
	{}

	student(const student& s)
		:_id(s._id)
		,Person(s)
	{}

private:
	int _id = 232323;
};

就直接传s就可以了,语法稍稍有点怪,但不大。

4.3 赋值重载

赋值重载这里,我们要考虑的是如何调用基类函数的赋值重载:

	student& operator=(const student& s)
	{
		if (this != &s)
		{
			operator=(s);
			_id = s._id;
		}

		return *this;
	}

如果这样调用,就会栈溢出,基类和子类的赋值重载的函数名一样,那么就构成了隐藏关系,所以需要我们显式的调用基类的赋值重载函数:

student& operator=(const student& s)
{
	if (this != &s)
	{
		Person::operator=(s);
		_id = s._id;
	}
	return *this;
}

4.4 析构函数

析构函数在这里就是很有说法的了,如果我们显式的去调用析构函数,就无法满足析构函数的先子后父原则。

这里引入两个原则,构造是先父后子,析构是先子后父,这也好理解,构造方面,如果基类都没有先构造好怎么继承给子类?

析构函数同理,如果我们先显式的调用了基类的析构函数,就无法满足先子后父了,所以如果加上打印观察的话,就会发生析构了两次的情况出现,如果碰上了动态开辟,就会析构两次从而导致程序挂掉。

那么这里,我们写析构函数只需要:

~student()
{
	// 显示写无法先子后父
	//Person::~Person();
	cout << "~Student()" << endl;
	// 注意,为了析构顺序是先子后父,子类析构函数结束后会自动调用父类析构
}

只调用子类的析构就好了,基类的析构会自己调用的。

实际上,子类的析构和基类的析构构成了隐藏关系,这里先了解,因为都是析构,编译器对函数名继续特殊处理,使析构函数的名字都变成destructor。


感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数电逻辑门电路分析和Digital仿真

文章目录 1. 逻辑门电路 2. 非门&#xff08;NOT Gate&#xff09; 3. 与门&#xff08;AND Gate&#xff09; 4. 或门&#xff08;OR Gate&#xff09; 5. 与非门&#xff08;NAND Gate&#xff09; 6. 或非门&#xff08;NOR Gate&#xff09; 7. 异或门&#xff08;XO…

Java面试八股之什么是mybatis流式查询

什么是mybatis流式查询 Mybatis流式查询是一种处理大量数据的有效方法&#xff0c;它允许你以低内存消耗的方式来处理查询结果。传统的查询操作会一次性将所有数据加载到内存中&#xff0c;如果数据量非常大&#xff0c;可能会导致OutOfMemoryError&#xff08;OOM&#xff09…

Matlab个性化绘图第3期—带三维球标记的折线图

前段时间有会员在群里问该如何绘制下面这种带三维球标记的折线图&#xff1a; 本期内容就来分享一下带三维球标记的折线图的Matlab绘制思路。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自行下载。有需…

Shell脚本、相关命令;重定向、管道符、变量相关命令讲解

目录 Shell脚本 概念 执行命令流程的交互区别 交互式 非交互式 Shell脚本应用场景 Shell的作用 Shell的作用 —— 命令解释器&#xff0c;“翻译官” 列出系统中全部解释器 实验 脚本的基本书写格式和执行命令 在子bash下执行脚本 指定解释器的方式执行脚本 指定…

苹果入局AI手机 iOS 18将应用AI功能

当三星、华为等国内外手机厂商都在卷着造AI手机时&#xff0c;智能手机大佬苹果那边确一直没什么动静。直到今年5月&#xff0c;距离苹果 WWDC24 全球开发者大会还有十多天时&#xff0c;长期关注苹果的博社记者Mark Gurman放料&#xff0c;iOS 18系统中将会应用一些AI功能。 从…

JavaScript妙笔生花:打造沉浸式中国象棋游戏体验

前言 随着信息技术的飞速发展&#xff0c;Web开发领域也出现了翻天覆地的变化。JavaScript作为前端开发中不可或缺的编程语言&#xff0c;其重要性不言而喻。而当我们谈论到利用JavaScript打造一款沉浸式的中国象棋游戏体验时&#xff0c;我们不仅仅是在开发一个游戏&#xff0…

本地大模型服务 Ollama:从安装到使用

文章目录 前言一、下载安装1.1 官网安装1.2 压缩包安装1.3 docker 安装二、命令行使用2.1 常用命令2.2 模型列表2.3 使用三、Open-WebUI3.1 安装3.2 修改语言3.3 使用参考前言 Ollama 是专为在本地机器上便捷部署和运行大型语言模型(LLM)而设计的开源框架,它有如下几个特点…

SAP_FICO模块-获利能力段新增特征字段

业务背景&#xff1a; 公司有启用获利能力分析功能&#xff0c;有一个销售订单接口&#xff0c;是通过第三方销售订单管理平台推送数据到SAP的&#xff0c;用户希望对接新增一个编号ID到销售订单上&#xff0c;并且可以用KE24/KE30报表查看显示&#xff1b; 对于我这么一个后勤…

Tailwindcss 扩展默认配置来自定义颜色

背景 项目里多个Tab标签都需要设置同样的背景颜色#F1F5FF&#xff0c;在集成tailwindcss之前就是重复该样式&#xff0c;如下图&#xff1a; .body {background-color: #f1f5ff; }集成tailwindcss时&#xff0c;我们希望在class中直接设置该背景色&#xff0c;但是默认的tai…

python如何做报表系统

首先我们安装的python和PyQt5要保持一致&#xff0c;要么都是32位或者都是64位。 下载安装&#xff0c;安装完成之后我们记得要设置环境变量。 一路选择“下一步”就可以了。 安装完成之后我们需要验证是否成功。 pyqt5的安装直接安装就可以的&#xff0c;主要更改环境变量~~\p…

Serverless如何赋能餐饮行业数字化?乐凯撒思变之道

导语 | 在数字化浪潮席卷全球的今天&#xff0c;每一个行业都在经历着前所未有的变革。餐饮行业作为人们日常生活中不可或缺的一部分&#xff0c;更是面临着巨大的转型压力。如何完成数字化转型&#xff0c;打破传统经营模式的限制&#xff0c;成为摆在众多餐饮商家面前的一道难…

RocketMQ快速入门:如何保证消息不丢失|保证消息可靠性(九)

0. 引言 在金融、电商等对数据完整性要求极高的行业&#xff0c;消息的丢失可能会导致数据不一致&#xff0c;严重影响业务逻辑和数据统计&#xff0c;也影响客户体验&#xff0c;所以在很多业务场景下&#xff0c;我们都要求数据不能丢失。而rocketmq中&#xff0c;如何对消息…

集合进阶(泛型、泛型通配符、数据结构(二叉树、平衡二叉树、红黑树

一、泛型类、泛型方法、泛型接口 1、泛型概述 泛型&#xff1a;是JDK5中引入的特性&#xff0c;可以在编译阶段约束操作的数据类型&#xff0c;并进行检查。泛型的格式&#xff1a;<数据类型>注意&#xff1a;泛型只能支持引用数据类型。 泛型的好处 1、统一数据类型。 …

建筑主体沉降观测规范详解

随着城市化进程的加速&#xff0c;高层建筑和大型建筑项目日益增多&#xff0c;建筑主体的沉降观测工作显得尤为重要。沉降观测是确保建筑安全稳定的关键环节&#xff0c;对于预防建筑安全事故、保障人民生命财产安全具有重要意义。本文将详细解析建筑主体沉降观测的规范和要求…

【机器学习】线性回归:从基础到实践的深度解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 线性回归&#xff1a;从基础到实践的深度解析引言一、线性回归基础1.1 定义与目…

Word和Excel如何快速对齐姓名

日常工作经常遇到整理参会人员名单时&#xff0c;有2字姓名、3字姓名&#xff0c;为保证文档美观&#xff0c;你是否还在一个一个空格在敲空格&#xff1f; 今天刘小生分享如何在Word和Excel中快速对齐姓名&#xff0c;快来练起来吧&#xff01; 1. Word姓名对齐 【第一步】…

看见未来社区:视频孪生技术打造智慧社区

智慧社区的建设需要创新的技术支撑。智汇云舟创新升级数字孪生为视频孪生技术&#xff0c;通过将真实世界的视频监控与数字模型实时融合&#xff0c;实现了对物理空间的实时实景动态模拟。 针对智慧社区管理业务&#xff0c;以智汇云舟视频孪生平台为支撑&#xff0c;综合承载…

一起笨笨的学C——014grep特别版

目录 前言 正文 原文&#xff1a; 要求总结&#xff1a; 一点一点来&#xff1a; grep学习&#xff1a; glob理解&#xff1a; dirent 目录函数&#xff1a; 加载日志文件&#xff1a; strstr与strcmp&#xff1a; 非首次尝试&#xff1a; 非二次 &#xff1a; 老师…

易兆微电子_嵌入式软件工程师笔试题

易先电子 嵌入式软件工程师笔试题(十七) 1.关键字 extern是什么含义, 请举例说明。 修饰符extern用在变量或者函数的声明前&#xff0c;用来说明 “ 此变量 / 函数是在别处定义的&#xff0c;要在此处引用 ”。 //main.c #include <stdio.h>int main() {extern int num…

英国牛津大学基因组学方向博士后职位

英国牛津大学基因组学方向博士后职位 牛津大学&#xff08;University of Oxford&#xff09;&#xff0c;简称“牛津”&#xff08;Oxford&#xff09;&#xff0c;位于英国牛津&#xff0c;是一所公立研究型大学&#xff0c;采用传统学院制。是罗素大学集团成员&#xff0c;被…