施磊c++基础6

【施磊C++】设计模式 本文介绍了常见的设计模式,基于施磊老师的C++课程。 阅读详情
继承的基本意义

1.继承的本质和原理
继承的本质:代码的复用
类和类之间的关系:组合,继承

继承示例代码:

class person
{
public:
	int ma;
protected:
	int mb;
private:
	int mc;
};
class student : public person
{
public:
	int md;
protected:
	int me;
private:
	int mf;
};

在这里插入图片描述

继承方式 基类的访问限定 派生类的访问限定 外部的访问限定

继承方式基类的访问限定派生类的访问限定外部的访问限定
publicpublicpublicY
publicprotectedprotectedN
publicprivate不可见N
protectedpublicprotectedN
protectedprotectedprotectedN
protectedprivate不可见N
privatepublicprivateN
privateprotectedprivateN
privateprivate不可见N

总结:
1-外部只能访问对象public的成员,protected和private的成员无法直接访问。
2-在继承结构中,派生类从基类可以继承过来private的成员,但派生类却无法直接访问。
3-protected和private的区别?在基类中定义的成员,像被派生类访问,但是不想被外部访问,那么在基类中把相关成员定义为protected;如果派生类和外部都不打算直接访问,那么在基类中就定义为private。

默认的继承方式是?
要看派生类是用class定义的还是struct定义的

如果是class定义,默认定义是private
如果是struct定义,默认就是public

派生类的构造过程

派生类怎么初始化从基类继承而来的成员变量?

1.派生类从基类可以继承来所有成员(变量和方法),除了构造函数和析构函数
那么派生类怎么初始化从基类继承而来的成员变量呢?
答:通过调用基类的构造函数

派生类的构造函数和析构函数,负责初始化和清理派生类部分
派生类从基类继承来的成员的初始化和清理 — 由基类的构造和析构来负责

派生类构造具体过程
派生类调用基类构造函数 =》 初始化继承的基类成员 =》 调用派生类自己的构造函数 =》 初始化派生类自己特有的成员

重载、隐藏、覆盖
  1. 重载:
    一组函数要重载,必须要在同一作用域下:而且函数名相同,参数列表不同

  2. 隐藏(作用域的隐藏)的关系:
    在继承结构中,派生类的同名成员,把基类的同名成员隐藏了

虚函数、静态绑定和动态绑定

示例代码:

#include<iostream>
using namespace std;

class Base
{
public:
	Base(int data = 10)
		:ma(data){}
	void show() { cout << "Base::show()" << endl; }

	void show(int) { cout << "Base::show(int)" << endl; }



protected:
	int ma;

	
};


class Derive : public Base
{
public:
	Derive(int data = 20):Base(data),mb(data){}
	void  show() { cout << "Derive::show()" << endl; }

private:
	int mb;
};

int main()
{
	Derive d(50);
	Base* pb = &d;
	pb->show(); //静态(编译时期)绑定(函数)Base::show()
	pb->show(10);//Base::show()

	cout << sizeof(Base) << endl;//4
	cout << sizeof(Derive) << endl;//8


	cout << typeid(pb).name() << endl;//class Base*
	cout << typeid(*pb).name() << endl;//class Base

	return 0;
}

发现并不会调用派生类的方法,因为在编译时期,已经绑定了基类Base的方法

将方法定义成虚函数

class Base
{
public:
	Base(int data = 10)
		:ma(data){}
	virtual void show() { cout << "Base::show()" << endl; }
	virtual void show(int) { cout << "Base::show(int)" << endl; }
protected:
	int ma;

};

//Derive::show()

//Base::show(int)

就会使用动态绑定

如果类里面定义了虚函数,那么在编译阶段,编译器给这个类类型产生一个唯一的vftable函数表,虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。

一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始的部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。
一个类型定义的n个对象,他们的vfpt指向的都是同一张虚函数表

一个类里面,虚函数的个数不影响对象内存大小(vfpt),影响的是虚函数表的大小。

