C++中的继承

news2024/11/24 19:00:14

把握住自己能把握住的点滴,把它做到极致,加油!

本节目标

  • 1.继承的概念及定义
    • 1.1继承的概念
    • 1.2 继承定义
      • 1.2.1 定义格式
      • 1.2.2 继承方式和访问限定符
      • 1.2.3 继承基类成员访问方式的变化
  • 2.继承中的作用域
    • 练习
  • 3.基类和派生类对象赋值转换
  • 4.派生类的默认成员函数
  • 5.继承和友元
  • 6.继承与静态成员

1.继承的概念及定义

1.1继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性基础上进行扩展,增加功能,这样产生新的类称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类层次的复用。
在这里插入图片描述
在这里插入图片描述

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
//protected:
	string _name = "Peter";//姓名
	int _age = 18;//年龄
};

class Student :public Person
{
protected:
	int _stuid;//学号
};

class Teacher : public Person
{
protected:
	int _jobid;//工号
};


int main()
{
	Student s;
	s._name = "张三";
	s._age = 18;
	s.Print();

	Teacher t;
	t._name = "赵老师";
	t._age = 40;
	t.Print();

	return 0;
}

继承后父类的Person成员(成员变量+成员函数)都会变成子类的一部分,这里体现了Student类和Teacher类复用Person类的成员。

1.2 继承定义

1.2.1 定义格式

在这里插入图片描述

1.2.2 继承方式和访问限定符

在这里插入图片描述

1.2.3 继承基类成员访问方式的变化

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见

总结:
1.基类的private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。(它像是在子类中隐身了)
(基类私有成员的意义:不想被子类继承下来使用的成员,可以设计成私有)
2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见的。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式), public > protected> private。
4.使用关键字class时默认的继承方式是private,使用struct时的默认继承方式是public,不过最好显示写出继承方式
5.在实际运用中一般使用都是public继承,几乎很少使用protected/private继承,也不提倡使用。
protected/private继承,因为protected/private继承下来的成员只能在派生类的类里面使用,实际中扩展维护性不强。

// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化  
class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
	}
protected:
	string _name;//姓名
private:
	int _age;//年龄
};

//class Student :private Person
//class Student:protected Person
class Student : public Person
{
public :
	void Print1()
	{
		cout << _name << endl;
	}
protected:
	int _stdid;//学号
};
struct Student : Person //默认是公有继承
{
public :
	void Print1()
	{
		cout << _name << endl;
	}
protected:
	int _stdid;//学号
};
class Student : Person //默认是私有继承
{
public :
	void Print1()
	{
		cout << _name << endl;
	}
protected:
	int _stdid;//学号
};

2.继承中的作用域

1.在继承体系中基类和派生类都有独立的作用域。
2.子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义
(在子类成员函数中,可以使用 基类:: 基类成员 显示访问
3.需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4.注意在实际中在继承体系里面最好不要定义同名的成员

//Student 的_num和Person的_num构成隐藏关系,可以看出这样的代码虽然能跑,但是非常容易混淆
class Person
{
protected:
	string _name = "小李子";//姓名
	int _num = 11; //身份证号
};

class Student :public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "学号:" << _num << endl;//999(默认访问的派生类)
		cout << "身份证号" << Person::_num << endl;//11(访问基类的成员)
	}
protected:
	int _num = 999;//学号
};

练习

//B中的fun()和A中的fun()并不构成重载,因为它们不在同一作用域中
//B中的fun()和A中的fun()构成隐藏,成员函数满足函数名相同就构成隐藏
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;
	}
};
void Test()
{
	B b;
	b.fun(10);
};

int main()
{
	B b; 
	b.fun(10);
	b.A::fun();
	return 0;
}

3.基类和派生类对象赋值转换

**1.派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫做切片或者切割。**寓意把派生类中父类那部分切来赋值过去。(当子类公有继承父类,可以说子类是一个特殊的父类,父类即是子类的切割或者切片,子类如果私有或者保护继承父类不行)

