C++面向对象特性——多态

news2025/1/15 16:42:27

C++面向对象之多态

    • 什么是多态?
    • 为什么使用多态?
    • 虚函数的定义
    • 虚函数的实现机制
    • 哪些函数不能被设置为虚函数?
    • 虚函数的访问
      • 指针访问
      • 引用访问
      • 对象访问
      • 成员函数中的访问
      • 构造函数和析构函数中访问
    • 纯虚函数
    • 抽象类
    • 虚析构函数
    • 重载、隐藏、覆盖
    • 菱形继承
    • 虚拟继承
    • 虚拟继承时派生类对象的构造和析构

注:C++Primer 学习笔记

什么是多态?

多态性( polymorphism )是面向对象设计语言的基本特征之一。仅仅是将数据和函数捆绑在一起,进行类的封装,使用一些简单的继承,还不能算是真正应用了面向对象的设计思想。多态性是面向对象的精髓。多态性可以简单地概括为“一个接口,多种方法”

通常是指对于同一个消息、同一种调用,在不同的场合,不同的情况下,执行不同的行为 。

为什么使用多态?

我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类)。它们的目的都是为了代码重用。而多态除了代码的复用性外,还可以解决项目中紧偶合的问题,提高程序的可扩展性。

如果项目耦合度很高的情况下,维护代码时修改一个地方会牵连到很多地方,会无休止的增加开发成本。而降低耦合度,可以保证程序的扩展性。而多态对代码具有很好的可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。

C++支持两种多态性:编译时多态和运行时多态。

编译时多态:也称为静态多态,我们之前学习过的函数重载、运算符重载就是采用的静态多态,C++编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数,又称为先期联编(early binding)。

运行时多态:在一些场合下,编译器无法在编译过程中完成联编,必须在程序运行时完成选择,因此编译器必须提供这么一套称为“动态联编”(dynamic binding)的机制,也叫晚期联编(late binding),C++通过虚函数来实现动态联编

虚函数的定义

虚函数就是在基类中被声明为virtual,并在一个或多个派生类中被重新定义的成员函数。其形式如下:

// 类内部
class 类名 {
	virtual 返回类型 函数名(参数表)
	{
		//...
	}
};
//类之外
virtual 返回类型 类名::函数名(参数表)
{
	//...
}

如果一个基类的成员函数定义为虚函数,那么它在所有派生类中也保持为虚函数,即时在派生类中忽略了virtual关键字,也仍然是虚函数。派生类对虚函数可根据需要重定义,重定义的格式有一定的要求:
1、与基类的虚函数有相同的参数个数
2、与基类的虚函数有相同的参数类型
3、与基类的虚函数有相同的返回类型

class Base {
public:
	virtual void display()
	{   cout << "Base::display()" << endl;  }
	virtual void print()
	{   cout << "Base::print()" << endl;    }
};

class Derived
: public Base {
public:
	virtual void display()
	{ cout << "Derived::display()" << endl; }
};

void test(Base * pbase)
{
	pbase->display();
	pbase->print();
}

int main(int argc,char **argv)
{
    Base base;
    Derived derived;
    test(&base);
    test(&derived);
    return 0;
}

程序执行结果:
Base::display()
Base::print()
Derived::display()
Base::print()

上面的例子中,对于test() 函数,如果不管测试的结果,从其实现来看,通过类Base 的指针pbase 只能调用到Base 类型的display 函数;但是最终结果test(&derived)调用,会调用到Derived 类的display 函数,这里就体现出虚函数的作用了,这是怎么做到的呢,或者说虚函数底层是怎么实现的呢?

虚函数的实现机制

虚函数就是通过一张虚函数表(Virtual Function Table)实现的。具体地讲,当类中定义了一个虚函数后,会在该类创建的对象的存储布局的开始位置多一个虚函数指针(vfptr),该虚函数指针指向了一张虚函数表,而该虚函数表就像一个数组,表中存放的就是各虚函数的入口地址。如下图:

当一个基类中设有虚函数,而一个派生类继承了该基类,并对虚函数进行了重定义,我们称之为覆盖(override),这里的覆盖指的是派生类的虚函数表中相应虚函数的入口地址被覆盖。

在这里插入图片描述
虚函数机制是如何被激活的呢,或者说动态多态是怎样表现出来的呢?

