C++多态(超级详细版)

news2024/9/20 18:26:28

目录

 

一、什么是多态

二、多态的定义及实现

1.多态构成条件

2.虚函数的重写和协变

虚函数重写的两个例外:

2.1协变

2.2析构函数的重写  (析构函数名统一处理成destructor)

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

4.final 和 override

三、抽象类 

四.多态的原理

1.虚函数表

2.多态的原理 

2.1虚表指针里的内容

2.2引用和指针如何实现多态 

2.3普通类接收为什么实现不了多态

 3.虚函数表存放位置

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

1.单继承中的虚函数表

2.多继承中的虚函数表

3.菱形继承和菱形虚拟继承

做一道题吧


一、什么是多态

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

举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人 买票时是优先买票。 

二、多态的定义及实现

1.多态构成条件

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

1. 必须通过基类的指针或者引用调用虚函数

2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{ 
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

注意:接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数

在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用

2.虚函数的重写和协变

上面例子中,我们实现了虚函数的重写(覆盖):

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

虚函数重写的两个例外:

2.1协变

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用派生类虚函数返回派生类对象的指针或者引用时,称为协变。

这里不仅仅可以返回当前基类和子类的类型,还可以返回其他有继承关系的类和类型。

2.2析构函数的重写  (析构函数名统一处理成destructor)

首先,我们来看看析构函数不处理成virtual的情况

我们本义是想让p1调用Person的析构,p2先调用Person的析构在调用Student的析构,但是这里并没有调用Student的析构,只析构了父类,就可能发生内存泄漏。

这是为什么呢? 

因为这里发生了隐藏,~Person()变为 this->destructor()  ~Student()为this->destructor() 

编译器将他们两个的函数名都统一处理成了destructor,因此调用的时候只看自身的类型,是Person就调用Person的函数,是Student就调用Student的函数,根本不构成多态,这并不是我们期望的哪样。

 我们给析构函数添加上virtual

发现子类对象,Student对象就能正常析构了

注意::析构函数加virtual是在new场景下才需要, 其他环境下可以不用

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

4.final 和 override

在添加父类虚函数后面添加final代表不能再被重写

 

 final修饰类,代表不能被继承

override代表必须要重写虚函数,如果没有重写便会报错

三、抽象类 

在虚函数的后面写上 =0 ,则这个函数为纯虚函数包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

注意这里的包含,只要类里面有一个有纯虚函数,就是抽象类,就无法实例化对象,间接强制派生类重写。

 

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

四.多态的原理

1.虚函数表

以下代码环境在X86中,涉及到的指针是4个字节

我们定义一个Base类,里面有虚函数,还有一个变量int,按照我们之前学习到了,这里Base类的大小应该是4个字节,图中确是8个字节

为什么会发生这种现象呢?

用监视窗口看一下

除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表。

其实应该叫__vftptr(多个t代表table)

我们多添加几个虚函数,看看这个表里面的内容是怎么样的 

可以发现虚函数会放到虚函数表中,普通函数不会,并且表里面的内容是一个数组,是函数指针数组

2.多态的原理 

 有了虚函数表的概念,我们可以尝试通过虚函数表,去找到多态的原理

下面是测试代码

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	virtual void fun(){}
private:
	int a;
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
private:
	int b;
};
void Func(Person* p)
{
	p->BuyTicket();
}
int main()
{
	Person p;
	Student s;
	Func(&p);
	Func(&s);
	return 0;
}

2.1虚表指针里的内容

从图中我们可以看到,在内存1里面输入&p可以找到p的地址, 因为p的第一个内容就是__vfptr,因此p的地址也是__vfptr的地址,那么我们通过__vfptr的地址就可以找到虚函数表里面的内容,因此我们在内存2里面输入__vfptr的地址,我们便找到了两个虚函数的地址。 

去找s的虚表虚函数也同理 

为什么我们要这么麻烦的去找呢?监视窗口不是可以看到吗?