如果派生类中的方法,和基类继承来的方法,返回值,函数名,参数列表都相同,而基类的方法是虚函数,那么这个派生类的方法,自动处理成虚函数。(覆盖关系)

虚析构函数

问题一:哪些函数不能实现成虚函数?
虚函数:
1.虚函数能产生函数地址,存储在vftable当中
2.需要依赖对象,对象必须存在(vfptr -》vftable 虚函数地址)

构造函数中调用的任何函数都是静态绑定。

vitrual + static !不可能

问题二:析构函数调用的时候,对象是存在的,所以可以使用虚函数

虚析构作用:
如果析构函数不使用虚函数(动态绑定),那么使用基类指针指向派生类时,析构函数会静态绑定成基类的析构函数。此时如果派生类有自己开辟的内存时,这块内存由于没有调用派生类的析构函数而发生内存泄漏。

什么时候把基类的析构函数必须实现成虚函数?
基类指针指向堆上new出来的派生类对象时,delete pb基类的指针,它调用析构函数的时候,必须发生动态绑定。

再谈动态绑定

是不是虚函数的调用一定就是动态绑定?
不是的,在构造函数中,就算使用虚函数,也是静态绑定
其次,如果表示通过指针或者引用变量来调用虚函数,那么就是静态绑定


class Base
{
public:
	Base(int data = 10)
		:ma(data){}
	virtual void show() { cout << "Base::show()" << endl; }
	~Base() { cout << "~Base()" << endl; }
	virtual void show(int) { cout << "Base::show(int)" << endl; }
protected:
	int ma;

};


class Derive : public Base
{
public:
	Derive(int data = 20):Base(data),mb(data){}
	virtual void  show() { cout << "Derive::show()" << endl; }

private:
	int mb;
};

int main()
{
	Base b;
	Derive d;
	
	b.show();//虚函数 call Base::show
	d.show();//虚函数 call Derive::show
	//都是静态绑定
	//使用对象本身调用虚函数,是静态绑定

	//动态绑定
	Base* pb1 = &b;
	pb1->show();
	Base* pb2 = &d;
	pb2->show();

	Base& rb1 = b;
	rb1.show();
	Base& rb2 = d;
	rb2.show();
	return 0;
}
理解多态到底是什么

静态(编译)多态:
函数重载,模板(函数模板,类模板)

动态(运行)多态:
在继承结构在,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数)。
基类指针指向哪个派生类对象,就会调用哪个派生类对象的覆盖方法,称为多态。

phase=》show()
底层是通过动态绑定来实现的。phase=》访问谁的vfptr=》继续访问谁的vftable=》当然是对应的派生类对象的方法了

理解抽象类

抽象类和普通类有什么区别?

拥有纯虚函数的类叫做抽象类
抽象类不能再实例化对象了,但是可以定义指针和引用变量

一般把什么设计成抽象类?

一般把基类设计为抽象类

继承多态笔试题实战分析

案例一:分析结果

#include<iostream>
using namespace std;

class Animal
{
public:
	Animal(string name) :_name(name) {
	};
	virtual void bark() = 0;
protected:
	string _name;

};


class Cat: public Animal
{
public:
	Cat(string name):Animal(name){}
	void bark() { cout<<"Cat"<<endl; }
};

class Dog : public Animal
{
public:
	Dog(string name) :Animal(name) {}
	void bark() { cout<<"Dog"<<endl; }
};

int main()
{
	Animal* p1 = new Cat("加菲猫"); //vfptr -> dog vftable
	Animal* p2 = new Dog("二哈");


	int* p11 = (int*)p1;//p11[0],访问是Cat的前4字节 ---- vfptr
	int* p22 = (int*)p2;//p22[0],访问是Dog的前4字节 ---- vfptr
	int tmp = p11[0];
	p11[0] = p22[0];
	p22[0] = tmp;
	//相当于把指向虚函数表的指针替换了


	p1->bark();
	p2->bark();

	delete p1;
	delete p2;
	return 0;
}

案例二:

class Base
{
public:
	virtual void show(int i = 10)
	{
		cout << "call Base::show i :: " << i << endl;
	}
};

class Derive :public Base
{
public:
	void show(int i = 20)
	{
		cout << "call Derive::show i::" << i << endl;
	}
};


int main()
{
	Base* p1 = new Derive();
	/*
	* 函数调用,参数压栈是在编译时期就确定好了的
	push 0Ah
	mov eax, dword ptr[p1]
	mov ecx, dword ptr[eax]
	call ecx
	运行时绑定,已经定下来了是Base类型的指针
	原来的Derive的形参默认值永远是没用的

	只有用Derive的指针才有用
	*/

	p1->show();
	//call Derive::show i:: 10 ----- ? ? ?

	delete p1;
	return 0;
}

案例三:将派生类的方法写成私有的,结果发现还是可以调用

class Base
{
public:
	virtual void show(int i = 10)
	{
		cout << "call Base::show i :: " << endl;
	}
};

class Derive :public Base
{
private:
	void show(int i = 20)
	{
		cout << "call Derive::show i::" << i << endl;
	}
};


int main()
{
	Base* p1 = new Derive();

	p1->show();
	delete p1;
	return 0;
}
//call Derive::show i::

为什么?
答:成员方法能不能调用,就是说方法的访问权限是不是public,是在编译阶段就需要确定的。

而p1->show()是在运行阶段才能确定的
在编译阶段看,p1是Base类指针,show()方法在Base类中是public,完全没有问题!

案例四:


class Base
{
public:
	Base() {
		cout << "call Base()" << endl;
		clear();
	}

	void clear() {
		memset(this, 0, sizeof(*this)); 
		//从this指针开始,大小为sizeof(*this)的内存都赋值为0(就是Base的大小)
	}
	virtual void show()
	{
		cout << "call Base::show()" << endl;
	}
};
class Derive :public Base
{
public:
	Derive()
	{
		cout << "call Derive()" << endl;
	}

	void show()
	{
		cout << "call Derive::show()" << endl;
	}
};


int main()
{
	//都是动态绑定
	Base* pb1 = new Base();
	pb1->show();
	delete pb1;

	Base* pb2 = new Derive();
	pb2->show();
	delete pb2;

	return 0;


}

结果编译出错
上半部分pb1部分出错

下半部分pb2没问题

原因:
当new Base()的时候,Base()的构造函数调用了clear(),把new的Base对象的内容全部归零了,包括vfptr,而在运行的时候,找不到vftable了,就出错了。
在这里插入图片描述

而下半部分new的是一个派生类Derive:
先调用基类Base()的构造,会把Base的vftable的地址写入vfptr
然后把Base()部分clear了
然后再调用派生类Derive的构造,会把Derive的vftable的地址写入vfptr
这样就又有了正确的虚函数表的地址了
在这里插入图片描述

这个问题的重点就是,vftable的地址是什么时候写入vfptr的!

施磊C++项目-【高级】集群聊天服务器-软件分层设计和高性能服务开发 5、负载均衡中,每个服务器有不同的人进行注册,不同服务器上的用户要进行通信的话,主要引入了redis的发布订阅作为MQ消息队列的功能,实现跨服务器的消息通信。4、单机下并发能力有限,要多机扩展,挂一个nginx的负载均衡,基于tcp的cs通信,是一个长连接(客户端给服务器发消息,服务端给客户端推消息)压力测试:单机到5、6千,是跟服务器的fd有关的,所以可以更改fd的最大上限,可以让单机达到两万的并发量。2、服务层map和绑定器,做了一个消息的fd,从消息解析到json,得到消息的id,通过回调处理消息。 阅读详情

相关推荐

施磊老师基于muduo网络库的集群聊天服务器(三)

业务模块, 数据库模块, 使用ORM框架 实现注册业务, 并进行测试

weixin_65612492的博客 3027

施磊老师 C++ 课程笔记--自己记录用

施磊老师 C++课程笔记

qq_35818865的博客 5976