1、基类定义虚函数,派生类重定义虚函数(需要有继承关系)
2、创建派生类对象
3、基类指针或引用指向派生类对象
4、基类指针调用虚函数

哪些函数不能被设置为虚函数?

1、普通函数(非成员函数):定义虚函数的主要目的是为了重写达到多态,所以普通函数声明为虚函数没有意义,因此编译器在编译时就绑定了它。

2、静态成员函数:静态成员函数对于每个类都只有一份代码,所有对象都可以共享这份代码,他不归某一个对象所有,所以它也没有动态绑定的必要。

3、内联成员函数:内联函数本就是为了减少函数调用的代价,所以在代码中直接展开。但虚函数一定要创建虚函数表,这两者不可能统一。另外,内联函数在编译时被展开,而虚函数在运行时才动态绑定。

4、构造函数:这个原因很简单,主要从语义上考虑。因为构造函数本来是为了初始化对象成员才产生的,然而虚函数的目的是为了在完全不了解细节的情况下也能正确处理对象,两者根本不能“ 好好相处 ”。因为虚函数要对不同类型的对象产生不同的动作,如果将构造函数定义成虚函数,那么对象都没有产生,怎么完成想要的动作呢。

5、友元函数:当我们把一个函数声明为一个类的友元函数时,它只是一个可以访问类内成员的普通函数,并不是这个类的成员函数,自然也不能在自己的类内将它声明为虚函数。

虚函数的访问

指针访问

使用指针访问非虚函数时,编译器根据指针本身的类型决定要调用哪个函数,而不是根据指针指向的对象类型;使用指针访问虚函数时,编译器根据指针所指对象的类型决定要调用哪个函数(动态联编),而与指针本身的类型无关。

引用访问

使用引用访问虚函数,与使用指针访问虚函数类似,表现出动态多态特性。不同的是,引用一经声明后,引用变量本身无论如何改变,其调用的函数就不会再改变,始终指向其开始定义时的函数。因此在使用上有一定限制,但这在一定程度上提高了代码的安全性,特别体现在函数参数传递等场合中,可以将引用理解成一种“受限制的指针” 。

对象访问

和普通函数一样,虚函数一样可以通过对象名来调用,此时编译器采用的是静态联编。通过对象名访问虚函数时,调用哪个类的函数取决于定义对象名的类型。对象类型是基类时,就调用基类的函数;对象类型是子类时,就调用子类的函数。

成员函数中的访问

在类内的成员函数中访问该类层次中的虚函数,采用动态联编,要使用this指针。

构造函数和析构函数中访问

构造函数和析构函数是特殊的成员函数,在其中访问虚函数时,C++采用静态联编,即在构造函数或析构函数内,即使是使用"this->虚函数名"的形式来调用,编译器仍将其解释为静态联编的“本类名::虚函数名”。即它们所调用的虚函数是自己类中定义的函数,如果在自己的类中没有实现该函数,则调用的基类中的虚函数。但绝对不会调用任何在派生类中重定义的虚函数。

纯虚函数

纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的是实现留给该基类的派生类去做。这就是纯虚函数的作用。纯虚函数的格式如下:

class 类名 {
public:
	virtual 返回类型 函数名(参数包) = 0;
}

设置纯虚函数的意义,就是让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它。

class Base { 
public:
	virtual void display() = 0;
};

class Child
: public Base {
public:
	virtual void display()
	{ cout << "Child::display()" << endl;} 
}

声明纯虚函数的目的在于,提供一个与派生类一致的接口。
注:成员函数后加const后我们称这个函数为常函数,常函数内不可以修改成员属性,成员属性声明时加关键字mutable后,在常函数中依然可以修改。

class Figure {
public:
	virtual void display() const = 0;
	virtual double area() const = 0;
};

class Circle
: public Figure {
public:
	explicit Circle(double radius)
	:_radius(radius)
	{}
	void display() const {cout << "Circle";}
	double area() const
	{return 3.14159 * _radius * _radius;}
private:
	double _radius;
}

class Rectangle
: public Figure {
public:
	Rectangle(double length, double width)
	: _length(length)
	, _width(width)
	{}
	void display() const { cout << "Rectangle"; }
	double area() const { return _length * _width; }
private:
	double _length;
	double _width;
};

