【浅尝C++】多态机制=>重载重写隐藏的区别/抽象类/单继承与多继承的虚函数表/多态原理及虚函数表内存存储详谈

news2024/11/18 2:30:35

在这里插入图片描述

🏠专栏介绍:浅尝C++专栏是用于记录C++语法基础、STL及内存剖析等。
🎯每日格言:每日努力一点点,技术变化看得见。

文章目录

  • 多态的概念
  • 多态的定义及实现
    • 多态的构成条件
    • 虚函数
    • 虚函数的重写
    • override与final(C++11)
    • 重载、重写(覆盖)、隐藏(重定义)的对比
  • 抽象类
    • 概念
    • 实现继承与接口继承
  • 多态的原理
    • 虚函数表
    • 多态原理
    • 动态绑定与静态绑定
  • 单继承和多继承关系中的虚函数表
    • 单继承中的虚函数表
    • 多继承中的虚函数表


多态的概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

在生活中,我们在乘坐公交车时,不同的人会有不同的折扣力度,例如:学生卡8折,老人卡免费等等。对于同一件事,会发生不同的不同形态就称为多态。

多态的定义及实现

多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。

例如:下图中Student类继承了Person类。两个对象在调用同一个函数takeBus时,一个执行的是Person中的getTickets,一个执行的是Student中的getTicket。这就是调用同一个函数takeBus,却产生了不同的行为。
在这里插入图片描述

继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

在讲述这两个条件之前,我们先来聊聊什么是虚函数↓↓↓

虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。

下面代码中,getTicket成员函数被virtual修饰,因而它是虚函数。↓↓↓

class Person
{
public:
	virtual void getTicket()
	{
		cout << "全票" << endl;
	}
};

虚函数的重写

那虚函数有什么用途呢?如果将基类的成员函数用virtual修饰成为虚函数后,派生类中可以对该成员函数进行重写(覆盖)。

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

下面代码中的Person类与Student类中的getTicket构成了虚函数重载↓↓↓

#include <iostream>
using namespace std;

class Person
{
public:
	virtual void getTicket()
	{
		cout << "全票" << endl;
	}
};

class Student : public Person
{
public:
	//派生类中可以省略virtual关键字,但不推荐
	//void getTicket() --> 这样写也可以
	virtual void getTicket()
	{
		cout << "8折" << endl;
	}
};

void takeBus(Person* p)
{
	p->getTicket();
}

void takeBusByReference(Person& p)
{
	p.getTicket();
}

int main()
{
	Student s;
	Person p;

	s.getTicket();
	s.Person::getTicket();
	cout << "================" << endl;
	takeBus(&s);
	takeBus(&p);
	cout << "================" << endl;
	takeBusByReference(s);
	takeBusByReference(p);
	return 0;
}

在这里插入图片描述
从上面代码中可以看出,如果从s.getTicket();s.Person::getTicket();两行代码来看,重写(覆盖)与隐藏并没有多大区别。没有显示指定调用基类的同名函数时,会默认调用派生类的;如果显示指定了基类作用域,则会调用基类的。

重写(覆盖)与隐藏的区别在于,重写执行takeBus及takeBusByReference时,对于Person对象和Student对象,它会调用对应对象的同名(同名、同参数列表、同返回值)函数。如果是隐藏,则都只会执行指针或引用类型对应类的函数,而不会执行它的子类的同名函数(同名、同参数列表、同返回值)。


虚函数重写(覆盖)的两个特例:

协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同,但两个不同的返回值之间必须构成子类与父类关系。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

下面代码给出了协变示例↓↓↓

#include <iostream>
using namespace std;

class Base
{};
class Son : public Base
{};

class Person
{
public:
	virtual Base* ptest()
	{
		cout << "Person's ptest()" << endl;
		return &b;
	}
	virtual Base& refertest()
	{
		cout << "Person's refertest()" << endl;
		return b;
	}
private:
	static Base b;
};

Base Person::b = Base();

