c++基础知识复习(2)

news2024/11/26 12:37:47

1. 多态的虚函数的意义

1 案例:父类和子类有同名函数,但是功能不一样,但是同时,子类又继承了父类,就会导致调用的错误,想调用子类的同名函数,

但是在某些情况下,会错误调用父类的同名函数,比如

  • 情形一:
	//父类指针可以指向子类对象
	cout <<"----- Father* men[] = { &father, &son1, &son2 }; -----" << endl;
	Father* men[] = { &father, &son1, &son2 };
	party(men, sizeof(men)/ sizeof(men[0]));
  • 情形二:
	//这段函数的本意是想调用儿子的paly函数,但是实际上调用的是父类的
	Father* p;
	p = &son1;
	p->play();

在这里插入图片描述

  • 上述问题的解决方案就是在父类里面使用虚基类,只需要在父类的同名函数前面加上virtual关键字即可,子类的同名函数可加可不加,再次运行结果如下
#pragma once
class Father
{
public:
	virtual void play();

};

在这里插入图片描述

son 函数
#pragma once
#include "Father.h"
class Son:public Father
{
public:
	virtual void play();
};

#include "Son.h"
#include <iostream>
#include "string"
using namespace std;

void Son::play()
{
	cout << "Game" << endl;
}
——————————————————————————————————————————————————————
Father函数
#pragma once
class Father
{
public:
	void play();

};

#include "Father.h"
#include <iostream>
#include "string"
using namespace std;
void Father::play()
{
	cout << "KTV sing" << endl;
}

——————————————————————————————————————————————————————
主函数
#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include  <sstream>
#include  "Father.h"
#include  "Son.h"

using namespace std;
void party(Father** men, int n)
{
	for (int i = 0; i < n; i++)
	{
		men[i]->play();
	}
}
int main(void) {

	Father father;
	Son son1, son2;

	//父类指针可以指向子类对象
	cout <<"----- Father* men[] = { &father, &son1, &son2 }; -----" << endl;
	Father* men[] = { &father, &son1, &son2 };
	party(men, sizeof(men)/ sizeof(men[0]));

	//这段函数的本意是想调用儿子的paly函数,但是实际上调用的是父类的
	cout << "----- Father* p -----" << endl;
	Father* p;
	p = &son1;
	p->play();

	return 0;
}

2. 虚函数的原理,用于实现多态

在这里插入图片描述

(1). 虚函数表

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include  <sstream>

using namespace std;

class Father
{
public:
	virtual void func1() { cout << "Father::func1" << endl; };
	virtual void func2() { cout << "Father::func2" << endl; };
	virtual void func3() { cout << "Father::func3" << endl; };
	void func4() { cout << "非虚函数,Father::func4" << endl; };

	int x = 200;
	int y = 300;
	static int z;
};

int Father::z = 0;