这是因为VS2022的监视窗口可能会骗人(不一定百分百准确),使用内存是一定准确的。

通过上面的图片,我们可以提炼出如下内容

注意这里Student类和Teacher的类表里的第二个虚函数地址是一样的,因为B类没有重写第二个虚函数,因此继承下来了。

为什么第一个虚函数不一样呢?

因为子类重写后覆盖掉了(这也是为什么重写被称作覆盖的由来)

2.2引用和指针如何实现多态 

可以分析,为什么多态可以实现指向父类调用父类函数 ,指向子类调用子类函数?

传递父类,通过vftptr找到虚函数表的地址,再去找到虚函数的地址,有了虚函数的地址,便可以去call这个虚函数

传递子类,首先会进行切割

将子类的内容切割掉,父类再去接受这个数据了,一样会有vftptr(是子类的vftptr),再去找到虚函数的地址,有了虚函数的地址,便可以去call这个虚函数。

这样就完成了多态。

附加一句,对于下面指出的代码(他其实并不清楚自己所存放的虚函数表指针是父类的还是子类的,他只是蠢蠢的去调用这个虚函数而已)

2.3普通类接收为什么实现不了多态

依然是之前的代码,参数部分不再是指针和引用,而是用普通类,我们发现这里没有实现多态。

我们将代码做一个小改动,方便观看区别 

 给Person类添加上一个构造函数

class Person {
public:
	Person(int x = 0)
		:a(x)
	{}
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	virtual void fun(){}
private:
	int a;
};

 我们给Person类构建出的p对象传10,他会调用构造函数将10赋值给成员a。

当执行Func(p)函数时,注意观察此时的a的值为10,虚函数表地址为0x004f9bfc

当执行Func(s)函数时,注意观察此时的a的值为0,虚函数表地址也为0x004f9bfc。

从上面的分析可以看出,Func(s)传递时,切割出子类中父类的那一份,成员会拷贝给父类,但是没有拷贝虚函数表指针

为什么只拷贝成员,不拷贝虚函数表指针呢?C++祖师爷为何这么设计?

我们可以用反证法

假设 拷贝构造赋值重载 会拷贝虚函数表指针

那么我们写出如下代码,运行后输出结果就应该为 两个 买票-半价  了(因为不管指向的累人,只管你所存储的数据)

这样就不能保证多态调用时,指向父类,父类调用的是父类的虚函数。因为还有可能经过一些操作,变成子类的虚函数

也许上面的问题并不那么致命,你说你自己控制好一点不就行了。

那么析构呢?要知道虚函数表中还可能有析构函数,如果我写出如下代码,阁下又该如何应对?

	Person* p = new Person;
	Student s;
	*p = s;
	delete p;

这个时候,你会发现Person父类的对象delete会去调用子类Student类的析构函数,这样会引发很多不可控制的事情。因此祖师爷帮我们处理了

这里会有点绕,不理解也没关系, 只要知道只有引用和指针才能触发多态就行!!!

最后再补充两点:

同类对象的虚表一样。

如果子类没有重写父类的虚函数,那么他们的虚函数表指针不同,但里面的内容相同

 3.虚函数表存放位置

 我们通过代码来打印各个区地地址,可以判断虚函数表存放位置

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

void func()
{}

int main()
{
	Base b1;
	Base b2;
	static int a = 0;
	int b = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	printf("静态区:%p\n", &a);
	printf("栈:%p\n", &b);
	printf("堆:%p\n", p1);
	printf("代码段:%p\n", p2);
	printf("虚表:%p\n", *((int*)&b1));
	printf("虚函数地址:%p\n", & Base::func1);
	printf("普通函数:%p\n", func);
}

注意打印虚表这里,vs x86环境下的虚表的地址是存放在类对象的头4个字节上。因此我们可以通过强转来取得这头四个字节

b1是类对象,取地址取出类对象的地址,强转为(int*)代表我们只取4个字节,再解引用,就可以取到第一个元素的地址,也就是虚函数表指针的地址