class Student : public Person
{
public:
	virtual Son* ptest()
	{
		cout << "Son's ptest()" << endl;
		return &s;
	}
	virtual Son& refertest()
	{
		cout << "Son's refertest()" << endl;
		return s;
	}
private:
	static Son s;
};

Son Student::s = Son();

void test(Person* p)
{
	p->ptest();
	p->refertest();
}

int main()
{
	Person p;
	test(&p);
	Student s;
	test(&s);
	return 0;
}

在这里插入图片描述
上面代码中Base与Son构成父子类关系,Person与Student类中的ptest及refertest分别返回父子类的指针和引用,因而构成协变关系。故在使用父类Person指针指向Student时,会调用Student的成员函数,因为此时构成了重写(覆盖)。

析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。(此时函数名、返回值、参数列表相同,满足构成重写的条件)

析构函数的重写主要应用在:父类与子类构成函数重载时,如果父类指针指向子类对象(在没有重写的情况下),只会调用父类的析构函数,这样会导致子类的空间未释放干净,进而导致内存泄漏。为了防止内存泄漏,在使用父类指针或引用调用子类的析构函数时(在构成重写的情况下),会调用子类的析构函数,子类析构函数不仅会释放父类继承下来的成员变量,还会释放自己多出的成员变量。

override与final(C++11)

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

  1. final:修饰虚函数,表示该虚函数不能再被重写

下面代码中Person重写了Animal中的showInfo,此时Person中的showInfo使用final修饰,则它的子类Student不能重写该函数。↓↓↓

class Animal
{
public:
	virtual void showInfo()
	{}
};

class Person : public Animal
{
public:
	virtual void showInfo() final
	{}
};

class Student : public Person
{
public:
	virtual void showInfo()//error!! 父类已经使用final修饰,表示不能够被重写(覆盖)
	{}
};

在这里插入图片描述
2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

下面代码中,Person类的showInfo使用override关键字检查showInfo是否与Animal中的ShowInfo构成重写,由于两者函数声明不同(参数列表不同),故不能构成重写而报错。

class Animal
{
public:
	virtual void showInfo()
	{}
};

class Person : public Animal
{
public:
	virtual void showInfo(int) override
	{}
};

在这里插入图片描述

重载、重写(覆盖)、隐藏(重定义)的对比

下图说明了重载、重写、隐藏之间的不同之处。注意:如果两个函数分别在基类和派生类作用域,并且函数名相同,如果没有构成重写(覆盖),则构成隐藏(重定义)。
在这里插入图片描述

抽象类

概念

在虚函数的后面写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数使得抽象类的各个派生类必须提供提供某个相同的接口,体现了接口继承。

下面程序中,Add类与Sub类均继承了Intern类,因而它们均需要提供show接口↓↓↓

#include <iostream>
using namespace std;

class Intern
{
public:
	virtual void show() = 0;//纯虚函数
}; 

class Add : public Intern
{
public:
	Add(int num1, int num2)
		:_num1(num1)
		, _num2(num2)
		, _result(num1 + num2)
	{}
	virtual void show() override
	{
		cout << _num1 << " + " << _num2 << " = " << _result << endl;
	}
private:
	int _num1;
	int _num2;
	int _result;
};

class Sub : public Intern
{
public:
	Sub(int num1, int num2)
		:_num1(num1)
		, _num2(num2)
		, _result(num1 - num2)
	{}
	virtual void show() override
	{
		cout << _num1 << " - " << _num2 << " = " << _result << endl;
	}
private:
	int _num1;
	int _num2;
	int _result;
};

int main()
{
	Add add(2, 1);
	add.show();
	Sub sub(2, 1);
	sub.show();
	return 0;
}

在这里插入图片描述

实现继承与接口继承

●普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用基类的函数,继承的是函数的实现。
●虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口(接口等同于函数声明)。

所以如果不实现多态,建议不要把函数定义成虚函数。如果隐藏(重定义)已经能满足需求的情况下,如果将其改为虚函数,则会导致当前类及该类的派生类会多出虚函数表指针及虚函数表的空间开销。