typedef void(*func_t)(void);//将数据类型转换成函数指针, 返回类型是void,输入参数也是void,也就是没有输入参数
int main(void) {
	Father father;
	//对象里面只装了虚函数表指针,指针为4字节,以及成员函数x,y 4字节+4字节,所以共12个字节
	cout << "sizeof(father)= "<< sizeof(father) << endl;  //输出值为12

	cout << "对象地址:" << (int*)&father << endl; //(int*)会按照16进制进行打印

	//(&father)表示取对象地址,将地址转成int型的指针(int*)(&father),然后再次取指针里面的值*(int*)(&father)
    //此时取到的是一个整数,再次将值转换成int*,因为左侧定义的是int* vptr,(int*)是强制类型转换
	int* vptr = (int*)*(int*)(&father);  //此处成功取到对象所指向的虚函数表的指针

	//*(vptr + 0 )表示取到了第一个虚函数的指针,虚函数表里面存的本身就是指针,
	//函数本身就是一个地址,因此强制类型转换,将取出来的指针转换成函数,就可以根据函数指针调用函数了
	cout << "调用第1个虚函数:"; ((func_t)*(vptr + 0 ))();
	cout << "调用第2个虚函数:"; ((func_t)*(vptr + 1))();
	cout << "调用第3个虚函数:"; ((func_t)*(vptr +2))();

	cout << " ********************" << endl;
	cout << "对象里面第1个数据成员的地址(方式1打印): " << &father.x << endl;
	//为什么加4,因为当前类是有两个成员变量,那么对象内首先存储的就是虚函数表指针,然后是整型变量x,然后是整型变量y
	//指针是4个字节,整型变量也是4个字节,&father默认是指向对象的第一个数据,也就是函数表指针,+4就指向了下一个整型数据
	cout << "对象里面第1个数据成员的地址(方式2打印): " << hex << (int)&father + 4<< endl;
	cout << "第1个数据成员的值(方式1打印): " << dec << father.x << endl;
	cout << "第1个数据成员的值(方式2打印): " << *(int*)((int)&father + 4) << endl;
	
	cout << " ********************" << endl;
	cout << "对象里面第2个数据成员的地址(方式1打印): " << &father.y << endl;
	cout << "对象里面第2个数据成员的地址(方式2打印): " << hex << (int)&father + 8  << endl;  //hex表示转为16进制表示
	cout << "第2个数据成员的值(方式1打印): " << dec << father.y << endl;
	cout << "第2个数据成员的值(方式2打印): " << *(int*)((int)&father + 8) << endl;
	return 0;
}

(2).子类的虚函数表

  • 父类中有3个函数,子类定义了一个父类的同名函数和一个父类中没有的函数,子类的同名函数要实现自己的功能,观察此时虚函数表的变化。
    在这里插入图片描述
    在这里插入图片描述
#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include  <sstream>

using namespace std;

class Father
{
public:
	virtual void func1() { cout << "Father::func1" << endl; };
	virtual void func2() { cout << "Father::func2" << endl; };
	virtual void func3() { cout << "Father::func3" << endl; };
	void func4() { cout << "非虚函数,Father::func4" << endl; };

	int x = 200;
	int y = 300;
	static int z;
};

int Father::z = 0;

class Son :public Father {
public:
	virtual void func1() { cout << "Son::func1" << endl; };
	virtual void func5() { cout << "Son::func5" << endl; };
};

typedef void(*func_t)(void);//将数据类型转换成函数指针, 返回类型是void,输入参数也是void,也就是没有输入参数
int main(void) {
	Son son;
	cout << "Son 对象地址:" << (int*)&son;//加 (int*)只是让打印按照指针格式打,不会改变值

	int* vptr = (int*)*(int*)(&son);
	cout << "虚函数表指针:vptr -- " << vptr << endl;

	for (int i = 0; i < 4; i++)
	{
		cout << "调用第" << i + 1 << "个虚函数" ;
		((func_t) * (vptr + i))();
	}


	for (int i = 0; i < 2; i++)
	{
		cout << *(int*)((int)&son +4 + i*4) << endl;
		
	}

	cout << "size of son = " << sizeof(son) << endl;
	return 0;
}

(3)final关键字,用来修饰类,让该类不能被继承

几种用法

  • 如果用于类定义时做修饰,那么这个类将不能被继承,如下列定义的类不允许被继承
class Phone8848 final {};
  • 用于类在继承上一个类的过程中做修饰,那么当前类将不允许被继承,XiaoMi3 类之后不允许被继承
class XiaoMi {};
class XiaoMi2: public XiaoMi {};
class XiaoMi3 final: public XiaoMi2{};
  • 如果用于修饰虚函数(不允许用来修饰其他函数),子类可以继承父类的该函数,但是子类不允许对该函数进行修改
class XiaoMi{
	virtual void milioa() final;
};

class XiaoMi2: XiaoMi {
	void milioa();
};