从图中可以发现代码段和虚表地址非常接近,存在代码段的常量区。

虚函数和普通函数地址非常接近,存在代码段。

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

1.单继承中的虚函数表

我们使用如下代码测试一下单继承的虚函数表。

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};
class X : Derive
{
public:
	virtual void fun3(){ cout << "X::func3" << endl; }
};

int main()
{
	Base b;
	Derive d;
	X x;
	return 0;
}

明明Base类和X类应该有4个虚函数,监视窗口发现表里面竟然只有2个,这真的很奇怪。

VS下的监视窗口不一定准确,我们用之前的办法打开内存来看看 

通过输入__vfptr的地址,我们成功找到了里面虚函数的地址。并且我们还发现似乎下面那两个地址跟上面两个非常接近,我们可以合理的设想,下面两个地址也是虚函数指针。

在vs环境下,虚函数表里面的虚函数以0结尾, 也很符合之前我们观察到的。

我们可以通过这一点,来打印虚表。

下面我们typedef了虚函数表指针  typedef void(*VFTPTR)(); 可以通过这个函数指针数组来打印里面的虚函数,这个打印函数终止条件就是 !=0 ,传递的参数内容跟前面我们分析的差不多,只是躲了一个强转,PrintVFPtr((VFTPTR*)*(int*)&b)  ; 因为后面的  *(int*)&b 虽然内容是地址,但是表现形式是一个整形,需要强为  (VFTPTR*) 

 

在*((int*)&d) 就会取到vTableAddress指向的地址,就得到虚函数的地址了。

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};
class X : Derive
{
public:
	virtual void func3(){ cout << "X::func3" << endl; }
};

typedef void(*VFTPTR)();

void PrintVFPtr(VFTPTR a[])
{
	for (size_t i = 0; a[i] != 0; i++)
	{
		printf("a[%d]:%p\n", i, a[i]);
	}
	cout << endl;
}

int main()
{
	Base b;
	Derive d;
	X x;
	PrintVFPtr((VFTPTR*)*(int*)&b);
	PrintVFPtr((VFTPTR*)*(int*)&d);
	PrintVFPtr((VFTPTR*)*(int*)&x);
	return 0;
}

我们运行一下如上代码,便可以打印出虚函数表里面的内容 

但是目前我们还是可以质疑这个地址到底是不是虚函数地址,我们可以打印虚函数的内容看一下。下面给打印代码略作修改,因为a[i]里面存放的就是函数指针,因此我们可以选择直接调用。 

void PrintVFPtr(VFTPTR a[])
{
	for (size_t i = 0; a[i] != 0; i++)
	{
		printf("a[%d]:%p->", i, a[i]);
		VFTPTR p = a[i];
		p();
	}
	cout << endl;
}

这下真的可以看到结果了可以确认虚函数都会放到虚表里面。并且监视窗口可能是个大骗子,要小心他!!!

2.多继承中的虚函数表

这里选择多继承,如图

这里我们代码

Base1有虚函数func1和func2,

Base2也有虚函数func1和func2。

derive继承了Base1和Base2,并重写了虚函数func1,还有虚函数func3

typedef void(*VFTPTR)();

void PrintVFPtr(VFTPTR a[])
{
	for (size_t i = 0; a[i] != 0; i++)
	{
		printf("a[%d]:%p->", i, a[i]);
		VFTPTR p = a[i];
		p();
	}
	cout << endl;
}
class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};
class Derive : public Base1, public Base2 {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};
int main()
{
	Derive d;
	PrintVFPtr((VFTPTR*)(*(int*)&d));
	return 0;
}

使用监视窗口观察到Dervie类对象d有两个虚函数表,那么问题就来了,他自身的虚函数func3会放在哪一张表里面呢? 