多态的原理

虚函数表

空类的大小为1个字节,这1字节用于标识这个类的存在。非空类的大小等于它的成员变量的大小(需要考虑内存对齐),而成员函数存储于公共代码段,不计入类对象的大小中。咱使用一个代码验证空类的大小↓↓↓

#include <iostream>
using namespace std;

class Empty
{};

class Base
{
public:
	virtual void virtualBaseFunc()
	{}
	void normalBaseFunc()
	{}
	int _base;
};

int main()
{
	Empty e;
	Base b;
	b._base = 2;
	cout << "size of Empty is " << sizeof(e) << endl;
	cout << "size of Base is " << sizeof(b) << endl;
	return 0;
}

在这里插入图片描述
Base类中明明只有一个成员变量_base,它的大小不应该是8吗?为什么是16呢?我们使用监视查看,b对象中不仅有成员变量_base,还多出了一个指针。
在这里插入图片描述
我们再使用内存查看以下对象b的存储情况↓↓↓
在这里插入图片描述
我们发现这个指针存储在整个对象的最前面,在它后面跟着的才是对象的成员变量。

对象中的这个指针我们叫做虚函数表指针。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表简称为虚表。

下面我们再来看看派生类中的虚函数指针指向的是什么?它是如何存储的↓↓↓

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void virtualBaseFunc1()
	{}
	virtual void virtualBaseFunc2()
	{}
	void normalBaseFunc()
	{}
	int _base;
};

class Son : public Base
{
public:
	virtual void virtualBaseFunc1()
	{}
	virtual void virtualSonFunc()
	{}
	void normalSonFunc()
	{}
	int _son;
};

int main()
{
	Base b;
	Son s;
	s._base = 1;
	s._son = 2;
	return 0;
}

上面代码通过监视窗口可以看到,对象s中的虚函数表存储的virtualBaseFunc1与对象b中的不同,因为Son类中重写(覆盖)了Base中virtualBaseFunc1虚函数;而s中的虚函数表存储的virtualBaseFunc2与对象b相同,因为Son类中没有对该虚函数进行重写。
在这里插入图片描述
通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象s中也有一个虚表指针,s对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。(下图是虚函数表指针指向的地址的内存情况)
    在这里插入图片描述
  2. 基类b对象和派生类s对象虚表是不一样的,这里我们发现virtualBaseFunc1完成了重写,所以s的虚表中存的是重写的Base::virtualBaseFunc1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外virtualBaseFunc2继承下来后是虚函数,所以放进了虚表,normalBaseFunc也继承下来了,但是它不是虚函数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  5. 总结一下派生类的虚表生成:
    a.先将基类中的虚表内容拷贝一份到派生类虚表中
    b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
    c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后
  6. 这里还有一个很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 对象中存储的是虚表指针,虚表并不存储在对象内,而虚函数的地址存储于虚表中,虚函数和普通函数一样的,都是存在代码段的。

下面代码用于验证虚函数表存储于代码段↓↓↓

#include <iostream>
using namespace std;

typedef void(*VFunc_Ptr)();

class Base
{
public:
	virtual void virtualBaseFunc1()
	{}
	virtual void virtualBaseFunc2()
	{}
	void normalBaseFunc()
	{}
	int _base;
};

class Son : public Base
{
public:
	virtual void virtualBaseFunc1()
	{}
	virtual void virtualSonFunc()
	{}
	void normalSonFunc()
	{}
	int _son;
};

void PrintVFuncTable(VFunc_Ptr* p)
{
	for (int i = 0; p[i]; i++)
	{
		cout << p[i] << endl;
	}
}

int main()
{
	Base b;
	Son s;
	s._base = 1;
	s._son = 2;
	cout << main << endl;
	PrintVFuncTable((VFunc_Ptr*)(*(VFunc_Ptr**)(&s)));
	return 0;
}

