C++:组合、继承与多态

news2024/11/16 23:51:49

面向对象设计的重要目的之一就是代码重用,这也是C++的重要特性之一。代码重用鼓励人们使用已有的,得到认可并经过测试的高质量代码。多态允许以常规方式书写代码来访问多种现有的且已专门化了的相关类。继承和多态是面向对象程序设计方法的两个最主要的特性。继承可以将一群相关的类组织起来,并共享它们之间的相同数据和操作行为;多态使程序员在这些类上编程时,就像在操作一个单一体,而非相互独立的类,并且可以有更多灵活性来加入和删除类的一些属性或方法。
在C++中可以用类的方法解决代码重用,通过创建新类重用代码,而不是从头创建,这样可以使用其他人已经创建并调试过的类,其关键是使用类而不是更改已存在的代码。下面将介绍两个方法:第一种方法是简单的创建一个包含已存在的类对象的新类称为组合,因为这个新类是由于存在类的对象组合的;第二种方法是创建一个新类作为一个已存在类的类型,采用这个已存在类的形式,只对它增加代码,但不修改,这种方法称为继承,其中大量的工作有编译器完成。继承是面向对象程序设计的核心。

组合

对于比较简单的类,其数据成员对位基本数据类型,但对于某些复杂的类来说,其某些数据成员可能又是另一些类的类型,这就形成了类的组合(聚集)。
例如:一类方式板寸某个班级的名称、人数以及每个学生的学号、姓名、学习成绩(省略类Student

//MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H_
#include "student.h"
class MyClass
{
	enum
	{
		NUM = 50
	};
	char Name[20];
	int Num;
	Student stuList[NUM];
public:
	MyClass();
	const char* GetClassName();
	const char* GetStuName(int No);
};

#endif // !MYCLASS_H

//MyClass.cpp
#include "MyClass.h"
#include <cstddef>
MyClass::MyClass()
{
	Num = 0;
}

inline const char* MyClass::GetClassName()
{
	return Name;
}

const char* MyClass::GetStuName(int No)
{
	for (int i = 0; i < NUM; i++)
	{
		if (stuList[i].GetNo() == No)
			return stuList[i].GetName();
	}
	return NULL;
}

若嵌入的对象是公有的,也可以用“多级”访问。

继承

类的继承就是新类从已有类中获得以有的属性和行为,或者说就是从基类派生出既有基类特征又有新特征的派生类。
创建新类,但不是从头创建,开源使用其他人已经创建并调试过的类。关键是使用类,而不是更改已存在的代码。
类继承格式为:class 子类名:[pubilc|private|protected]父类名{···}
子类(派生类)具有父类(基类)的所有属性和行为,且可以增加新的行为和属性。
C++提供了三种继承的方式:公有(public)、受保护(protected)和私有(private)。

  1. 公有继承
    a. 基类的private、public和protected成员的访问属性在派生类中保持不变。
    b.派生类中继承的成员函数可以直接访问基类中的所有成员,派生类中新增二点成员函数只能访问基类的public和protected成员,不能访问基类的private成员。
    c.通过派生类的对象只能访问基类的public成员。
  2. 受保护继承
    a.基类的public和protected成员都已protected身份出现在派生类中。
    b.派生类中新增的成员函数可以直接访问基类中的public和protected成员,但不能访问基类中private成员。
    c.通过派生类的对象不能访问基类中的任何成员。
  3. 私有继承
    a.基类的public和protected成员都已private身份出现在派生类中。
    b.派生类中新增的成员函数可以直接访问基类中的任何成员。
    c.通过派生类的对象不能访问基类中的任何成员。

继承与组合

在实际工作中往往一个新类中既有从已有类中继承的,也有由其他类组合的,这需要把组合和继承放在一起使用。
组合通常希望新类内部有已存在类性能时使用,而不希望已存在类作为其接口。也就是说,嵌入 一个计划用于实现新类性能的对象,而新类的用户看到的时新定义的接口,而不是来自父类的接口。
继承是取一个已存在的类,并制作它的一个专门的版本。通常,这意味着取一个一般目的的类,并为特殊的需要对它进行专门化。

继承与组合中的构造和析构

成员对象初始化

对于继承,应在冒号之后和这个类体的左花括号“{”之前放基类。而在过早函数的初始化表达式中,可以将对子对象构造函数的调用语句放在构造函数参数表和冒号之后,在函数体的左花括号“{”之前。
对于组合应给出对象的名字而不是类名。若在初始化表达式中有多于一个的构造函数调用,应当用逗号隔开,例如:

Student::Student(const char*Name = NULL,int Age = 0, char Sex = 'm', int No = 0):Person(CpName,Age,Sex),No(No),Ave(0){···}

需要注意的是,这里对基本数据类型的初始化工作成为“伪构造函数”,甚至可以应用到类外:int i(10);

构造和析构顺序

对于析构函数来说,执行次序与构造函数相反,系统调用构造函数生成对象,调用析构函数释放对象所占用的内存空间。当采用继承方式创建子类对象时,先从父类开始执行构造函数->父类的成员->执行子类的构造函数->子类成员;当撤销子类对象时,执行相反的顺序,即先撤销子类的成员->执行子类的析构函数->撤销父类成员->执行父类的析构函数。
例如:在采用继承方式生成的类中,构造函数与析构函数的调用顺序。

#include <iostream>
using namespace std;
class A
{
	int a;
public:
	A(int i = 0) :a(i)
	{
		cout << "A is constructed" << endl;
	}
	~A()
	{
		cout << "A is destructed" << endl;
	}
};

class B:public A
{
	int b;
public:
	B(int i = 0) :b(i)
	{
		cout << "B is constructed" << endl;
	}
	~B()
	{
		cout << "B is destructed" << endl;
	}
};

int main()
{
	B b;
	return 0;
}

程序运行结果:

A is constructed
B is constructed
B is destructed
A is destructed

相对于基类和派生类,若类中有静态数据成员,构造顺序又是怎样?仍然时“仿生”自然界的顺序,即先父类再子类,按照属性成员声明的先后顺序进行构造:

  1. 调用基类的构造函数;
  2. 根据类中声明的顺序构造函数组合对象;
  3. 派生类中构造函数的执行。
    派生类构造函数的格式为:
class 派生类:[public|private|protested]基类名
{
	public:
		派生类名(参数列表1):基类名(参数列表2),组合对象列表{···}
};

析构函数的顺序正好相反。
例如:组合类中构造函数与析构函数的调用顺序

#include <iostream>
using namespace std;
class X
{
public:
	X()
	{
		cout << "X is constructed" << endl;
	}
	~X()
	{
		cout << "X is destructed" << endl;
	}
};

class A
{
	int a;
	X x;//组合对象
public:
	A(int i = 0) :a(i)//基类构造函数
	{
		cout << "A is constructed" << endl;
	}
	~A()//基类析构函数
	{
		cout << "A is destructed" << endl;
	}
};

class Y
{
	int y;
public:
	Y(int i = 0)
	{
		y = i;
		cout << "Y is constructed" << endl;
	}
	~Y()
	{
		cout << "Y is destructed" << endl;
	}
};

class Z
{
	int z;
public:
	Z(int i = 0)
	{
		z = i;
		cout << "Z is constructed" << endl;
	}
	~Z()
	{
		cout << "Z is destructed" << endl;
	}
};

class B:public A
{
	int b;
	Y y;//派生类组合对象
	Z z;
public:
	B(int i = 0) :A(1), b(i), z(i), y(i)//派生类构造函数的后面为内嵌对象列表
	{
		cout << "B is constructed" << endl;
	}
	~B()//派生析构函数
	{
		cout << "B is destructed" << endl;
	}
};

int main()
{
	B b;
	return 0;
}

程序运行结果为:

X is constructed
A is constructed
Y is constructed
Z is constructed
B is constructed
B is destructed
Z is destructed
Y is destructed
A is destructed
X is destructed

注意:

  1. 派生类不能继承基类的构造函数和析构函数,当基类有带参数的构造函数时,则派生类必须定义构造函数,以便把参数传递给基类构造函数
  2. 当派生类也作为基类使用时,则各派生类子负责其直接的基类构造。
  3. 因为析构函数不带参数,所以派生类中析构函数的存在不依赖于基类,基类中析构函数的存在也不依赖于派生类。
    例如:继承类中构造函数与析构函数的调用顺序。
#include <iostream>
using namespace std;
class A
{
	int a;
public:
	A(int i = 0) :a(i)
	{
		cout << "A is constructed" << endl;
	}
	~A()
	{
		cout << "A is destructed" << endl;
	}
};

class Y
{
	int y;
public:
	Y(int i = 0)
	{
		y = i;
		cout << "Y is constructed" << endl;
	}
	~Y()
	{
		cout << "Y is destructed" << endl;
	}
};

class B:public A
{
	int b;
	Y y;
public:
	B(int i = 0) :b(i), y(i)
	{
		cout << "B is constructed" << endl;
	}
	~B()
	{
		cout << "B is destructed" << endl;
	}
};

class C:public B
{
	int c;
public:
	C(int i = 0) :B(1), c(i)
	{
		cout << "C is constructed" << endl;
	}
	~C()
	{
		cout << "C is destructed" << endl;
	}
};

int main()
{
	C c(2);
	return 0;
}

程序运行结果为:

A is constructed
Y is constructed
B is constructed
C is constructed
C is destructed
B is destructed
Y is destructed
A is destructed

名字覆盖

若再基类中有一个函数名被重载多次,在派生类中又重定义了这个函数名,则在派生类中会掩盖这个函数的所有基类定义。也就是说,通过派生类来访问该函数时,由于采用就近匹配的原则,只会调用在派生类中所定义的该函数,基类中所定义的函数都变得不再可用。
若要访问基类中声明的函数,则有以下方法:

  1. 使用作用域标识符限定。
  2. 避免名称覆盖。

虚函数

多态是面向对象程序设计的重要特性,重载和虚函数是体现多态的两个重要手段。虚函数体现了多态的灵活性,可进一步减少冗余信息,显著提高软件的可扩充性。
学习函数重载与继承的方法后,经常会遇见到下面问题,在派生类中存在对基类函数的重载,当通过派生类对象调用重载函数时却调用了基类中的原函数。
例如:通过派生类对象简介调用重载函数

#include <iostream>
using namespace std;
class A
{
public:
	void play() const
	{
		cout << "A::play" << endl;
	}
};

class B:public A
{
public:
	void play() const
	{
		cout << "B::play" << endl;
	}
};

void tune(A& i)
{
	i.play();
}

int main()
{
	B b;
	tune(b);
	return 0;
}

程序运行结果为:

A::play

可以看出,输出结果并不是我们想要的B::play,而是A::play。显然,这不是所希望的输出结果,因为这个对象实际上就是B类型,而不只是A类型。C++类型虽然语法检验很严格,但是函数tune(通过引用)接受一个A类型的对象,也不拒绝任何从A派生的类对象。为了理解这个问题引入下面的概念。

虚函数的定义

虚函数定义格式为:

class 基类名
{
	virtual 返回值类型 将要在派生类中重载的函数名(参数列表);
};

例如,用虚函数修改上例

#include <iostream>
using namespace std;
class A
{
public:
	virtual void play() const
	{
		cout << "A::play" << endl;
	}
};

class B:public A
{
public:
	void play() const
	{
		cout << "B::play" << endl;
	}
};

class C:public B
{
public:
	void play() const
	{
		cout << "C::play" << endl;
	}
};

void tune(A& i)
{
	i.play();
}

int main()
{
	B b;
	tune(b);
	C c;
	tune(c);
	A* p = &b;
	tune(*p);
	p = &c;
	tune(*p);
	A a;
	tune(a);
	return 0;
}

程序运行结果为:

B::play
C::play
B::play
C::play
A::play

不管有多少层,虚函数都能得到很好的应用。因为一旦在基类中声明为虚函数,就可根据类对象的类型动态决定调用基类或派生类的函数。
使用虚函数需要注意以下5点:

  1. 在基类中声明虚函数,即需要在派生类中重载的函数,必须在基类中声明。
  2. 虚函数一经声明,在派生类中重载的基类的函数即是虚函数,不在需要加virtual
  3. 只有非静态成员函数可以声明为虚函数,静态成员函数和全局函数不能声明为虚函数。
  4. 编译器把名称相同、参数不同的函数看作不同的函数。基类和派生类中名字相同但参数不同的函数,不需要声明为虚函数。
  5. 普通对象调用虚函数时,系统仍然以静态绑定方式调用函数。因为编译器编译时能确切地知道对象的类型,且能确切地调用其成员函数。

虚析构函数

通过了解虚函数可以掌握虚函数在继承和派生中的调用方式。那么类的两种特殊的函数——构造函数和析构函数是否可以声明为虚函数呢?

  1. 构造函数不能声明为虚函数。因为构造函数有其特殊的工作,它处在对象创建初期,先要调用基类构造函数,然后调用按照继承顺序派生的派生类的构造函数。
  2. 析构函数能够且经常是虚函数。系后汉书调用顺序与构造函数完全相反,从最晚派生类开始,以此向上到基类。因此,析构函数确切地知道它是从按个类派生而来的。
    虚析构函数声明格式为:virtual~析构函数名称();

虚函数的目的是让派生类编制自己的行为,所以应该在基类中声明虚析构函数。当类中存在虚函数时,也应该使用虚析构函数,这样保证类对象销毁时能得到“完全”的空间释放。
若某个类不包含虚函数时,一般表示它将不作为一个基类来使用,建议不要将析构函数声明为虚函数,以保证程序执行的高效性。

纯虚函数和抽象基类

在实际工作中往往需要定义这样一个类,对这个类中的处理函数只需要说明函数的名称、参数列表、以及返回值的类型,只提供一个接口以及说明和规范其他程序对此服务的调用,至于这个函数如何实现,根据具体需要在派生类中定义。通常把这样的类称为抽象基类,而把这样的函数成为纯虚函数。
纯虚函数定义格式为:virtual 返回值类型 函数名称(参数列表)=0;
当一个类中存在纯虚函数时,这个类就是抽象类。抽象类的主要作用是,为一个类建立一个公共的接口,使它们能够更有效地发挥多态特性。使用抽象类需要注意:

  1. 抽象类只能用于其他类的基类,不能建立抽象类对象。抽象类处于继承层次结构的较上层,抽象类自身无法实例化,只能通过继承机制生成抽象类的非抽象派生类,然后再实例化。
  2. 抽象类不能用于参数类型、函数返回值或显示转换的类型。
  3. 可以声明一个抽象类的指针和引用。通过指针或引用可以指向并访问派生类对象,以访问派生类的成员。
  4. 抽象类派生出新的类之后,若派生类给出所有纯虚函数的函数实现,这个派生类就可以声明自己的对象,因而其不再是抽象类;反之,若派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类

纯虚函数非常有用,因为它使类有明显的抽象性,并告诉用户和编译器希望如何使用。再基类中,对纯虚函数提供定义是可能的,告诉编译器不允许纯抽象基类声明对象,而且纯虚函数再派生类中必须定义,以便创建对象。然而,若希望一段代码对于一些或所有派生类定义能共同使用,而不希望在每个函数中重复这段代码,具体实现方法如下:
例如:虚函数与纯虚函数的使用。

#include <iostream>
using namespace std;
class A
{
public:
	virtual void play() const = 0;
	virtual void show()const = 0
	{
		cout << "A:show()" << endl;
	}
};

class B :public A
{
public:
	void play() const
	{
		cout << "B::play" << endl;
	}
	void show() const
	{
		A::show();
	}
};

class C :public B
{
public:
	void play() const
	{
		cout << "C::play" << endl;
	}
	void show() const
	{
		A::show();
	}
};

void tune(A& i)
{
	i.play();
}

int main()
{
	B b;
	tune(b);
	b.show();
	C c;
	tune(c);
	c.show();
	return 0;
}

程序运行结果为:

B::play
A:show()
C::play
A:show()

多重继承

在派生类中声明,基类名既可以有一个,也可以有多个。若只有一个基类名,则这种继承方式称为单继承;若基类名有多个,则这种继承方式称为多继承,这时派生类就同时得到多个已有类的特征。如图:
在这里插入图片描述

多继承语法

多继承允许派生类有两个或多个基类的能力,就是想使多个类以这种方式组合起来,使派生类对象的行为具有多个基类对象的特征。在多继承中各个基类名之间用逗号隔开。
多继承的声明格式为:class 派生类名:[继承方式]基类名1,[继承方式]基类名2,···,[继承方式]基类名n{···};
例如:多继承的使用

#include <iostream>
using namespace std;
class A
{
	int a;
public:
	void SetA(int i)
	{
		a = i;
	}
};

class B
{
	int b;
public:
	void SetB(int i)
	{
		b = i;
	}
};

class C:public A,private B
{
	int c;
public:
	void SetC(int, int, int);
};
//派生类成员函数直接访问基类的公有成员
void C::SetC(int x, int y, int z)
{
	SetA(x);
	SetB(y);
	c = z;
}

int main()
{
	C obj;
	obj.SetA(5);
	obj.SetC(6, 7, 9);
	obj.SetB(6);//错误,不能访问私有继承的基类成员
	return 0;
}

虚基类

在多继承中,经常会遇到这样的情况,若两个及其以上的基类中有相同的成员函数,那么它们的派生类声明的对象将调用哪个基类的函数呢?
解决的方法是使用域名进行控制,但是增加作用域分辨符虽然可以消除二义性,但显然降低了程序的可读性。同时,多继承里还有一种极端的情况,即由相同基类带来的二义性,该继承方式称为“菱形”方式,它使子类对象重叠,增加了额外的空间开销。为此,C++引入了虚基类。
二义性问题需要在基类中重新定义函数,额外的空间开销问题可采用虚基类的方式。
把一个基类定义为虚基类,必须在派生子类时在父类名字前加关键字virtual.
定义格式为:class 派生类名:virtual 访问权限修饰符 父类名{};
例如:虚基类使用方法举例。

#include <iostream>
using namespace std;
class base
{
public:
	virtual const char* show() = 0;
};

class d1 :virtual public base
{
public:
	const char* show()
	{
		return "d1";
	}
};

class d2 :virtual public base
{
public:
	const char* show()
	{
		return "d2";
	}
};

class m :public d1, public d2
{
public:
	const char* show()
	{
		return d2::show();//为消除二义性,使用作用域运算符
	}
};

int main()
{
	m m1;
	m1.show();
	return 0;
}

这样不仅消除了二义性,而且类m1中只有一个基类base,也节省了空间。

最终派生类

在上述例中,各类没有构造函数,使用的是默认构造函数。若类里有了带有参数的构造函数,情形将有所不同。
在派生类声明对象时,编译器报错,表示没有合适的构造函数调用。即使在基类的派生类d1,d2中也会增加对基类base的构造,情况也是如此。为了解决此类问题,可引入最终派生类(most derived class)的概念。
最终派生类(最晚辈派生类)指当前所在的累。在基类base的构造函数,base就是最终派生类;在基类d1的构造函数,d1就是最终派生类;在基类m的构造函数,m就是最终派生类。
当使用虚基类时,尤其是带有参数的构造函数的虚基类时,最终派生类的构造函数必须对虚基类初始化。不管派生类离虚基类有多远,都必须对虚基类进行初始化。
例如:含虚基类构造函数的使用方法。

#include <iostream>
using namespace std;
class base
{
	int i;
public:
	base(int x):i(x){}
	int geti()
	{
		return i;
	}
	virtual const char* show()
	{
		return "base";
	}
};

class d1 :virtual public base
{
	int id1;
public:
	d1(int x = 1) :base(0), id1(x) {}
	const char* show()
	{
		return "d1";
	}
};

class d2 :virtual public base
{
	int id2;
public:
	d2(int x = 2) :base(1), id2(x) {}
	const char* show()
	{
		return "d2";
	}
};

class m :public d1, public d2
{
	int im;
public:
	m(int x = 0) :base(3), im(x) {}
	const char* show()
	{
		return d2::show();
	}
};

int main()
{
	m m1;
	cout << m1.show() << endl;
	cout << m1.geti() << endl;
	d1 d;
	cout << d.geti() << endl;
	cout << d.show() << endl;
	return 0;
}

程序运行结果是:

d2
3
0
d1

使用虚基类时要注意:

  1. 必须在派生类的构造函数中调用初始化虚函数的构造函数;
  2. 给虚基类安排默认构造函数,可使虚基类的程序开发变得简单易行。

多继承的构造顺序

例如:修改上例。

#include <iostream>
using namespace std;
class base
{
	int i;
public:
	base(int x):i(x)
	{
		cout << "base is constructed" << endl;
	}
	virtual ~base()
	{
		cout << "base is destructed" << endl;
	}
	virtual const char* show() = 0;
};

class d1 :virtual public base
{
	int id1;
public:
	d1(int x = 1) :base(0), id1(x) 
	{
		cout << "d1 is constructed" << endl;
	}
	virtual ~d1()
	{
		cout << "d1 is destructed" << endl;
	}
	const char* show()
	{
		return "d1";
	}
};

class d2 :virtual public base
{
	int id2;
public:
	d2(int x = 2) :base(1), id2(x) 
	{
		cout << "d2 is constructed" << endl;
	}
	virtual ~d2()
	{
		cout << "d2 is destructed" << endl;
	}
	const char* show()
	{
		return "d2";
	}
};

class m :public d1, public d2
{
	int im;
public:
	m(int x = 0) :base(3), im(x) 
	{
		cout << "m is constructed" << endl;
	}
	~m()
	{
		cout << "m is destructed" << endl;
	}
	const char* show()
	{
		return d2::show();
	}
};

int main()
{
	m m1(5);
	return 0;
}

程序运行结果是:

base is constructed
d1 is constructed
d2 is constructed
m is constructed
m is destructed
d2 is destructed
d1 is destructed
base is destructed

多继承构造顺序与单继承构造顺序类似,从基类开始,沿着派生顺序逐层向下,当同一层次派生同一个类时,按照声明继承的顺序自左向右。析构顺序与构造顺序相反。

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

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

相关文章

fix bug: FileNotFoundError: [Errno 2] No such file or directory: ‘nvcc‘

1.问题描述 运行的代码设计pycuda,会调用nvcc&#xff0c;确定已经安装cuda toolkit&#xff0c;在terminal中云运行 nvcc -V 能得到想到的结果&#xff1a; 但是在 pycharm中运行代码时提示&#xff1a; FileNotFoundError: [Errno 2] No such file or directory: nvcc 2.…

细数语音识别中的几个former

随着Transformer在人工智能领域掀起了一轮技术革命&#xff0c;越来越多的领域开始使用基于Transformer的网络结构。目前在语音识别领域中&#xff0c;Tranformer已经取代了传统ASR建模方式。近几年关于ASR的研究工作很多都是基于Transformer的改进&#xff0c;本文将介绍其中应…

拦截器的简单使用

拦截器的简单使用 拦截器的使用创建拦截器preHandle 目标方法执行前执行postHandle 目标方法执行后执行afterCompletion 视图渲染后执行 拦截器使用场景返回值注册拦截器运用拦截器 拦截器的使用 创建拦截器 首先,我们需要创建一个拦截器器的类,并且需要继承自HandlerIntercep…

java分布式锁(详解)

本地锁 浏览器把100w请求由网关随机往下传&#xff0c;在集群情况下&#xff0c;每台服务都放行10w请求过来&#xff0c;这时候每台服务都用的是本地锁是跨JVM的&#xff0c; 列如这些服务都没有49企业&#xff0c;此时有几个服务进行回原了打击在DB上面&#xff0c;那后期把这…

一种基于YOLOV5框架的新型轻量级雾天行人车辆实时检测算法(XM-YOLOViT)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文摘要&#xff1a;提出了一种基于YOLOV5框架的新型XM-YOLOViT雾天行人车辆实时检测算法&#xff0c;有效解决了密集目标干扰和雾霾遮挡问题&#xff0c;提高了复杂雾天环境下的检测效果。 1.XM-YOLOViT原理介绍 XM-YOLOViT框架图 摘…

04.领域驱动设计:了解聚合和聚合根,怎样设计聚合-学习总结

目录 1、概述 2、聚合 3、聚合根 4、怎么设计聚合 4.1 聚合的构建过程主要步骤 第 1 步&#xff1a;采用事件风暴。 第 2 步&#xff1a;选出聚合根。 第 3 步&#xff1a;找出与聚合根关联的所有紧密依赖的实体和值对象。 第 4 步&#xff1a;画出对象的引用和依赖模型…

数据结构·双向链表

1. 双向链表的结构 我们之前提到过&#xff0c;双向链表的全称是&#xff1a;带头双向循环链表。带头就是相当于一个“哨兵位”&#xff0c;用来标记链表的开始&#xff0c;它存储的数据是无效的&#xff0c;但是它将存储有效的前驱节点和后继节点的地址&#xff0c;带头链表的…

【Linux】进程通信——管道

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f4cb;进程通信的目的&#x1f4cb;管道匿名管道pipe函数创…

【DeepLearning-9】YOLOv5模型网络结构中加入MobileViT模块

一、神经网络的前中后期 在神经网络中&#xff0c;特别是在深度卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;“网络早期&#xff08;低层&#xff09;”、“网络中期&#xff08;中层&#xff09;”和“网络后期&#xff08;高层&#xff09;”通常指的是网络结构中…

sqli-labs靶场(1-6关)

1、第一关 测试id1 id1加一个引号报错&#xff0c;两个引号正常&#xff0c;应该是字符&#xff0c;还有回显 猜测字段长度 id1 order by 3 -- id1 order by 4 -- 字段长度为三&#xff0c;接下来确定位置&#xff1a;id1 and 12 union select 1,2,3 -- 查出库名,及版本号id1 …

用C语言实现贪吃蛇游戏!!!(破万字)

前言 大家好呀&#xff0c;我是Humble&#xff0c;不知不觉在CSND分享自己学过的C语言知识已经有三个多月了&#xff0c;从开始的C语言常见语法概念说到C语言的数据结构今天用C语言实现贪吃蛇已经有30余篇博客的内容&#xff0c;也希望这些内容可以帮助到各位正在阅读的小伙伴…

深度学习(6)--Keras项目详解

目录 一.项目介绍 二.项目流程详解 2.1.导入所需要的工具包 2.2.输入参数 2.3.获取图像路径并遍历读取数据 2.4.数据集的切分和标签转换 2.5.网络模型构建 2.6.绘制结果曲线并将结果保存到本地 三.完整代码 四.首次运行结果 五.学习率对结果的影响 六.Dropout操作…

N-141基于springboot,vue网上拍卖平台

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatis-plusredi…

一张图区分Spring Task的3种模式

是的&#xff0c;只有一张图&#xff1a; fixedDelay 模式cron 模式fixedRate 模式

2024/1/28周报

文章目录 摘要Abstract文献阅读题目引言方法The ARIMA modelTime delay neural network (TDNN) modelLSTM and DLSTM model 评估准则实验数据描述实验结果 深度学习AttentionAttention思想公式步骤 Attention代码实现注意力机制seq2seq解码器Model验证 总结 摘要 本周阅读了一…

腾讯云幻兽帕鲁4核16G/8核32G/16核64G服务器配置价格表

腾讯云幻兽帕鲁服务器4核16G、8核32G和16核64G配置可选&#xff0c;4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64G35M配置580元年1个月、1740元3个月、6960元一年&#xff0c;腾讯云百科txybk.com分享腾讯云幻兽帕鲁…

uniapp组件库fullScreen 压窗屏的适用方法

目录 #平台差异说明 #基本使用 #触发压窗屏 #定义压窗屏内容 #注意事项 所谓压窗屏&#xff0c;是指遮罩能盖住原生导航栏和底部tabbar栏的弹窗&#xff0c;一般用于在APP端弹出升级应用弹框&#xff0c;或者其他需要增强型弹窗的场景。 警告 由于uni-app的Bug&#xff0…

深度强化学习(王树森)笔记04

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

探索IOC和DI:解密Spring框架中的依赖注入魔法

IOC与DI的详细解析 IOC详解1 bean的声明2 组件扫描 DI详解 IOC详解 1 bean的声明 IOC控制反转&#xff0c;就是将对象的控制权交给Spring的IOC容器&#xff0c;由IOC容器创建及管理对象。IOC容器创建的对象称为bean对象。 要把某个对象交给IOC容器管理&#xff0c;需要在类上…

基于springboot+vue的在线教育系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目背景…