【施磊】C++课程笔记目录

【C++编程】施磊老师C++笔记目录

GeniusAng的博客 4124

【施磊C++】多线程基础

本文介绍了C++多线程编程的基础知识,基于施磊老师的C++课程。

source7777的博客 809

施磊C++课程学习路径,以及方法教程

最强C++内容,C++学习路线。

保姆级教程:用Ansible 2.9自动化配置华为ENSP路由器(附Cloud连接避坑指南)

本文提供了一份详细的Ansible 2.9与华为ENSP路由器自动化配置教程,涵盖环境搭建、网络连通性配置、Playbook开发及典型问题排查。特别针对Cloud连接常见问题提供避坑指南,帮助运维工程师快速掌握网络自动化技能,提升工作效率。

weixin_42561476的博客 376

【施磊C++】智能指针

本文介绍了C++中智能指针的原理与实现,基于施磊老师的C++课程。});return 0;

source7777的博客 1599

告别License烦恼:手把手教你用VS Code+Cppcheck搭建免费的MISRA-C代码检查环境

本文详细介绍了如何使用VS Code和开源工具Cppcheck搭建免费的MISRA-C代码检查环境,帮助开发者规避商业工具的高额License费用。通过四步环境搭建和实战技巧,实现高效的代码静态检查,提升嵌入式开发的安全性和可靠性。

weixin_42523326的博客 248

施磊老师c++笔记(三)

模板编程, 容器配置空间allocator

weixin_65612492的博客 1171

逆向解析喜马拉雅新版xm-sign算法:从dws.1.6.8.js到签名生成

本文详细解析了喜马拉雅新版xm-sign算法的逆向破解方法。通过定位核心JS文件dws.1.6.8.js,构建本地Node.js模拟执行环境,并解密关键参数cid与appKey,最终实现签名算法的本地化稳定生成,为数据采集提供可靠解决方案。

weixin_29250403的博客 214

实战分享:一文读懂RS-232总线

RS-232标准接口(又称EIARS-232)是常用的串行通信接口标准之一,它是由美国电子工业协会(Electronic IndustryAssociation,EIA)联合贝尔系统公司、调制解调器厂家及计算机终端生产厂家于1970年共同制定,其全名是“数据终端设备(DTE)和数据通信设备(DCE)之间串行二进制数据交换接口技术标准”。

阿荣嵌入式 8520

施磊c++基础8

C++STL : standard template libaray底层数据结构:动态开辟的数组。每次以空间大的二倍扩容增加 vec.push_back(20); 末尾添加元素20 — O(1) vec.insert(it,20);在it迭代器指向的位置插入元素20 — O(n)删除 vec.pop_back; 末尾删除元素 ---- O(1) vec.erase(it) ;删除it迭代器指向的位置的元素 — O(n)查询 operator[ ]使用vec[ i ]访问第i个位置元素 — O(1) iter

fcopy的博客 1217

QGis软件 —— 5、QGis增加线,并对线进行绘制、旋转、缩放、移动等基本操作

QGis软件 —— 5、QGis增加线,并对线进行绘制、旋转、缩放、移动等基本操作

8246

施磊c++基础2

所以在C++中第一个结果和第三个结果都是被常量初始化替换了,就是初始化的10,而实际上a的值也已经被*p给修改了。inline内联函数,在编译过程中就没有函数调用的开销了,在函数调用点直接把函数的代码进行展开处理。答2:实际上就是作用域的问题,只有在同一个作用域内的相同函数名直接使用不同的参数列表才能进行重载。什么是函数重载:从函数调用来看,是处在同一作用域内的同名但不同参数列表的一组函数。由于c++和c生产符号的规则不一样,所以在编译的时候,无法进行对应(找不到)。

fcopy的博客 835

YOLOv12优化:原创自研 | 带有注意力机制的隐藏状态混合器的状态空间(HSM-SSD),实现暴力涨点 | CVPR2025 EfficientViM