class Triangle
: public Figure {
public:
	Triangle(double a, double b, double c)
	: _a(a)
	, _b(b)
	, _c(c)
	{}
	void display() const { cout << "Triangle"; }
	double area() const {
		double p = (_a + _b + _c) / 2;
		return sqrt(p * (p - _a) * (p - _b) * (p - _c));
	}
private:
	double _a;
	double _b;
	double _c;
};

抽象类

一个类可以包含多个纯虚函数。只要类中含有一个纯虚函数,该类便为抽象类。一个抽象类只能作为基类来派生新类,不能创建抽象类的对象。

和普通的虚函数不同,在派生类中一般要对基类中纯虚函数进行重定义。如果该派生类没有对所有的纯虚函数进行重定义,则该派生类也会成为抽象类。这说明只有在派生类中给出了基类中所有纯虚函数的实现时,该派生类便不再是抽象类。

除此以外,还有另外一种形式的抽象类。对一个类来说,如果只定义了protected型的构造函数而没有提供public构造函数,无论是在外部还是在派生类中作为其对象成员都不能创建该类的对象,但可以由其派生出新的类,这种能派生新类,却不能创建自己对象的类是另一种形式的抽象类。

class Base {
protected:
	Base(int base): _base(base) { cout << "Base()" << endl;}
public:
	int _base;
};

class Derived
: public Base {
public:
	Derived(int base, int derived)
	: Base(base)
	, _derived(derived)
	{ cout << "Derived(int,int)" << endl; }
	void print() const
	{
		cout << "_base:" << _base
		<< ", _derived:" << _derived << endl;
	}
private:
	int _derived;
};
void test()
{
	Base base(1);//error
	Derived derived(1, 2);
}

虚析构函数

虽然构造函数不能被定义成虚函数,但析构函数可以定义为虚函数,一般来说,如果类中定义了虚函数,析构函数也应被定义为虚析构函数,尤其是类内有申请的动态内存,需要清理和释放的时候。

class Base {
public: 
	Base(const char * pstr)
	: _pstr(new char[strlen(pstr)+1]())
	{ cout << "Base(const char *)" << endl;}

	~Base() {
		delete [] _pstr;
		cout << "~Base()" << endl;
	}
private:
	char * _pstr;
}

class Derived
: public Base {
public:
	Derived(const char * pstr, const char * pstr2)
	: Base(pstr)
	, _pstr2(new char[strlen(pstr2)+1]())
	{ cout << "Derived(const char *, const char *)" << endl;}

	~Derived(){
		delete [] _pstr2;
		cout << "~Derived()" << endl;
	}
private:
	char * _pstr2;
}

void test()
{
	Base * pbase = new Derived("hello","wuhan");
	delete pbase;
}

如上,在例子中,如果基类Base的析构函数没有设置成虚函数,则在执行delete pbase;语句时,不会调用派生类Derived的析构函数,这样就造成内存泄漏。此时,将基类Base的析构函数设置为虚函数,就可以解决该问题。

如果有一个基类的指针指向派生类的对象,并且想通过该指针delete派生类对象,系统只会执行基类的析构函数,而不会执行派生类的析构函数。为避免这种情况的发生,往往把基类的析构函数声明为虚的,此时,系统将先执行派生类对象的析构函数,然后再执行基类的析构函数。

如果基类的析构函数声明为虚的,派生类的析构函数也将自动成为虚析构函数,无论派生类析构函数声明中是否加virtual关键字。

重载、隐藏、覆盖

重载:发生在同一个类中,函数名称相同,但是参数的类型、个数、顺序不同。

覆盖:发生在父子类中,同名虚函数,参数亦完全相同。

隐藏:发生在父子类中,指的是在某些情况下,派生类中的函数屏蔽了基类中的同名函数。当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。

菱形继承

菱形继承概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类。
设定基类为Base,两个派生类为Derived1和Derived2,该类为MyClass
则Derived1和Derived2都继承了Base的数据,当MyClass使用Base中的数据时,会产生二义性。
(多继承引发的二义性,我们可以通过作用域加以区分)。
并且,在MyClass类中,Base中的数据我们只需要一份就可以了。

class Base {
public:
	int _data;
};

class Derived1 : virtual public Base {

};

class Derived2 : virtual public Base {

};

class MyClass : public Derived1, public Derived2 {


};