(4) override关键字,只能用于虚函数

在这里插入图片描述

  • 只需要在函数声明的时候用override,函数实现的时候不需要加这个关键字
class XiaoMi{
	virtual void milioa();
};

class XiaoMi2: XiaoMi {
	void milioa() override;  //提示程序员当前函数重写了父类的方法,实际上没有什么用
};
//函数实现
void XiaoMi2::milioa()
{
}

(5) 多态使用过程中,子类的析构函数不调用,导致内存泄露问题

  • 以下案例,打印如下结果,有可能提示内存泄漏,有可能根本不提示,但是实际上有内存泄漏,因为没有delete指针
    case 3情况,使用了多态,用父类指针指向子类对象,但是释放内存的时候,不调用子类的析构函数,导致内存泄漏
  • 解决方案是:在父类的析构函数前面加关键字virtual,把Father类的析构函数定义为virtual函数时,如果对father类的指针使用delete操作时,就会对指针使用动态析构,所谓动态析构:指的时如果father指针指向的子类对象,就会先调用子类的析构函数,再调用自己析构函数释放内存
  • 在实际开发过程中,如果某一个类作为基类,推荐是对该类的析构函数都加上virtual关键字

在这里插入图片描述

#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include  <sstream>

using namespace std;

class Father {
public:
	Father(const char* addr = "china") {
		cout << "执行了Father构造函数" << endl;
		int len = strlen(addr) + 1;
		this->addr = new char[len];
		strcpy_s(this->addr, len, addr);
	};
	
	//把Father类的析构函数定义为virtual函数时,如果对father类的指针使用delete操作时,就会对指针使用动态析构
	//所谓动态析构:指的时如果father指针指向的子类对象,就会先调用子类的析构函数,再调用自己析构函数释放内存
	//virtual ~Father() {
	~Father() {
		cout << "执行了Father析构函数" << endl;
		if (addr)
		{
			delete addr;
			addr = NULL;
		}
	};

private:
	char* addr;
};

class Son :public Father {
public:
	Son(const char* game = "吃鸡",const char* addr = "china"):Father(addr) {
		cout << "执行了Son构造函数" << endl;
		int len = strlen(game) + 1;
		this->game = new char[len];
		strcpy_s(this->game, len, game);
	};
	~Son() {
		cout << "执行了Son析构函数" << endl;
		if (game)
		{
			delete game;
			game = NULL;
		}
	};
private:
	char* game;
};

int main(void) {
	cout << "------case 1 --------" << endl;
	Father* father = new Father;
	delete father;

	cout << "------case 2 --------" << endl;
	Son* son = new Son;
	delete son;

	cout << "------case 3 --------" << endl;
	father = new Son;
	delete father;

	return 0;
}

3. 纯虚函数定义,什么时候用到纯虚函数

  • 一旦类里面使用了纯虚函数,那么这个类就是抽象类,抽象类不能用来具体化对象,抽象类的目的是用来做基类,给其他的类做继承
  • 子类继承抽象类之后,如果子类没有对父类所定于的纯虚函数做实现,那么子类也是抽象类,也不能用于具体化对象,但是如果子类对纯虚函数做实现,那么子类将不再是抽象类

在这里插入图片描述

class Shape{
public:
	Shape(const string& color = "White") { this->color = color; };

	//把当前函数定义为纯虚函数
	virtual float area() = 0;

	string getColor() { return color; };

private:
	string color;

};

class Circle :public Shape {
public:
	Circle(float radius = 0, const string& color = "White"):Shape(color), r(radius) {};
	float area() { return 3.14 * r * r; }
private:
	float r;
};

int main(void) {
	Circle c1(10);
	cout << c1.area() << endl;
	return 0;
}

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

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

相关文章

NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案