2.基类对象不能赋值给派生类对象。
3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才安全。这里基类如果是多态类型,可以使用RTT(Run-Time Type Information)的dynamic case 来进行识别后进行安全转换。(后面多态的文章在详细说)

在这里插入图片描述
(pp指针指向子类的成员,rp是子类那一部分的引用)


class Person
{
protected:
	string _name; //姓名
	string _sex; //性别
	int _age; //年龄
};

class Student :public Person
{
public:
	int _stuid;//学号
};

int main()
{
	Student sobj;

	//1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//父类是子类的切割或切片
	//Person pobj = sobj;
	//Person& rp = sobj;
	//特殊,这里虽然是不同类型,但是不是隐式类型转换
	//这里算是一个特殊支持,语法天然支持的
	//int i = 0;
	//double& d = i; //error// 隐式类型转换 这里会生成一个const的引用变量,d是const的引用变量的引用
	//const double& d = i;
     
 //2.基类对象不能赋值给派生类对象
// sobj = pobj;//error

//3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student* ps1 = (Student*) pp; //这种情况转换时可以的
ps1->_stuid = 10;

pp = &pobj;
Student* ps2 = (Student*)pp; //这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_stuid = 10;
	return 0;
}

4.派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变帮我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数初始化列表阶段显示调用。
2.派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。
3.派生类的operator=必须要调用基类的operator=完成基类的赋值。
4.我们知道析构顺序是先定义的后析构,父类是比子类先定义的,所以子类要先析构,父类要后析构,当然其实不需要显示的去调用父类的析构,子类完成析构后会自动调用父类的析构,显示调用会产生重复。

	//子类的析构函数跟父类的析构函数构成隐藏
	//由于后面多态的需要,析构函数名字会统一处理成destructor()
	~Student()
	{
		//不需要显示调用父类的析构函数
		//每个子类析构函数后面,会自动调用父类析构函数,这样才能保证先析构子类,再析构父类
		//Person::~Person();

		//~Person();//error

		//...处理子类自己的
		cout << "~Student()" << endl;
	}

上面这段程序是会报错的,不需要再显示调用父类的,会造成重复。

下面程序,望君细品

class Person
{
public:

	//父类的默认构造函数
	/*Person(const char* name = "Peter")
		:_name(name)
	{
		cout << "Person()" << endl;
	}*/

	Person(const char* name )//父类显示写的构造函数
		:_name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;//姓名
};

class Student :public Person
{
public:
	//Student(const char* name, int num)
	//	:_name(name) //想对继承下来的父类的对象进行初始化,这种方式是错误的
	//{}

	//正确的方式如下
	Student(const char* name, int num)
		:Person(name) //这里不是匿名对象,而是调用Person类的构造函数
		, _num(num)
	{
		cout << "Student(const char* name, int num)" << endl;
	}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}
		cout << "Student& operator=(const Student& s)" << endl;
		return *this;
	}

	void Print()
	{
		cout << _name << endl;
	}

	//子类的析构函数跟父类的析构函数构成隐藏
	//由于后面多态的需要,析构函数名字会统一处理成destructor()
	~Student()
	{
		//不需要显示调用父类的析构函数
		//每个子类析构函数后面,会自动调用父类析构函数,这样才能保证先析构子类,再析构父类
		//Person::~Person();

		//~Person();//error

		//...处理子类自己的
		cout << "~Student()" << endl;
	}

	Student* operator&()
	{
		return this;
	}

protected:
	int _num;
};

int main()
{
	如果子类没有显示写构造函数,定义一个子类对象会去调用其父类的默认构造函数
	//结束时会去调用父类的析构函数
	//Student s; 

	//如果父类没有默认构造函数,需要我们自己显示写子类的构造函数
	Student s1("张三",1);

	//子类没有显示写拷贝构造函数时
	Student s2(s1);

	Student s3("李四", 2);

	s1 = s3;//赋值

	return 0; 
}