void test() {
	MyClass m;
	//m._data = 20;   产生不明确的数据问题
	m.Derived1::_data = 20;
	m.Derived2::_data = 10;
	//当菱形继承,两个父类拥有相同数据,需要加以作用域区分
	cout << "m.Derived1::_data = " << m.Derived1::_data << endl;
	cout << "m.Derived2::_data = " << m.Derived2::_data << endl;

	//菱形继承导致了最初的基类数据有两份,但我们只需要一份,浪费资源
	//利用虚继承 解决菱形继承问题
    //继承之前 加上关键字virtual变为虚继承,则Base类称为虚基类
	//当我们发生虚继承之后,该份_data数据就只有一个了

	cout << "m._data = " << m._data << endl;

	//采用virtual继承时,MyClass从Derived1和Derived2继承下来的不再是两份_data,而是两个vbptr
	//vbptr 虚基类指针 指向了 vbtable 虚基类表
	//两个vbptr分别指向两个不同父类的vbtable,表中存有_data数据的地址,
	//这个地址只有一份,通过加上不同的偏移可以得到同一块_data数据内存的地址
}

虚拟继承

C++ 中被virtual关键字所修饰的事物或现象在本质上是存在的,但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段才能够体现出其实际上的效果。关键就在于存在、间接和共享这三种特征:

1、虚函数是存在的
2、虚函数必须通过一种间接的运行时(而不是编译时)机制才能够激活(调用)的函数。
3、共享性表现在基类会共享被派生类重定义后的虚函数

那虚拟继承又是如何表现这三种特征的呢?

1、存在即表示虚继承体系和虚基类确实存在
2、间接性表现在当访问虚基类的成员时同样也必须通过某种间接机制来完成(通过虚基表来完成)
3、共享性表现在虚基类会在虚继承体系中被共享,而不会出现多份拷贝

虚拟继承是指在继承定义中包含了virtual关键字的继承关系。虚基类是指在虚继承体系中的通过virtual继承而来的基类。语法格式如下:

class Baseclass;
class Subclass
: public/private/protected virtual Baseclass
{
public:    //...
private:   //...
protected: //...
};
//其中Baseclass称之为Subclass的虚基类,而不是说Baseclass就是虚基类
#pragma vtordisp(off)
#include <iostream>
using std::cout;
using std::endl;

class A {
public:
	A() : _ia(10) {}
	virtual void f() { cout << "A::f()" << endl; }
private:
	int _ia;
};
class B
: virtual public A {
public:
	B() : _ib(20) {}
	void fb() { cout << "A::fb()" << endl; }
	/*virtual*/ void f() { cout << "B::f()" << endl; }
	virtual void fb2() { cout << "B::fb2()" << endl; }
private:
	int _ib;
};
void test(void) {
	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	B b;
	return 0;
}
// 结论一:单个虚继承,不带虚函数
// 虚继承与继承的区别
// 1. 多了一个虚基指针
// 2. 虚基类位于派生类存储空间的最末尾
// 结论二:单个虚继承,带虚函数
// 1. 如果派生类没有自己的虚函数,此时派生类对象不会产生虚函数指针
// 2. 如果派生类拥有自己的虚函数,此时派生类对象就会产生自己本身的虚函数指针,并且该虚函数指针位于派生类对象存储空间的开始位置

虚拟继承时派生类对象的构造和析构

在普通的继承体系中,比如A派生出B,B派生出C,则创建C对象时,在C类构造函数的初始化列表中调用B类构造函数,然后在B类构造函数初始化列表中调用A类的构造函数,即可完成对象的创建操作。但在虚拟继承中,则有所不同。

#include <iostream>

using namespace std;

class A{
public:
    A(){ cout << "A()" << endl; }
    
    A(int ia):_ia(ia) 
    {cout << "A(int)" << endl;}
    
    void f() { cout << "A::f()" << endl; }
protected:
    int _ia;
};

class B
: virtual public A{
public:
    
    B(){ cout << "B()" << endl; }
    
    B(int ia, int ib)
    :A(ia)
    ,_ib(ib)
    { cout << "B(int,int)" << endl;}

protected:
    int _ib;
};

class C
: public B {
public:
    
    C(int ia, int ib, int ic)
    : B(ia, ib)
    , _ic(ic)
    { cout << "C(int,int,int)" << endl; }

    void show() const {
        cout << "_ia: " << _ia << endl
             << "_ib: " << _ib << endl
             << "_ic: " << _ic << endl;
    }
private:
    int _ic;
};