我们还是选择打印来观看,发现这里只能打印出第一张虚表的内容,并且func3在第一张虚表里,第二张虚表有没有func3呢?好像也需要打印出来观看,而好像我们对于第二张虚表不好打印,我们有没有什么方法可以打印第二张虚表里面的内容呢?

答案是有的,有很多种方法,这里我们介绍两种

第一种:将&d强转为Base1*,这样+1就会跳过整个Base1,就刚好到达了Base2类的开始,再进行之前的强转便可以打印了。

第二种方法:直接将&d赋值给Base2* ptr;这样Base2会进行切片操作,于是ptr就直接指向了Base2的虚函数表,依然就行之前的强转操作便可以打印了。

主函数代码如下

int main()
{
	Derive d;
	PrintVFPtr((VFTPTR*)(*(int*)&d));
	//写法1
	//PrintVFPtr((VFTPTR*)(*(int*)((Base1*)&d +1)));
	//写法2
	Base2* ptr = &d;
	PrintVFPtr((VFTPTR*)(*(int*)ptr));
	return 0;
}

 我们打印出来看一看这两个表是什么情况

这里可以得出结论了,Derive类对象的虚函数会放在多继承中继承的第一个类的虚函数表里(即Base1类虚函数表) 

问题又来了,为什么多继承要搞多个虚表呢?

还是之前的继承关系,请看如下图和代码,如果不搞多个虚表,那么p1去调用func1(),p2也去调用func1(),如若d没有重写func1(),那么这个多态就会紊乱,调用的都是那一个func1()了,而不是p1调用Base1的func1(),p2调用Base2的func1()了。因此我们多继承就搞多个虚表才不会出现紊乱的问题

int main()
{
	Derive d;
	Base1* p1 = &d;
    PrintVFPtr((VFTPTR*)*(int*)p1);
	p1->func1();
	Base2* p2 = &d;
    PrintVFPtr((VFTPTR*)*(int*)p1);
	p2->func1();
	return 0;
}

运行一下代码 

 调用的func1()函数确实没问题,实现了多态,但是我们发现两张虚表里func1()函数的地址竟然不同,这是为什么,我们重写的func1()两个都实现了啊,调用的也肯定是同一个函数,按道理来说应该地址是一样的,为什么地址不一样呢?我们尝试用反汇编来看一下

p1调用func1的反汇编  call了eax,走到了eax里面的jmp指令,再走一步,就到了func1()函数

p2调用反汇编,首先也是call了eax,再jmp,但是这个jmp竟然没有走到func1()函数,而是先执行了 sub   ecx,8 指令,后面再jmp了两下,才走到了func1()函数。why???

 sub   ecx,8 指令代表了什么?为什么调用的同一个函数,汇编代码却不相同?

我们尝试破解一下这个指令到底是为什么,首先Derive类重写了func1()函数,既然是Derive类的func1()函数,那么他所存放的*this指针类型肯定也是Derive。那么在这个函数里,我是不是可以调用父类的非私有成员或者函数?

 

那如果我执行Base2* p2 = &d;   这就会对d对象进行切片,p2对象按道理来说只能看到Base2的虚函数表和自己存放的数据b2,但是又有可能在func1()函数访问Base1或者Base2的非私有成员和函数,这样好像就不太好调用了,如果我将p2的地址放在Derive类对象地址的地方,那是不是就会很方便访问了。

sub   ecx,8  此时,我们在俩看这句指令,从下面图片可以看出来exc存放的是p2的地址,sub 是减去的意思, 这局指令是 exc - 8,也就是p2的地址-8,那么我现在的地址是什么???没错啦,现在的地址就回到的&d的地址,这样一来是不是就可以调用整个Derive类还有继承下来的数据啦!!!!

这局代码的本质就是修正this指针,指向derive对象

那么为何 Base1* p1 = &d;  p1却没有减去值,回到&d的地方了呢?笨蛋,因为p1的地址就是d的地址,Derive首先继承的就是Base1,而d的首元素就是Base1的虚函数表指针啊。因此我自己就在这里,我还减去什么,我传过去直接开用就完事了!!!!