在这里插入图片描述
上面结果打印了main函数的地址及s对象中的各个虚函数的地址。下图是虚函数指针指向的地址0x00007FF7BBE7BD18的内存情况↓↓↓
在这里插入图片描述
由上面可以得出虚函数与其他函数一样存储于代码段(地址相近),而虚函数表也存储于代码段(与上面一个函数的地址相近)。

含有虚函数的派生类的内存存储情况如下图所示↓↓↓
在这里插入图片描述

多态原理

下面代码中Son继承了Base,并重写了Base中的func1↓↓↓

#include <iostream>
using namespace std;

class Base
{
public:
	virtual void func1()
	{
		cout << "Base-func1()" << endl;
	}
	virtual void func2()
	{
		cout << "Base-func2()" << endl;
	}
};

class Son : public Base
{
public:
	virtual void func1()
	{
		cout << "Son-fun21" << endl;
	}
	void func3()
	{
		cout << "Son-func2" << endl;
	}
};

void test(Base* p)
{
	p->func1();
	p->func2();
}

int main()
{
	Son s;
	s.func3();
	test(&s);
	return 0;
}

对上述程序使用监视窗口调试,获取它的虚函数表指针及各个虚函数的地址↓↓↓
在这里插入图片描述
对于func3,它不是使用多态调用的,因此,在编译阶段已经知道它的函数地址了。lea将s对象存入ecx寄存器,后调用直接call调用Son::func3
在这里插入图片描述
而对于多态调用的func1和func2。它无法直接像上述func3一样调用。它的调用情况如下图所示↓↓↓
在这里插入图片描述

从上图分析可知,满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

动态绑定与静态绑定

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

单继承和多继承关系中的虚函数表

单继承中的虚函数表

#include <iostream>
using namespace std;

typedef void(*VFunc_Ptr)();

class Base
{
public:
	virtual void func1()
	{
		cout << "Base-func1()" << endl;
	};
	virtual void func2()
	{
		cout << "Base func2()" << endl;
	}
	int _base;
};

class Son : public Base
{
public:
	virtual void func1()
	{
		cout << "Son func1()" << endl;
	}
	virtual void func3()
	{
		cout << "Son func3()" << endl;
	}
	virtual void func4()
	{
		cout << "Son func4()" << endl;
	}
	int _son;
};

void PrintVFuncTable(VFunc_Ptr* p)
{
	for (int i = 0; p[i]; i++)
	{
		cout << p[i] << endl;
		p[i]();
	}
}

int main()
{
	Son s;
	VFunc_Ptr* p = (VFunc_Ptr*)(*(VFunc_Ptr**)(&s));
	PrintVFuncTable(p);
	return 0;
}

在这里插入图片描述
根据上面的代码执行结果,可以总结出如下的内存存储情况图↓↓↓
在这里插入图片描述

多继承中的虚函数表

#include <iostream>
using namespace std;

typedef void(*VFunc_Ptr)();

class Base1
{
public:
	virtual void func1()
	{
		cout << "Base1-func1()" << endl;
	};
	virtual void func2()
	{
		cout << "Base1 func2()" << endl;
	}
	int _base1;
};

class Base2
{
public:
	virtual void func3()
	{
		cout << "Base2-func3()" << endl;
	};
	virtual void func4()
	{
		cout << "Base2 func4()" << endl;
	}
	int _base2;
};

class Son : public Base1, public Base2
{
public:
	virtual void func1()
	{
		cout << "Son func1()" << endl;
	}
	virtual void func3()
	{
		cout << "Son func3()" << endl;
	}
	virtual void func5()
	{
		cout << "Son func5()" << endl;
	}
	int _son;
};

void PrintVFuncTable(VFunc_Ptr* p)
{
	for (int i = 0; p[i]; i++)
	{
		cout << p[i] << endl;
		p[i]();
	}
}

int main()
{
	Son s;
	s._base1 = 1;
	s._base2 = 2;
	s._son = 3;
	VFunc_Ptr* p = (VFunc_Ptr*)(*(VFunc_Ptr**)(&s));
	PrintVFuncTable(p);
	cout << "===================================" << endl;
	VFunc_Ptr* p2 = (VFunc_Ptr*)(*((VFunc_Ptr**)(&s) + 2));
	PrintVFuncTable(p2);
	return 0;
}