int main(int argc,char **argv)
{
    C c(10,20,30);
    c.show();
    return 0;
}

该程序的执行结果为:

A()
B(int,int)
C(int,int,int)
_ia: 190518752
_ib: 20
_ic: 30

从最终的打印结果来看,在创建对象c的过程中,我们看到C带三个参数的构造函数执行了,同时B带两个参数的构造函数也执行了,但A带一个参数的构造函数没有执行,而是执行了A的默认构造函数。这与我们的预期是有差别的。如果想要得到预期的结果,我们还需要在C的构造函数初始化列表最后,显式调用A的相应构造函数。那为什么需要这样做呢?

在 C++ 中,如果继承链上存在虚继承的基类,则最底层的子类要负责完成该虚基类部分成员的构造。即我们需要显式调用虚基类的构造函数来完成初始化,如果不显式调用,则编译器会调用虚基类的缺省构造函数,不管初始化列表中次序如何,对虚基类构造函数的调用总是先于普通基类的构造函数。如果虚基类中没有定义的缺省构造函数,则会编译错误。因为如果不这样做,虚基类部分会在存在的多个继承链上被多次初始化。很多时候,对于继承链上的中间类,我们也会在其构造函数中显式调用虚基类的构造函数,因为一旦有人要创建这些中间类的对象,我们要保证它们能够得到正确的初始化。这种情况在菱形继承中非常明显, 我们接下来看看这种情况。

#include <iostream>
using std::cout;
using std::endl;
class B {
public:
	B() : _ib(10), _cb('B') { cout << "B()" << endl; }
	B(int ib, char cb)
	: _ib(ib), _cb(cb){ cout << "B(int,char)" << endl; }
	//virtual
	void f() { cout << "B::f()" << endl; }
	//virtual
	void Bf() { cout << "B::Bf()" << endl; }
private:
	int _ib;
	char _cb;
};
class B1 : virtual public B {
public:
	B1() : _ib1(100), _cb1('1') {}
	B1(int ib, char ic, int ib1, char cb1)
	: B(ib, ic)
	, _ib1(ib1)
	, _cb1(cb1)
	{ cout << "B1(int,char,int,char)" << endl; }
	//virtual
	void f() { cout << "B1::f()" << endl; }
	//virtual
	void f1() { cout << "B1::f1()" << endl; }
	//virtual
	void Bf1() { cout << "B1::Bf1()" << endl; }
private:
	int _ib1;
	char _cb1;
};

class B2 : virtual public B {
public:
	B2() : _ib2(1000), _cb2('2') {}
	B2(int ib, char ic, int ib2, char cb2)
	: B(ib, ic)
	, _ib2(ib2)
	, _cb2(cb2)
	{ cout << "B2(int,char,int,char)" << endl; }
	//virtual
	void f() { cout << "B2::f()" << endl; }
	//virtual
	void f2() { cout << "B2::f2()" << endl; }
	//virtual
	void Bf2() { cout << "B2::Bf2()" << endl; }
private:
	int _ib2;
	char _cb2;
};
class D
: public B1
, public B2 {
public:
	D() : _id(10000), _cd('3') {}
	D(int ib1, char ib1,
	int ib2, char cb2,
	int id, char cd)
	: B1(ib1, ib1)
	, B2(ib2, cb2)
	, _id(id)
	, _cd(cd)
	{ cout << "D(...)" << endl; }
	//virtual
	void f() { cout << "D::f()" << endl; }
	//virtual
	void f1() { cout << "D::f1()" << endl; }
	//virtual
	void f2() { cout << "D::f2()" << endl; }
	//virtual
	void Df() { cout << "D::Df()" << endl; }
private:
	int _id;
	char _cd;
};
void test(void) {
	D d;
	cout << sizeof(d) << endl;
	return 0;
}
//结论:虚基指针所指向的虚基表的内容
// 1. 虚基指针的第一条内容表示的是该虚基指针距离所在的子对象的首地址的偏移
// 2. 虚基指针的第二条内容表示的是该虚基指针距离虚基类子对象的首地址的偏移