5.继承和友元

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

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name;//姓名
};

class Student :public Person
{
protected:
	int _stuNum; //学号
};

void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl; //error
}

int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

6.继承与静态成员

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

class Person
{
public:
	Person()
	{// 此处构造函数有什么作用?
		++_count; //可以通过这行代码知道我们实例化了多少个对象
		//因为实例化一个父类对象会调用父类的构造函数,实例化一个子类对象也会调用父类的构造函数。
	}
//protected:
	string _name;//姓名
public:
	static int _count;//统计人的个数
};

int Person::_count = 0;

class Student :public Person
{
protected:
	int _stuNum;//学号
};

class Graduate :public Student
{
protected:
	string _seminarCourse; //研究科目
};

int main()
{
	Person p;
	Student s;

	p._name = "张三";
	cout << s._name << endl;

	cout << Student::_count << endl;
	++Person::_count;
	cout << Student::_count << endl;
	return 0;
}

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

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

相关文章

Java+SSM网上订餐系统点餐餐厅系统(含源码+论文+答辩PPT等)

项目功能简介: 该项目采用的技术实现如下 后台框架&#xff1a;Spring、SpringMVC、MyBatis UI界面&#xff1a;BootStrap、H-ui 、JSP 数据库&#xff1a;MySQL 系统功能 系统分为前台订餐和后台管理&#xff1a; 1.前台订餐 用户注册、用户登录、我的购物车、我的订单 商品列…

Linux 常用的命令

前言 Linux 的学习对于一个程序员的重要性是不言而喻的。前端开发相比后端开发&#xff0c;接触 Linux 机会相对较少&#xff0c;因此往往容易忽视它。但是学好它却是程序员必备修养之一。 作者使用的是阿里云服务器 ECS &#xff08;最便宜的那种&#xff09; CentOS 7.7 64…

快速了解JSON及JSON的使用

文章目录JSON简介JSON语法JSON 名称/值对JSON对象数组JSON的简单使用JSON简介 JSON&#xff08;JavaScriptObjectNotation&#xff0c;JS对象简谱&#xff09;是一种轻量级的数据交换格式 JS对象简谱&#xff0c;那么JSON如何转换为JS对象&#xff1a; JSON文本格式在语法上与…

多弹协同攻击时的无源定位

题目 采用被动接收方式的无源探测定位技术具有作用距离远、隐蔽接 收、不易被敌方发觉等优点&#xff0c;能有效提高探测系统在电子战环境下的 生存能力和作战能力。 在无源定位的研究中&#xff0c;测向定位技术&#xff08;Direction of Arrival&#xff0c;DOA&#xff09; …

SpringBoot操作Mongo

文章目录引入依赖yaml实体类集合操作创建删除相关注解文档操作添加实验 数据查询添加更新删除引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency><de…

Jmeter配置不同业务请求比例,应对综合场景压测

背景 在进行综合场景压测时&#xff0c;遇到了如何实现不同的请求所占比例不同的问题。 有人说将这些请求分别放到单独的线程组下&#xff0c;然后将线程组的线程数按照比例进行配置。 这种方法不是很好&#xff0c;因为服务器对不同的请求处理能力不同&#xff0c;有的处理快…

C规范编辑笔记(八)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) 正文&#xff1a; 今天来给大家分享我们的第八篇C规范编辑笔记&#xff0c;话不多说&#xff0c;我们直接来看&…