上述代码通过监视窗口可以看到如下内容。Son对象保存了Base1与Base2的两个虚函数指针。由于VS下的监视窗口存在BUG,无法显示当前对象的虚函数存储哪张虚函数表中,我们可以通过查看程序执行结果来查看。
在这里插入图片描述
我们从执行结果查看可以得到,当前对象的自己的虚函数存储第一张虚函数表的最后。
在这里插入图片描述
下面给出上述程序中对象s的内存存储情况↓↓↓
在这里插入图片描述

因此,我们可以得到多继承的虚函数表存储示意图↓↓↓
在这里插入图片描述

🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

ffmpeg 将多个视频片段合成一个视频

ffmpeg 将多个视频片段合成一个视频 References 网络视频 6 分钟的诅咒。 新建文本文件 filelist.txt filelist.txtfile output_train_video_0.mp4 file output_train_video_1.mp4 file output_train_video_2.mp4 file output_train_video_3.mp4 file output_train_video_4.m…

C语言完结篇(17)

编译和链接 1. 翻译环境和运⾏环境 2. 翻译环境&#xff1a;预编译编译汇编链接 我们知道计算机能够执行的是二进制的指令 而我们的C语言代码都是文本信息 所以我们需要让C语言代码转变为二进制的指令&#xff08;这是需要编译器来进行处理的&#xff09; 翻译环境和运⾏…

2024年MathorCup妈妈杯数学建模思路D题思路解析+参考成品