如果是在若干类层次中,从虚基类直接或间接派生出来的派生类的构造函数初始化列表均有对该虚基类构造函数的调用,**那么创建一个派生类对象的时候只有该派生类列出的虚基类的构造函数被调用,其他类列出的将被忽略,**这样就保证虚基类的唯一副本只被初始化一次。即虚基类的构造函数只被执行一次。

对于虚继承的派生类对象的析构,析构函数的调用顺序为:

1、先调用派生类的析构函数;
2、然后调用派生类中成员对象的析构函数;
3、再调用普通基类的析构函数;
4、最后调用虚基类的析构函数。

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

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

相关文章

spring boot文档阅读笔记——01

目录标题一、文档地址二、第一个spring boot例子三、 Starters&#xff08;spring boot 官方提供的启动器&#xff09;四、SpringBootApplication注释&#xff08;一&#xff09;EnableAutoConfiguration&#xff08;二&#xff09;ComponentScan五、devtools&#xff08;热插拔…

当项目经理看世界杯决赛时…

12月18日&#xff0c;2022卡塔尔世界杯决赛&#xff0c;阿根廷在点球大战中击败卫冕冠军的法国队&#xff0c;捧走大力神杯。这场跌宕起伏的“巅峰对决”&#xff0c;给大家呈现了一场精彩绝伦的比赛。 当阿根廷2-0领先七十多分钟的时候&#xff0c;都以为这局稳了&#xff0c…

跨平台应用开发进阶(五十一):HTML5(富文本内容)连续数字、字母不自动换行问题分析及解决

文章目录一、前言二、问题分析三、解决方法3.1 对 input 标签设置3.2 对 input 标签内的 p 标签设置四、延伸阅读 顶部状态栏穿透问题五、拓展阅读一、前言 项目开发过程中&#xff0c;涉及在Web端维护富文本内容&#xff0c;通过APP端查看的相关的功能&#xff0c;功能描述大…

repo init详解

首先选择manifest源&#xff0c;一般直接使用清华或中科大的镜像源 repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest是清华提供的镜像源 repo init -u git://mirrors.ustc.edu.cn/aosp/platform/manifest是中国科学技术大学的镜像源 repo init推荐使用-b 分…

未来汽车产业新生态高峰论坛在深圳举行

【2022年12月22日发自深圳】汽车产业正面临百年未有之大变局&#xff0c;以数字化技术为特征的智能网联汽车已经成为全球汽车产业转型升级的战略方向。汽车的属性也从一个机械化的交通工具转变成与各个生态相互连通的移动终端和数字空间。12月21日&#xff0c;由工业和信息化部…

【C++初阶】模板进阶

文章目录非类型模板参数模板特化函数模板特化类模板特化全特化偏特化模板的分离编译模板总结所有测试的代码非类型模板参数 模板参数分类类型形参与非类型形参 1.类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 2.非类型形…

创建城市人口总量趋势图

创建城市人口总量趋势图学习目标使用数据人口总量趋势方程数据导入对数据进行处理fct_reorder2的应用对数据进行整理对图像进行可视化操作使用fct_reorder2去掉趋势点内容小结学习目标 我们所采用的学习内容来自B站的Lizongzhang老师的R语言的学习分享 今天学习的主要内容是关…

数据集说明:COCO的两个数据集COCO-stuff和COCO-Caption

数据集图片数量比较 COCO-Caption有33万张图、50万个caption COCO-Stuff有16.4万张图 172个类别(80thing、91stuff、1个未标注类) COCO-stuff的具体类别说明 COCO-Stuff contains 172 classes:80 thing, 91 stuff, and 1 class unlabeled. The 80 thing classes are the same …

群晖nas部署python项目

事件起因是因为想再硬盘里下载小姐姐&#xff0c;在线播放会卡顿很不爽&#xff0c; 开始用一些在线爬的网站&#xff0c;网速非常慢&#xff0c;我发现自己有nas 如果把项目部署再nas上 通过命令行下载就好了 我就开始检索资料&#xff0c;发现网上的都是一些过时的文章&#…

分布式系统关键路径延迟分析实践

作者 | 月色如海 导读 随着对用户体验的不断追求&#xff0c;延迟分析成为大型分布式系统中不可或缺的一环。本文介绍了目前在线服务中常用的延迟分析方法&#xff0c;重点讲解了关键路径分析的原理和技术实现方案&#xff0c;实践表明此方案效果显著&#xff0c;在耗时优化方面…