我们成功的管中窥豹,明白了多继承虚函数表的情况,还有为什么重写多个父类的同名虚函数,地址为何不一样了(如果地址一样,就不可能一起回到Derive类对象的地址,因为类里的元素总会有先后顺序)!!

3.菱形继承和菱形虚拟继承

我的建议是别碰,饶了我吧,投降了。

做一道题吧

 

 这道题选B,很难相信

首先,B类型的对象p去调用test(); test()是B类继承下来的,但是里面默认存放的this指针依然是A*,将一个B类型的指针传给A类型的指针,会发生多态,B类里面的func()是重写了A类的func()  (A类func()为虚函数,B类重写了可以不写virtual)。

注意重写的关键点,仅仅是重写了A类的实现,而前面的那些声明,依然是调用的A类的声明,因此给到的val默认值是1,调用了B类的函数实现!!! 所以输出B->1

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

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

相关文章

在本地模拟C/S,Socket套接字的使用

public class SocketTCP01Server {public static void main(String[] args) throws IOException {/**1.在本机的 9999 端口监听 &#xff0c;等待连接细节&#xff1a; 要求在本机没有其他服务在监听999细节&#xff1a;这个ServerSocket 可以通过accept()返回多个Socket[多个客…

指针仪表读数YOLOV8NANO

指针仪表读数YOLOV8 NANO 采用YOLOV8 NANO训练&#xff0c;标记&#xff0c;然后判断角度&#xff0c;得出角度&#xff0c;可以通过角度&#xff0c;换算成数据

End-to-End Adversarial-Attention Network for Multi-Modal Clustering

方法 融合表征h f _f f​ ∑ v \sum_v ∑v​w v _v v​ h v h^v hv 辅助信息 作者未提供代码

2558. 从数量最多的堆取走礼物

2558. 从数量最多的堆取走礼物 难度: 简单 来源: 每日一题 2023.10.28 给你一个整数数组 gifts &#xff0c;表示各堆礼物的数量。每一秒&#xff0c;你需要执行以下操作&#xff1a; 选择礼物数量最多的那一堆。如果不止一堆都符合礼物数量最多&#xff0c;从中选择任一…

Java工具库——Commons IO的50个常用方法

工具库介绍 Commons IO&#xff08;Apache Commons IO&#xff09;是一个广泛用于 Java 开发的开源工具库&#xff0c;由Apache软件基金会维护和支持。这个库旨在简化文件和流操作&#xff0c;提供了各种实用工具类和方法&#xff0c;以便更轻松地进行输入输出操作。以下是 Com…

基于51单片机的温度测量报警系统的设计与制作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、实习目的二、实习任务2.1 设计温度测量报警系统硬件电路2.2 温度测量报警系统软件编程、仿真与调试&#xff1b;2.3 完成温度测量报警系统的实物制作与调试…

【蓝桥每日一题]-前缀和与差分(保姆级教程 篇2)#差分序列

昨天讲的概念和模板&#xff0c;今天讲一个差分序列的好题(好好体会里面的优化思想)&#xff1a; 目录 题目&#xff1a; 思路&#xff1a; 题目&#xff1a; 手动打出样例哈 输入&#xff1a; 输出&#xff1a; 4 …

Android问题笔记四十二:signal 11 (SIGSEGV), code 1 (SEGV_MAPERR) 的解决方法

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

etcd的mvcc源码剖析

mvcc简介 悲观锁 在对于一些临界资源进行读写的时候&#xff0c;为了防止其他人进行同步的修改数据&#xff0c;直接将当前的数据锁住&#xff0c;不让别人使用&#xff0c;来实现并发安全 乐观锁 在对临界资源进行操作的时候&#xff0c;不锁住数据&#xff0c;实现独占&…

锐捷NBR 1300G路由器 越权CLI命令执行漏洞

执行查看版本的命令不需要其它权限&#xff0c; 访问url&#xff1a; /WEB_VMS/LEVEL15/构造payload数据包&#xff1a; POST /WEB_VMS/LEVEL15/ HTTP/1.1commandshow version&strurlexec%04&mode%02PRIV_EXEC&signnameRed-Giant.回显如下&#xff1a; 测试发现…

软件测试之单元测试

前言 为什么我们需要测试&#xff1f; 让产品可以快速迭代&#xff0c;同时还能保持高质量 对于一些相对稳定的系统级别页面&#xff0c;自动化测试在提高测试的效率的方面起到非常重要的作用。前端的自动化测试主要包括&#xff1a;浏览器测试和单元测试。Vue官方脚手架自带…

Linux系统之watch命令的基本使用

Linux系统之watch命令的基本使用 一、watch命令介绍二、watch命令的使用帮助2.1 watch命令的help帮助2.2 watch命令的语法解释 三、watch命令的基本使用3.1 使用默认的2秒时间间隔执行ls命令3.2 每隔10秒执行一次ps命令3.3 每隔1秒输出一次磁盘使用情况3.4 高亮显示grep命令的输…

File 复制文件

一.代码 package org.example;import java.io.*;public class day05 {public static void main(String[] args) throws IOException {//复制图片//创建字节输入流管道与源文件接通final FileInputStream is new FileInputStream("D:\\temp\\day05\\图2.jpg");//创建…

软考高项-计算题(2)

题4 项目的总预算是包含管理储备的&#xff0c;所以总预算应该是&#xff1a;13238102*360 ETC(BAC-EV)/CPI BAC60 EV60*0.318 CPI18/200.9 ETC42/0.9 答案选择C A 题5 因为题目中提到了“按目前的状况继续发展”&#xff0c;那么是&#xff1a;ETC(BAC-EV)/CPI EV1230*0…

计算机网络基础二

课程目标 了解 OSI 七层模型分层结构 了解 TCP/IP 协议簇四层模型分层结构 能够说出 TCP/IP 协议簇中 运输层、网络层和数据链路 层常见的 相关协议 能够说出 TCP/IP 的三次握手四次断开过程 了解 Vmware 的三种网络模式 能够使用客户端工具连接虚拟机 掌握主机名、 DNS…

java原子类-Atomic

什么是原子类&#xff1f; java 1.5引进原子类&#xff0c;具体在java.util.concurrent.atomic包下&#xff0c;atomic包里面一共提供了13个类&#xff0c;分为4种类型&#xff0c;分别是&#xff1a; 原子更新基本类型&#xff0c;原子更新数组&#xff0c;原子更新引用&…

Redis(06)| 数据结构-整数集合

整数集合是 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素&#xff0c;并且元素数量不大时&#xff0c;就会使用整数集这个数据结构作为底层实现。 整数集合结构设计 整数集合本质上是一块连续内存空间&#xff0c;它的结构定义如下&#xff1a; typedef struct…

【黑马程序员】Springboot2 学习笔记

课程地址 1. Springboot parent和starter区别 parent&#xff1a;开发Springboot项目需要继承spring-boot-starter-parent&#xff0c;其中定义了若干个依赖管理&#xff08;坐标版本号&#xff09;&#xff0c;避免依赖版本冲突&#xff1b;starter&#xff1a;开发Springboo…

提升演讲口才,助青少年踏上成功之路

提升演讲口才&#xff0c;助青少年踏上成功之路 引言&#xff1a; 青少年时期是一个人成长发展的关键阶段&#xff0c;而演讲口才的培养不仅可以帮助他们在学业和职业上取得成功&#xff0c;还能帮助他们塑造自信、提升沟通能力&#xff0c;并在社交场合中脱颖而出。本文将探讨…

springboot在线招聘系统

springboot在线招聘管理系统&#xff0c;java在线招聘管理系统&#xff0c;在线招聘管理系统 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;8.x版本都可&#xff09; 硬件环境&#xf…