1 赛题思路 (赛题出来以后第一时间在群内分享&#xff0c;点击下方群名片即可加群) 2 比赛日期和时间 报名截止时间&#xff1a;2024年4月11日&#xff08;周四&#xff09;12:00 比赛开始时间&#xff1a;2024年4月12日&#xff08;周五&#xff09;8:00 比赛结束时间&…

RGB三通道和灰度值的理解

本文都是来自于chatGPT的回答!!! 目录 Q1:像素具有什么属性?Q2:图像的色彩是怎么实现的?Q3:灰度值和颜色值是一个概念吗?Q4:是不是像素具有灰度值&#xff0c;也有三个颜色分量RGB&#xff1f;Q5:灰度图像是没有色彩的吗&#xff1f;Q6: 彩色图像是既具有灰度值也具有RGB三…

Java Spring IoCDI :探索Java Spring中控制反转和依赖注入的威力,增强灵活性和可维护性

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;Java Spring IoC&DI :探索Java Spring中控制反转和依赖注入的威力,增强灵活性和可维护性 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 前提小知识:高内…

16-代码随想录206反转链表

16-代码随想录206反转链表 206.反转链表 力扣题目链接(opens new window) 题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&…

RTX RTOS 操作实例分析之---线程(thread)

0 Preface/Foreword 1 线程&#xff08;thread&#xff09; 1.1 线程定义 1.1.1 USE_BASIC_THREADS&#xff08;宏定义&#xff09; 经过以上步骤&#xff08;makefile包含&#xff09;&#xff0c;USE_BASIC_THREADS在编译阶段被定义到相应的模块中。 1.1.2 定义线程ID变量…

博客部署004-centos安装mysql及redis

1、如何查看当前centos版本&#xff1f; cat /etc/os-release 2、安装mysql 我的是centos8版本&#xff0c;使用dnf命令 2.1 CentOS 7/8: sudo yum install -y mysql-community-server 或者在CentOS 8上&#xff0c;使用DNF:&#x1f31f; sudo dnf install -y mysql-ser…

无尽加班何时休--状态模式

1.1 加班&#xff0c;又是加班&#xff01; 公司的项目很急&#xff0c;所以要求加班。经理把每个人每天的工作都排得满满的&#xff0c;说做完就可以回家&#xff0c;但是没有任何一个人可以在下班前完成的&#xff0c;基本都得加班&#xff0c;这就等于是自愿加班。我走时还有…

[技术闲聊]我对电路设计的理解(七)-Cadence原理图绘制

一、原理图软件推荐 之前的章节有讲过AD、PADS、Cadence&#xff0c;以及三者的应用标准&#xff0c;今天再讲讲这一点。 如果是学生&#xff0c;可以学习AD软件&#xff0c;因为学校在学习&#xff0c;上手容易&#xff0c;而且即使工作后&#xff0c;如果是电机控制等4层板或…

数据劫持的冲突问题

在近段时间我又再一次使用了数据劫持&#xff0c;发现了一些冲突问题&#xff0c;并在此介绍我所应用的场景。 一、冲突问题 在之前的文章中有介绍过数据劫持&#xff0c;但后来使用的很少&#xff0c;最近在一次使用的过程中&#xff0c;发现了一些问题。 1.value属性的冲突…

第十四届蓝桥杯省赛大学C组(C/C++)填充

原题链接&#xff1a;填充 有一个长度为 n 的 01 串&#xff0c;其中有一些位置标记为 ?&#xff0c;这些位置上可以任意填充 0 或者 1&#xff0c;请问如何填充这些位置使得这个 01 串中出现互不重叠的 0 和 1 子串最多&#xff0c;输出子串个数。 输入格式 输入一行包含一…

【51单片机学习记录】超声波测距

一、超声波测距概述 &#xff08;1&#xff09;超声波时间差测距原理 超声波发射器向某一方向发射超声波&#xff0c;在发射时刻的同时开始计时&#xff0c;超声波在空气中传播&#xff0c;途中碰到障碍物就立即返回来&#xff0c;超声波接收器收到反射波就立即停止计时。超声…

环形链表 - LeetCode 热题 25

大家好&#xff01;我是曾续缘&#x1f970; 今天是《LeetCode 热题 100》系列 发车第 25 天 链表第 4 题 ❤️点赞 &#x1f44d; 收藏 ⭐再看&#xff0c;养成习惯 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可…

道路病害的检测与评估

基于yolov8道路病害的检测与评估 1 安装yolov8并使用 1.下载后: 2.选择为所有用户安装 3.选择一个合适的目录 4.第一个是自动添加环境变量,我们用默认的第二个后边手动添加 5.等待安装 1.1 安装anconda并配置环境变量 安装完成anconda之后,主要用的两个为Anaconda Prompt …

用树莓派获取传感器数据通过Onenet云平台(物模型)传输至微信小程序(上)

前言 为了传输传感器数据&#xff0c;在网上找了很多方法&#xff0c;但都因为各种各样的问题最终没能成功实现。最终找到这个既简单&#xff0c;又方便实现的方法。步骤有点多&#xff0c;希望大家可以跟着教程&#xff0c;一步步耐心的做下去&#xff0c;愿大家都能成功实现数…

ubuntu系统安装systemc-2.3.4流程

背景&#xff1a;systemC编程在linux下的基础环境配置 1&#xff0c;下载安装包&#xff0c;并解压 &#xff08;先下载了最新的3.0.0&#xff0c;安装时候显示sc_cmnhdr.h:115:5: error: #error **** SystemC requires a C compiler version of at least C17 **** &#xff…

idea开发 java web 高校学籍管理系统bootstrap框架web结构java编程计算机网页

一、源码特点 java 高校学籍管理系统是一套完善的完整信息系统&#xff0c;结合java web开发和bootstrap UI框架完成本系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 前段主要技术 css jq…

【云计算】云数据中心网络(一):VPC

云数据中心网络&#xff08;一&#xff09;&#xff1a;VPC 1.什么是 VPC2.VPC 的组成2.1 虚拟交换机2.2 虚拟路由器 3.VPC 网络规划3.1 VPC 数量规划3.2 交换机数量规划3.3 地址空间规划3.4 不同规模企业地址空间规划实践 4.VPC 网络高可靠设计4.1 单地域单可用区部署4.2 单地…