图解深度学习-提高泛化能力的方法

数据集 在深度学习的训练过程中&#xff0c;神经网络的类型和结构固然重要&#xff0c;但训练样本才是重中之重。 数据增强 当训练样本数量较少时&#xff0c;可以通过数据增强来增加样本的多样性。 数据增强就是通过对样本图像进行平移、旋转、镜像翻转等方式进行变换。除…

【Java】Log4j日志

目录 1.日志概述 日志是什么 为什么会有日志 2.log4j概述 什么是log4j 版本 3.log4j入门案例 步骤 实现 总结 4.log4j1详情&#xff1a;记录器rootLogger 5.log4j1详情&#xff1a;日志级别 6.log4j1详情&#xff1a;输出源appender ConsoleAppender FileAppend…

第二十八章 数论——扩展欧几里德算法与线性同余方程

第二十八章 扩展欧几里德算法一、裴蜀定理1、定理内容2、定理证明二、扩展欧几里德定理1、作用2、思路3、代码三、线性同余方程1、问题2、思路3、代码一、裴蜀定理 1、定理内容 对于任意整数aaa和bbb&#xff0c;一定存在整数xxx&#xff0c;yyy使得axbyaxbyaxby是gcd(a,b)gc…

Linux的基本指令

前言 相对于linux操作系统,其实window也是操作系统&#xff0c;我们这节课说的是Linux的基本指令 那么在window上有没有基本指令呢&#xff0c;答案是有的&#xff0c;我们可以使用windowsr进入cmd就可以用我们的基本指令 基本指令 在本文中&#xff0c;我们基本上都使用类比的…

基于threeJS实现圣诞节孔明灯效果

1.效果图 2.实现思路 使用three.js的套路几乎是固定的&#xff1a; 1 初始化场景&#xff08;scene&#xff09; 2.创建透视相机&#xff08;camera&#xff09; 3.设置相机位置&#xff08;position&#xff09; 4.创建纹理加载器对象&#xff08;texture&#xff09; 5.创建…

【SegNeXt】语义分割中对卷积注意力设计的反思

目录 1.摘要 2.相关工作 2.1语义分割 2.2多尺度网络 2.3注意力机制 3.网络结构 3.1卷积编码器 3.2解码器 4.实验 个人总结&#xff1a; 论文链接&#xff1a;论文 代码链接&#xff1a;代码 论文发表于NeurIPS 2022&#xff0c;值得注意的是&#xff0c;在Transforme…

UVM入门和进阶实验0

一. 概述 UVM学习流程仍然按照SV时候的核心流程&#xff0c;即&#xff1a; 如何搭建验证框架验证组件之间的连接和通信如何编写测试用例&#xff0c;继而完成复用和覆盖率的收敛 我们UVM入门和进阶实验0还是同之前SV验证实验0思想一样&#xff0c;让大家通过简单的实验要求…

学习HTTP协议,这一篇就够啦 ~~

HTTP协议一、什么是HTTP1.1 应用层协议1.2 HTTP1.3 HTTP协议的工作过程二、HTTP协议格式2.1 Fiddler抓包工具2.2 协议格式三、HTTP请求 (Request)3.1 认识 "方法" (method)3.1.1 GET 方法3.1.2 POST 方法3.1.3 GET和POST比较3.1.4 其他方法3.2 认识URL3.2.1 URL基本格…

基于迅为3568开发板的多屏同/异显动态方案

iTOP-RK3568开发板采用四核Cortex-A55处理器&#xff0c;芯片内置VOP控制器&#xff0c;支持HDMI、LVDS、MIPI、EDP四种显示接口的多屏同显、异显和异触&#xff0c;可有效提高行业定制的拓展性。 iTOP-RK3568开发板支持以下屏幕&#xff1a; 迅为 LVDS 7 寸屏幕 迅为 LVDS …

【数学】三角函数小题

∣三角函数小题NightguardSeries.∣\begin{vmatrix}\huge{\textsf{ 三角函数小题 }}\\\texttt{Nightguard Series.}\end{vmatrix}​ 三角函数小题 Nightguard Series.​​ 以下是一些废话 不愿意看的可以跳过 最近守夜人更新得很慢&#xff0c;守夜人拖更主要有三个原因&#…