EasyNVR是基于端-边-云一体化架构的安防监控视频融合云平台&#xff0c;具有简单轻量的部署方式与多样的功能&#xff0c;支持多种协议&#xff08;如GB28181、RTSP、Onvif、RTMP&#xff09;和设备类型&#xff08;IPC、NVR等&#xff09;&#xff0c;提供视频直播、录像、回放…

SpringBoot3+Jasypt如何在配置文件中对数据库的密码进行加密以防止密码泄露

在 Spring Boot 3 中&#xff0c;可以通过jasypt-spring-boot-starter对配置文件中的数据库密码或者其他重要密码进行加密&#xff0c;操作非常简单&#xff0c;可以有效防止密码泄露&#xff1a; 1. 使用 Jasypt 加密 添加依赖 在 pom.xml 中添加 Jasypt 依赖&#xff1a; …

ARM(安谋) China处理器

0 Preface/Foreword 0.1 参考博客 Cortex-M23/M33与STAR-MC1星辰处理器 ARM China&#xff0c;2018年4月established&#xff0c;独立运行。 1 处理器类型 1.1 周易AIPU 1.2 STAR-MC1&#xff08;星辰处理器&#xff09; STAT-MC1&#xff0c;主要为满足AIOT应用性能、功…

Adobe Illustrator 2024 安装教程与下载分享

介绍一下 下载直接看文章末尾 Adobe Illustrator 是一款由Adobe Systems开发的矢量图形编辑软件。它广泛应用于创建和编辑矢量图形、插图、徽标、图标、排版和广告等领域。以下是Adobe Illustrator的一些主要特点和功能&#xff1a; 矢量绘图&#xff1a;Illustrator使用矢量…

CVE-2022-26201

打开是这么个页面 左上角找到Admin访问 里面有个Add Users&#xff0c;访问一下&#xff0c;能创建用户&#xff0c;有个能上传图片的地方 普通的一句话木马无法访问flag&#xff0c;需要创建一个权限马 <?php system($_GET[1]);phpinfo();?> 因为只能上传jpg形式的文…

使用 OpenCV 进行视频中的行人检测

在计算机视觉领域&#xff0c;行人检测是一个重要的研究方向&#xff0c;它在视频监控、自动驾驶、人机交互等领域都有着广泛的应用。本文将介绍如何使用 OpenCV 库来实现视频中的行人检测。 环境准备 首先&#xff0c;我们需要安装 OpenCV 库。可以通过以下命令来安装&#…

【K8s】专题十五(4):Kubernetes 网络之 Calico 插件安装、切换网络模式、卸载

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 | 精选文章 | Kubernetes | Docker | Linux | 羊毛资源 | 工具推荐 | 往期精彩文章 【Docker】&#xff08;全…

鸿蒙面试题-某迈-2024年11月22日

某迈-2024年11月22日 1. 自我介绍 2. 鸿蒙中地图功能如何实现&#xff0c;申请流程是什么样的 主要通过 集成 Map Kit 的功能来实现Map Kit 功能很强大&#xff0c;比如有 创建地图&#xff1a;呈现内容包括建筑、道路、水系等。地图交互&#xff1a;控制地图的交互手势和交…

微软要求 Windows Insider 用户试用备受争议的召回功能

拥有搭载 Qualcomm Snapdragon 处理器的 Copilot PC 的 Windows Insider 计划参与者现在可以试用 Recall&#xff0c;这是一项臭名昭著的快照拍摄 AI 功能&#xff0c;在今年早些时候推出时受到了很多批评。 Windows 营销高级总监 Melissa Grant 上周表示&#xff1a;“我们听…

【Android】静态广播接收不到问题分析思路

参考资料&#xff1a; Android 静态广播注册流程(广播2)-CSDN博客 Android广播发送流程(广播3)_android 发送广播-CSDN博客 https://zhuanlan.zhihu.com/p/347227068 在Android中&#xff0c;静态广播如果静态广播不能接收&#xff0c;我们可以从整个流程中去分析&#xff…