计算机毕设Python+Vue新闻类网站(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于微信小程序的灯具商城系统-计算机毕业设计

项目介绍 开发语言&#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…

Java中IO体系

File File类 File类 : 表示计算机中所有的文件和文件夹; [计算机硬盘上除了文件就是文件夹]如何创建File对象 :File(String pathname) : 传入文件路径[String],创建File对象并指向这个路径的文件/文件夹File(String parent, String child) :传入文件路径[String],创建File对象…

【机器学习---02】机器学习相关名词解释

文章目录1. 损失函数、期望风险、经验风险2. 经验风险最小化和结构风险最小化2.1 结构风险&#xff08;正则化&#xff09;2.2 两者的定义3. 训练误差 与 测试误差4. 过拟合 与 欠拟合4.1 过拟合及解决方法4.2 交叉验证4.3 欠拟合5. 泛化误差 与 泛化误差上界5.1 泛化误差5.2 泛…

Filter Listener Ajax学习笔记

1 Filter Filter用于请求的过滤&#xff0c;如请求时&#xff0c;做登录的全局性校验 1.1 示例 在创建Filter前&#xff0c;可以通过启动Tomcat访问index.jsp http://localhost:8080/Mvc-Demo/index.jsp添加Filter后&#xff0c;重新启动Tomcat&#xff0c;并再次访问index…

8、java常见名词总结

一、JMM 1.1、JMM简介 JMM 是Java内存模型&#xff08; Java Memory Model&#xff09;&#xff0c;简称JMM。它本身只是一个抽象的概念&#xff0c;并不真实存在&#xff0c;它描述的是一种规则或规范&#xff0c;是和多线程相关的一组规范。通过这组规范&#xff0c;定义了…

babel-plugin-transform-remove-console 项目打包去除console

安装babel-plugin-transform-remove-console 项目打包去除console npm install babel-plugin-transform-remove-console --save-dev 在vue项目中babel.config.js中&#xff1a; module.exports {plugins: ["transform-remove-console",], }; 如果只想在生产环境…

Java+SSM影院订票系统|电影院购票系统(含源码+论文+答辩PPT等)

项目功能简介: 该项目采用的技术实现如下 后台框架&#xff1a;Spring、SpringMVC、MyBatis UI界面&#xff1a;BootStrap、jQuery 、JSP 数据库&#xff1a;MySQL 系统分为前台订票和后台管理&#xff1a; 1.前台订票 用户注册、用户登录、查看电影列表、分类查看 电影搜索、查…

C语言基础篇 —— 5.0 详解C语言变量的四大属性

文章目录概述C语言变量四大属性存储类概念解析Linux 内存映像并解析作用域概念解析局部变量的代码块作用域函数名和全局变量的文件作用域同名变量的掩蔽规则生命周期概念解析栈变量的生命周期堆变量的生命周期数据段、bss段变量的生命周期代码段、只读段的生命周期链接属性概念…

Linux——虚拟机安装Linux系统

实验1-2 虚拟机安装Linux系统 VMware 9.0 虚拟机Linux镜像ISO文件相关工具可以在这里边找到 http://pan.baidu.com/s/1ntA18FJ 或者请自行下载使用 创建新的虚拟机&#xff0c;如下图&#xff1a; 下一步&#xff1a;选择安装配置类型为“典型”如下图&#xff1a; 下一步&…

k8s之Ingress

Ingress和Ingress控制器介绍 Ingress官方文档&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/ Ingress控制器官方文档&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress-controllers/ 在k8s中将一个…

Multipartfile判断文件类型的简单处理办法。

通过浏览器上传的文件在后台需要验证文件类型。如果单纯匹配后缀名的方式是有风险的&#xff0c;容易被换了后缀的病毒文件给破坏掉。 比如&#xff1a; 如果我上传已修改的文件。&#xff08;把xlsx改成了jpg&#xff09; 这样&#xff0c;无法识别出来真实的内容。 所以为了…

java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(六:Hystrix之熔断、降级、限流)~整起

Hystrix 中文文档&#xff1a;https://www.apiref.com/spring-cloud-zh/dalston/#_circuit_breaker_hystrix_clients服务雪崩&#xff1a;服务 A 调用了服务 B&#xff0c;服务 B 再调用了服务 C&#xff0c;但是因为某些原因&#xff0c;服务 C 顶不住了&#xff0c;这个时候大…