可以将 带有注意力机制的隐藏状态混合器的状态空间(HSM-SSD)中的注意力机制换成专栏其他注意力机制 ,那么你就能实现独一无二的原创。

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 664

合肥工业大学操作系统实验9

依据操作系统课程所介绍的页面置换算法与动态内存分配的原理,本实验的目的是掌握OPT、FIFO、LRU、LFU、Clock等页面置换算法;掌握可用空间表及分配方法;掌握边界标识法以及伙伴系统的内存分配方法和回收方法。从而深入理解操作系统的内存管理。从而加深对教材上页面置换算法与动态内存分配的理解。

Martin-share的博客 1095

施磊C++高级进阶课程 | 学习笔记 | 博客汇总

剩余有关面试的等到明年投简历前学习。

m0_74795952的博客 2710

Halcon那些事:Halcon缺陷检测检测印刷数字完整性

创建模板 create_shape_model模板匹配 find_shape_model。

十多年IT经验,专注机器视觉,智能制造半导体行业 766

施磊c++基础3

那么就会出错,原因是,在创建一个CGoods类时,由于在构造函数中并没有指明_date要如何构造,使用会使用Cdate的默认构造函数,但在Cdate中,我们重写了他的构造函数,那么他就不会提供默认的构造函数了。上图的浅拷贝中,s2仅仅是复制了一份s1对象的内容的一个新对象,但他们里面指针指向的仍然是同一块内存,当s1析构之后,这块内存就被释放,此时s2析构时去释放一块不存在的内存,就会出错。即,类的成员方法一经编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址。

fcopy的博客 1007

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

施磊c++基础5

示例代码1 — 删除容器中的偶数i < 20;++i)for (;it!报错了!!!为什么?//第一次调用erase以后,迭代器it就失效了//那么再进行++就报错示例代码2 — 给vec容器中所有的偶数添加一个小于偶数值1的数字for (;it!也会报错在遇到第一个偶数的时候,是把第一个偶数及其以后的迭代器全部失效所以到底为什么会失效?

fcopy的博客 910

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

施磊老师c++笔记(一)

学习c++还必须掌握的!

weixin_65612492的博客 1143

【施磊C++】对象优化

本文介绍了C++中的对象优化,基于施磊老师的C++课程。1、函数参数传递过程中,对象优先按引用传递,不要按值传递2、函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象3、接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收。

source7777的博客 532

施磊老师c++笔记(四)

运算符重载相关, 对象池应用

weixin_65612492的博客 923

施磊c++基础7

注意:此时需要在D中加入A的构造,即使从继承类看,他并没有直接继承A。但是由于B,C都是虚继承,此时A的构造就要定位到D中对A进行构造;由于内存结构*p指向的是派生类基类部分数据的起始地址D8,但是vbptr和mb都还在前面,这时候delete p;这两句是一样的,只不过使用第二种,可以保证类型转换是安全的,如果要转换成不符合的类型就会报错。基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址。对于第一种,int转char,编译器认为是安全的,就可以通过。d的内存布局 ---- 占20字节。

fcopy的博客 565

施磊c++基础4

当使用pop()的时候,仅仅是_last–了,原来Test指向的内存并没有析构掉,当创建新对象时,原来的内存就丢掉了。,那么他就会强转为int类型,并且抛出一个警告,说明在T类型转换时可能会发生数据丢失。这使得我们可以只用一套代码实现一套逻辑,至于具体的实例化,交给编译器就行了。可以根据用户传入的实参类型,来推导出模板类型参数的具体类型。其实,在STL中,有很多的对于字符串类型的特例化版本。,a与b是同一类型,此时编译器无法判断,就会报错。模板函数,模板的特例化,非模板函数的重载关系。

fcopy的博客 462

C++基础笔记汇总——施磊课程

3.从编译器角度理解C++代码的编译和链接原理。1.掌握进程虚拟空间地址区域划分。

qq_58696030的博客 1805
上一篇: 施磊c++基础5
下一篇: 施磊c++基础7
fcopy
博客等级 码龄6年 252粉丝 30原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值