非递归遍历二叉树(数据结构)

我的博客主页 非递归遍历二叉树 前序遍历&#xff08;迭代&#xff09;中序遍历&#xff08;迭代&#xff09;后续遍历&#xff08;迭代&#xff09; 二叉树的遍历方式有&#xff1a;前序遍历、中序遍历、后续遍历&#xff0c;层序遍历&#xff0c;而树的大部分情况下都是通过递…

2024 java大厂面试复习总结(一)(持续更新)

10年java程序员&#xff0c;2024年正好35岁&#xff0c;2024年11月公司裁员&#xff0c;记录自己找工作时候复习的一些要点。 java基础 hashCode()与equals()的相关规定 如果两个对象相等&#xff0c;则hashcode一定也是相同的两个对象相等&#xff0c;对两个对象分别调用eq…

【可变参数,lambda,function,bind】

可变参数 Args模板参数包 解析参数包&#xff0c;使用递归和再来一个参数包。参数包传参时&#xff0c;会把第一个数据给前面的&#xff0c;剩下的数据全部传给后面的参数包&#xff0c;参数包就一直变小。 lambda表达式 书写格式&#xff1a;[capture-list] (parameters)…

ArcGIS API for Javascript学习

一、ArcGIS API for Javascript 介绍 ArcGIS API for Javascript 是由美国 Esri 公司推出&#xff0c;跟随ArcGIS 9.3 同时发布的&#xff0c;是Esri 基于dojo 框架和 REST 风格实现的一套编程接口。通过 ArcGIS API for Javascript可以对ArcGIS for Server 进行访问&#xff…

JavaScript的let、var、const

这张图片主要介绍了JavaScript中的三种变量声明方式&#xff1a;let、var和const。 1. let 含义&#xff1a;let是现在实际开发中常用的变量声明方式。特点&#xff1a; 块级作用域&#xff1a;let声明的变量只在其所在的块级作用域内有效。例如&#xff1a;{let x 10; } co…

24.11.25 Mybatis1

1.Mybatis介绍 1.封装JDBC 减少重复性代码 2.ORM(实体关系映射框架) 通过框架 实体类 <--> 数据表 自动封装对象 3.半自动的ORM框架 还需要写sql语句 2.使用mybatis连接数据库(调通一遍 记住需要哪些文件) 1.创建全局配置文件 mybatis-config.xml <?xml ver…

【Python爬虫五十个小案例】爬取猫眼电影Top100

博客主页&#xff1a;小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介&#xff1a;分享五十个Python爬虫小案例 &#x1f40d;引言 猫眼电影是国内知名的电影票务与资讯平台&#xff0c;其中Top100榜单是影迷和电影产业观察者关注的重点。通过爬取猫眼电影Top10…

Oh-My-ZSH安装教程

1. 安装zsh sudo apt-get install zsh2.安装on-my-zsh wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh上面方式很大可能因为网络连接问题而失败&#xff0c;可以通过以下方式&#xff1a; git clone gitgithub.com:ohmyzsh/ohmyzsh…

三六零[601360]行情数据接口

1、三六零&#xff1a;实时行情 Restful API # 测试接口&#xff1a;可以复制到浏览器打开 https://tsanghi.com/api/fin/stock/XSHG/realtime?tokendemo&ticker601360获取股票实时行情&#xff08;开、高、低、收、量&#xff09;。 请求方式&#xff1a;GET。 Python示例…

用 OceanBase 4.3.3,搭建《黑神话:悟空》的专属游戏AI助手

本文分享了如何基于 OceanBase 4.3.3 bp1 社区版的向量检索能力&#xff0c;通过几条简单的命令&#xff0c;快速搭建一个定制化的专属游戏助手的过程。 背景 在 OceanBase 最新推出 V 4.3.3 免费试用的同时&#xff0c;也同时发布了几个基于OB Cloud 的向量能力&#xff0c;搭…