6.18 多态

多态相较于继承是更加重要的体现面向对象的特征。

多态: 同一个消息、同一种调用,在不同的场合,不同的情况下,执行不同的行为 。

背景需求:继承是实现可以在圆柱或者圆锥中复用圆的特征,多态是可以通过一个圆直接就可以调用圆柱的体积,或者调用圆锥的体积。继承实现了很好的代码复用性,多态的实现可以更好的降低耦合性,若高耦合在修改代码的时候往往需要修改多出。

多态分类:静态多态(编译时多态)和动态多态(运行时多态)

静态多态: 以前其实我们以前学习过的函数重载和运算符重载理解为采用的是静态重载,因为同一个运算符是进行不同的操作。在编译时候选择要运行哪个函数,静态联编。

动态多态(重点): C++通过虚函数来实现动态联编。在运行时候选择要运行哪个函数,动态联编。

基类指针指向派生类对象

经过前一章的学习可以知道基类指针不能访问派生类的同名成员函数,但是在基类中同名函数前面加上vistual变为虚函数以后就可以访问到派生类的同名函数。

实现虚函数定义以后,发生了覆盖的机制(override),覆盖的是虚函数表中虚函数的入口地址

定义虚函数以后就产生了相关的虚函数指针,通过虚函数指针去虚函数表中查找相关的信息。 

Base* p 去指向Derived对象,依然只能访问到基类的部分。用指针p去调用display函数,发现是一个虚函数,那么会通过vfptr找到虚表,此时虚表中存放的是Derived::display的入口地址,所以调用到Derived的display函数。

【注意】1.覆盖的是虚函数表中的入口地址,并不是覆盖函数本身。 2. 在派生类中加上vistual都可

进一步测试发现:多态被覆盖的要求:返回值,参数和函数名必须都完全一致

这样就可能出现一个问题就是:当我们希望能够覆盖的时候,有可能会在派生类中写错,所以说在派生类的定义中加上 override(覆盖)(overload重载,oversee隐藏)表示这个函数要求实现对于基类的函数进行覆盖。这个时候会去基类中去找,如果基类中没有相关的函数就会报错。

虚函数表

virtual关键字的含义理解(六字箴言):

存在(虚函数是实际存在的),间接(通过基类指针而不是派生类对象访问),共享(基类可以访问到派生类的函数,就像是共享)

虚函数表的存在位置:

当编译完成以后,虚函数表中的内容不应该改变,所以说应该放在只读区域。通过sizeof可以得到大小是包含指针在其中的,所以说在编译的时候就已经将虚函数表放在一个只读区域中去了。

虚函数表的个数:

没有虚函数的时候就没有虚函数表。

多个虚函数在一个基类中,这个时候就会有一个虚函数表。如果这个时候在派生类中继续定义了虚函数,这个时候依然是只有一个虚函数表,也是借助于基类的虚函数指针接着进行存储。

如果是有多个基类,这个时候有几个基类有虚函数那么就有几张虚表。

上述可以通过sizeof进行验证,多了一个虚表也就会多一个虚表指针。

虚函数底层的实现就是:

基类定义一个虚函数,派生类定义一个完全相同的函数进行覆盖,基类指针(引用)指向派生类的对象。这个时候就在编译的时候实现虚函数表的地址的覆盖。

虚函数的限制

构造函数不能设置为虚函数:虚函数多态的实现就是借助于对象,构造函数还没有创建对象,多以不能设置为虚函数。

析构函数最好设置为虚函数

静态函数不能设置为虚函数:但是静态成员函数没有this指针所以访问不到虚函数指针

inline函数不能设置为虚函数:因为都是在编译期间进行处理,会产生冲突,这个时候不会内联(条件弱是发出请求)。

虚函数的调用各种访问情况:

1.通过派生类对象访问虚函数的时候,这个时候并没有产生一个覆盖的效果,实际上就是一个隐藏的实现,当在执行时候并没有走虚函数表

2.构造函数和析构函数中访问虚函数

这个问题主要是了解函数的执行时机

class Grandpa
{
public:
    Grandpa(){ cout << "Grandpa()" << endl; }
    ~Grandpa(){ cout << "~Grandpa()" << endl; }

    virtual void func1() {
        cout << "Grandpa::func1()" << endl;
    }

    virtual void func2(){
        cout << "Grandpa::func2()" << endl;
    }
};

class Parent
: public Grandpa
{
public:
    Parent(){
        cout << "Parent()" << endl;
        //func1();//构造函数中调用虚函数
    }

    ~Parent(){
        cout << "~Parent()" << endl;
        //func2();//析构函数中调用虚函数
    }
};

class Son
: public Parent
{
public:
    Son() { cout << "Son()" << endl; }
    ~Son() { cout << "~Son()" << endl; }

    virtual void func1() override {
        cout << "Son::func1()" << endl;
    }

    virtual void func2() override{
        cout << "Son::func2()" << endl;
    }
};

void test0(){
    Son ss;
    Grandpa * p = &ss;
    p->func1();
    p->func2();
}

上述的例子中就是可以知道,隔代也是可以调用虚函数,son中的fun1和fun2可以对于grandpa中的函数进行覆盖,很好理解因为只有一个虚函数指针。

同时还有一个地方就是在son类中不能直接对于parent直接进行构造函数的定义

这样一个三层的类的情况,在parent类中调用func1()或者是func2()的时候,不能知道孩子情况,所以说调用的只能是上一层级的函数,因为son的类还没有创建出来。

在parent析构函数中也是定义fun1的时候调用到的还是grand中的因为,这个时候son已经被销毁。

3.在普通的成员函数中

class Base{
public:
    Base(long x)
    : _base(x)
    {}

    virtual void display() const{
        cout << "Base::display()" << endl;
    }

    void func1(){
        display();
        cout << _base << endl;
    }

    void func2(){
        Base::display();
    }
private:
    long _base = 10;
};


class Derived
: public Base
{
public:
    Derived(long base,long derived)
    : Base(base)
    , _derived(derived)
    {}

    void display() const override{
        cout << "Derived::display()" << endl;
    }
private:
    long _derived;
};

void test0(){
    Base base(10);
    Derived derived(1,2);

    base.func1();
    base.func2();

    derived.func1(); //调用了Derived::display();
    derived.func2();
}

主要的问题是第三次调用的时候,因为是derived对象调用的时候,使用到的this指针开始是一个derived类型的指针,然后向上转型为base类型的指针,这个时候就变成标准的基类的指针指向派生类的对象,所以说当然就是进行一个虚函数的使用。

抽象类

抽象类的两种形式:

1.定义纯虚函数的类

2 . 只定义了protected型构造函数的类

纯虚函数

纯虚函数就是在基类中不需要进行定义只是进行声明,在派生类中进行不同的实现。

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

声明了纯虚函数的类是一个抽象类,不能实例化为一个对象,可以设置一个指针。

派生类对于基类的纯虚函数都要进行实现,这样的话才能实例化一个对象。当然也可以该类继承以后没有全部实现(不能创建对象),可以在接下来的子类中继续进行实现当达到全部实现的时候就可以创建对象了。

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

class B
: public A
{
public:
    virtual void print() override{
        cout << "B::print()" << endl;
    }
};

class C
: public B
{
public:
    virtual void display() override{
        cout << "C::display()" << endl;
    }
};

void test0(){
    //A类定义了纯虚函数,A类是抽象类
    //抽象类无法创建对象
    //A a;//error
  
    //B b;//error
    C c;
    A * pa2 = &c;
    pa2->print();
    pa2->display();
}

都是通过基类的指针进行调用,指向完全定义以后的派生类对象。

当想要定义一个在一个类中进行独有,该类的不同的对象中共享的时候,并且不想要数据被任意的修改。在类中定义一个static const类型的数据。

这个地方了解一个小的点就是如果定义的int类型数据可以直接在类中直接赋值,如果是double类型使用const不行,因为int类型在编译的时候及已经确定,double比较复杂需要在运行才确定,使用constexpr可以在编译时直接确定。

基类在protected中写构造函数

这个时候基类就不能创建一个对象,但是在派生类中可以使用构造函数。

这个时候基类就是一个抽象类。

【注意】这种情况下如果是使用成员对象的方式构造基类对象是有问题的,即使说是构造函数其实也是在派生类的初始化列表中调用,之所以禁止是因为也有可能会出现并不是派生类中调用成员子对象的构造函数,所以直接就禁止成员子对象的构造函数也视为类外。

【注意】单例模式类是不能进行派生的,因为构造函数的是私有的,这样也和单例相符。

析构函数推荐被定义为虚函数(重点)

class Base
{
public:
    Base()
    : _base(new int(10))
    { cout << "Base()" << endl; }

    virtual void display() const{
        cout << "*_base:" << *_base << endl;
    }

    ~Base(){
        if(_base){
            delete _base;
            _base = nullptr;
        }
        cout << "~Base()" << endl;
    }

private:
    int * _base;
};

class Derived
: public Base
{
public:
    Derived()
    : Base()
    , _derived(new int(20))
    {
        cout << "Derived()" << endl;
    }

    virtual void display() const override{
        cout << "*_derived:" << *_derived << endl;
    }

    ~Derived(){
        if(_derived){
            delete _derived;
            _derived = nullptr;
        }
        cout << "~Derived()" << endl;
    }

private:
    int * _derived;
};

void test0(){
    Base * pbase = new Derived();
    pbase->display();

    delete pbase;
    //编译器会进行类型检查,pbase指向的空间是一个Derived对象
  	//所以会调用Derived的析构函数 —— 需要让析构函数设为虚函数,Derived析构函数会在虚表中覆盖Base析构函数的地址
    //这样通过pbase才能调用到Derived析构函数
    //Derived析构函数执行完,会自动调用Base的析构函数(没有走虚表这个途径) —— 析构函数本身的机制
}

 这个时候如果没有delete因为对象是在堆上的,所以不会直接主动释放的。没有delete的话会泄漏3个指针和2个int类型数据一共32个字节。

这个时候想要将这些空间都释放掉,如果是想要使用基类指针释放掉空间的话,仍然会有四个字节的泄漏,因为base的控制空间不能调用derivaed的析构函数所以有一个int泄漏掉

所以说可以将析构函数设置为虚函数,给人的感觉是这样,虽然析构函数名字不同虚函数表进行了覆盖。

在派生类析构函数执行完毕后,会自动调用基类析构函数。这是由编译器在析构函数调用序列中隐式安排的,这个过程不依赖于虚函数表,属于C++的语言规则。

当需要使用基类指针回收派生类对象的时候,将析构函数设置为虚函数。

如果是一个多层级的结构只是在第一个基类中定义析构函数,那么在

当一个类中有虚函数的时候,就设置析构函数为虚函数。

验证虚表的存在(重点)

下面我们就是验证真正的物理空间是不是这个样子的

因为我们知道derived本身就是一个指针两个long型数据进行存储,可以将其视为一个long型数组。

然后根据第一个指针继续访问虚函数表,将访问到的long数据转化为函数指针进行访问函数。

class Base{
public:
	virtual void print() {
		cout << "Base::print()" << endl;
		}
	virtual void display() {
		cout << "Base::display()" << endl;
	}
	virtual void show() {
		cout << "Base::show()" << endl;
	}
private:
	long _base = 10;
};

class Derived
: public Base
{
public:
 	virtual void print() {
		cout << "Derived::print()" << endl;
	}
	virtual void display() {
		cout << "Derived::display()" << endl;
	}
	virtual void show() {
		cout << "Derived::show()" << endl;
	}
private:
	long _derived = 100;
};

void test0(){
	Derived d;
	long * pDerived = reinterpret_cast<long*>(&d);
	cout << pDerived[0] << endl;
	cout << pDerived[1] << endl;
	cout << pDerived[2] << endl;

	cout << endl;
	long * pVtable = reinterpret_cast<long*>(pDerived[0]);
	cout << pVtable[0] << endl;
	cout << pVtable[1] << endl;
	cout << pVtable[2] << endl;

	cout << endl;
	typedef void (*Function)();
	Function f = (Function)(pVtable[0]);
	f();
	f = (Function)(pVtable[1]);
	f();
	f = (Function)(pVtable[2]);
 	f();
}

这个地方就是使用将地址进行转换,而不能将对象直接进行转换,也是因为有类封装的保护。

这个地方将虚表中只是记录了函数的地址,虽然是成员函数,但是不能定义成员函数指针的形式,因为成员函数指针除了函数的地址还有类的相关的信息,所以不能直接成功。

可以验证出来是在文字常量区 

【注意】这样也就是引出一个问题就是这样可以通过指针直接就访问类中的函数,是不是很不安全。

带虚函数的多继承

class Base1
{
public:
	Base1() 
	: _iBase1(10) 
	{ cout << "Base1()" << endl; }
	virtual void f()
	{
		cout << "Base1::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base1::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base1::h()" << endl;
	}

	virtual ~Base1() {}
private:
	double _iBase1;
};

class Base2
{
	//...
private:
	double _iBase2;
};

class Base3
{
public:
	//...
private:
	double _iBase3;
};

class Derived 
	: public Base1
	, public Base2
	, public Base3
{
public:
	Derived()
	: _iDerived(10000) 
	{ cout << "Derived()" << endl; }

	void f()
	{
		cout << "Derived::f()" << endl;
	}

	void g1()
	{
		cout << "Derived::g1()" << endl;
	}
private:
	double _iDerived;
};

int main(void)
{
	cout << sizeof(Derived) << endl;

	Derived d;
	Base1* pBase1 = &d;
	Base2* pBase2 = &d;
	Base3* pBase3 = &d;

	cout << "&Derived = " << &d << endl; 
	cout << "pBase1 = " << pBase1 << endl; 
	cout << "pBase2 = " <11 endl;

	return 0;
}

在有多个基类子对象的时候基类子对象的地址并不一定是和派生类对象的地址是相同的。

在单继承的时候,第一个继承的基类子对象和派生类子对象的地址是相同的,但是与后来的基类子对象的地址是不同的。也就是每个基类指针指向自己的基类子对象的地址

多态情况下的内存声明:vs平台下的底层实现

只是在第一个基类子对象中实现覆盖,在其他的情况下属于使用goto指令。使用到指针的偏移量,向前偏移8或者16位。 

是这样认为派生类中有多个基类的虚函数表,在多继承结构下派生类中自己定义的虚函数是放在第一个虚函数表中,但是是放在第一张虚函数表的最后。

1 . 每个基类都有自己的虚函数表(前提是基类定义了虚函数)

2 . 派生类如果有自己的虚函数,会被加入到第一个虚函数表之中 —— 希望尽快访问到虚函数

这个时候最开始的基类是调用不了派生类中定义的虚函数,因为在基类中并没有定义同名的函数的。

3 . 内存布局中,其基类的布局按照基类被声明时的顺序进行排列(有虚函数的基类会往上放——希望尽快访问到虚函数

4 . 派生类会覆盖基类的虚函数,只有第一个虚函数表中存放的是真实的被覆盖的函数的地址;其它的虚函数表中对应位置存放的并不是真实的对应的虚函数的地址,而是一条跳转指令 —— 指示到哪里去寻找被覆盖的虚函数的地址

带虚函数的多重继承的二义性的问题

class A{
public:
    virtual void a(){ cout << "A::a()" << endl; } 
    virtual void b(){ cout << "A::b()" << endl; } 
    virtual void c(){ cout << "A::c()" << endl; } 
};

class B{
public:
    virtual void a(){ cout << "B::a()" << endl; } 
    virtual void b(){ cout << "B::b()" << endl; } 
    void c(){ cout << "B::c()" << endl; } 
    void d(){ cout << "B::d()" << endl; } 
};

class C
: public A
, public B
{
public:
    virtual void a(){ cout << "C::a()" << endl; } 
    void c(){ cout << "C::c()" << endl; } 
    void d(){ cout << "C::d()" << endl; } 
};


//先不看D类
class D
: public C
{
public:
    void c(){ cout << "D::c()" << endl; }
};
void test0(){
    C c;
    c.a(); //隐藏
    //c.b(); //报错,没有走虚函数表,因为没有基类指针,所以不知道调用a的还是b的
    c.c(); //隐藏
    c.d(); //隐藏
    
    cout << endl;
    A* pa = &c;
    pa->a(); //动态多态c->a()
    pa->b(); //基类子对象a->b(),没有被覆盖
    pa->c(); //动态多态c->c()
    //pa->d();//A没有定义d无法调用 
    
    cout << endl;
    B* pb = &c;
    pb->a(); //动态多态c->a()
    pb->b(); //基类子对象b->b(),没有被覆盖
    pb->c(); 
//基类子对象b->c(),访问权限只能到基类子对象的, 因为不是虚函数不能走虚表
    pb->d(); 
//基类子对象b->d(),访问权限只能到基类子对象的, 因为不是虚函数不能走虚表

    cout << endl;
    C * pc = &c;
    pc->a(); 
//c->a() !!!这个派生类对象指针也是会通过虚函数表,不像是对象本身不会走虚函数表
//也可以这样理解就是如果还有继续继承的派生类可能就会被接下来的派生类覆盖
//因为指向的不知道是Base对象还是Derived对象
    pc->b(); //基类中有两个b()
    pc->c(); 
//c->c()是虚函数,问题点是在于A中这个函数是虚函数但是B中这个函数是普通函数,通过
//D继续继承C写一个c()函数,使用基类c指针指向d对象,发现是调用d的c函数,说明是
//是虚函数,因为如果C中的c不是一个虚函数的话,C类型指针只能访问D中的基类子对象的
//c()函数。
    pc->d(); //c->d(),隐藏
}

【补充】虚函数和普通函数是不冲突的,当通过指针调用虚函数的时候,就会使用虚表如果是本对象调用本对象中的函数,非指针的情况下即使是虚函数,拿对象调用的时候也不会走虚表。(如果是指针调用虚函数也是会走虚表)

虚函数和虚继承的问题

都是添加virtual关键字

同时也都有这样的三个特征就是存在,间接,共享

虚函数存在这个函数存在,通过虚函数指针访问,这个函数被基类和派生类共享

虚继承对于这三个特征解释:

因为这个相对而言的虚基类是存在的

通过虚基类指针获取到a

只有一份函数a

【注意】虚基类是相对而言的,a是b的虚基类,或c的

1.虚拟继承的内存结构

vbptr一个虚基类指针,其中第一个是0因为派生类对象就是在一开始所以偏移量是0,但是虚基类a是在16个字节以后所以偏移+16

在虚函数指针后面接着跟上派生类的声明的元素

2. 在虚基类中包含虚函数

因为并不是普通的继承所以说这个时候并不是共用一个虚函数指针,所以说基类的虚函数指针是被放在后面的。然后再后面再跟上基类中定义的元素。

前面是放虚基类指针和派生类中声明的元素。

3.虚拟继承,并在基类和派生类中都有定义虚函数

解释一下图的布局:

vfptr 虚函数指针 因为是虚拟继承,所以说派生类会有自己的虚函数表,而不是和基类共用一个

vbptr 虚基类指针 向前偏移8表示派生类的位置,向后偏移8是虚基类的位置。

不使用虚拟继承的时候,不会单开一张虚表

4.带虚函数的菱形继承--采用虚拟继承方式

【注意】为了解决菱形继承存储二义性,使用虚拟继承。一般没有必要使用,因为这样使得内存布局非常复杂。

首先因为这个地方d并没有虚拟继承上面的b和c所以说d并没有再另开一个虚函数表,而是直接使用b1的虚函数表,并且共有的B中的无论是虚函数表指针还是元素都被放在最后的位置。

同时派生类d的元素也是被放在虚函数表的后面,为了虚函数可以尽快地被访问。如果是这个地方还是虚拟继承,这个还是先有虚基类指针然后再有派生类的元素然后后面再跟着基类的指针等。

一共有五张表

b1的虚函数表:

虚函数指针指向的虚函数表:b1开始定义的函数,可能存在被覆盖的问题,还有D定义的虚函数因为并不是虚拟继承所以说没有单开一个虚函数表。

-8b1对象的首地址

+48来到虚基类的首地址

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

class A
{
public:
    A(double a)
    : _a(a)
    {
        cout << "A(double)" << endl;
    }

    ~A(){cout << "~A()" << endl;}
private:
    double _a = 10;
};

class B
: virtual public A
{
public:
    B(double a, double b)
    : A(a)
    , _b(b)
    {
        cout << "B(double,double)" << endl;
    }

    ~B(){ cout << "~B()" << endl; }
private:
    double _b;
};


class C
: virtual public A
{
public:
    C(double a, double c)
    : A(a)
    , _c(c)
    {
        cout << "C(double,double)" << endl;
    }

    ~C(){ cout << "~C()" << endl; }
private:
    double _c;
};

class D
: public B
, public C
{
public:
    D(double a,double b,double c,double d)
    : A(a)
    , B(a,b)
    , C(a,c)
    , _d(d)
    {
        cout << "D(double * 4)" << endl;
    }

    ~D(){ cout << "~D()" << endl; }
private:
    double _d;
};

如果没有进行bc对于a的虚拟继承,在d中就不能手动调用对于a的构造函数,因为在b和c中都已经对于a各自构造了两份,再显示定义就构造了三次了。

如果是虚拟继承的时候如果没有能够显示的定义a的构造函数并且没有

有了a的显示定义的构造函数,就会抑制b或者c的构造函数,其实就算不显示定义也会抑制

总结能够采用多继承就不要使用菱形继承,能够使用单继承就不使用多继承,不使用继承的时候效率是最高的,所以说没有特别的需求的时候尽量让结构简单

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ElementUI搭建

概述 Element&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组 件库. 安装 ElementUI npm 安装 推荐使用 npm 的方式安装&#xff0c;它能更好地和 webpack 打包工具配合使用。 npm i element-ui -S 在控制台输入此命令来安装ElementUI 在 main.j…

#03动态规划

要点&#xff1a; 动态规划方法与贪心法、分治法的异同&#xff1b; 动态规划方法的基本要素与求解步骤&#xff1b; 动态规划方法的应用。 难点&#xff1a; 如何根据问题的最优子结构性质构造构造动态规划方法中的递归公式或动态规划方程。 动态规划的基本思想 动态规…

Jetpack架构组件_Navigaiton组件_1.Navigaiton切换Fragment

1.Navigation主要作用 方便管理Fragment &#xff08;1&#xff09;方便我们管理Fragment页面的切换 &#xff08;2&#xff09;可视化的页面导航图&#xff0c;便于理清页面间的关系。 &#xff08;3&#xff09;通过destination和action完成页面间的导航 &#xff08;4&a…

基于Java+MySQL停车场车位管理系统详细设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

SSL证书类型解析:DV、OV、EV证书的区别与适用场景

在互联网时代&#xff0c;数据安全和用户隐私保护变得尤为重要。SSL证书作为加密网站通信的主要工具&#xff0c;为用户提供了一个安全的浏览环境。然而&#xff0c;面对市场上多种类型的SSL证书&#xff0c;许多网站所有者常常感到困惑。本文将重点解析三种常见的SSL证书类型—…

第二证券:突然飙升!涨超10%!“躺赚”机会又来

接近2024年上半年底&#xff0c;国债逆回购操作的“窗口期”或正在到来&#xff0c;相关国债逆回购的利率有望呈现飙升。 就在今天上午&#xff0c;沪深买卖所3天期国债逆回购利率已呈现大幅上涨&#xff0c;盘中最大涨幅均超过了10%。 国债逆回购操作“窗口期”或正在到来 …

TDengine 推出新连接器,与 Wonderware Historian 无缝连接

在最新发布的TDengine 3.2.3.0 版本中&#xff0c;我们进一步更新了 TDengine 的数据接入功能&#xff0c;推出了一款新的连接器&#xff0c;旨在实现 Wonderware Historian&#xff08;现称为 AVEVA Historian&#xff09;与 TDengine 的集成。这一更新提供了更加便捷和高效的…

Appium+python自动化(二十五)- 那些让人抓耳挠腮、揪头发和掉头发的事 - 获取控件ID(超详解)

简介 在前边的第二十二篇文章里&#xff0c;已经分享了通过获取控件的坐标点来获取点击事件的所需要的点击位置&#xff0c;那么还有没有其他方法来获取控件点击事件所需要的点击位置呢&#xff1f;答案是&#xff1a;Yes&#xff01;因为在不同的大小屏幕的手机上获取控件的坐…

零基础STM32单片机编程入门(三)中断详解及按键中断实战含源码视频

文章目录 一.概要二.可嵌套的向量中断控制器 (NVIC)三.中断向量表四.中断优先级详解五.STM32外部中断控制器(EXTI)1.EXTI简介2.EXTI在中断向量表的位置3.EXTI外部中断产生的信号流向4.EXTI中断产生后的中断服务程序 六.CubeMX配置一个GPIO输入中断的例程七.CubeMX工程源代码下载…

四步轻松搞定!探索字节最新AnimateDiff-Lightning:高质量视频生成的秘密武器!

字节前脚刚发布了文生图大模型 SDXL-Lightning&#xff0c;后脚就又对文生视频领域下手了。 就在这几天又推出了文生视频模型&#xff1a;AnimateDiff-Lightning&#xff0c;它是一种快速的文本到视频生成模型。它生成视频的速度比原始 AnimateDiff 快十倍以上&#xff0c;只需…

二、大模型原理(Transformer )

Transformer是一种基于自注意力机制&#xff08;Self-Attention Mechanism&#xff09;的深度学习模型&#xff0c;它在2017年由Vaswani等人在论文《Attention Is All You Need》中提出。Transformer模型的出现极大地推动了自然语言处理&#xff08;NLP&#xff09;领域的发展&…

浏览器扩展V3开发系列之 chrome.cookies 的用法和案例

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 chrome.cookies API能够让我们在扩展程序中去操作浏览器的cookies。 在使用 chrome.cookies 要先声明…

IDEA中使用leetcode 刷题

目录 1.IDEA下载leetcode插件 2.侧边点开插件 3.打开网页版登录找到cookie复制 4.回到IDEA登录 5.刷题 6.共勉 1.IDEA下载leetcode插件 2.侧边点开插件 3.打开网页版登录找到cookie复制 4.回到IDEA登录 5.刷题 6.共勉 算法题来了不畏惧&#xff0c; 挑战前行是成长的舞台…

中文检测程序(静态代码扫描)

欢迎您关注我们&#xff0c;经常分享有关Android出海&#xff0c;iOS出海&#xff0c;App市场政策实时更新&#xff0c;互金市场投放策略&#xff0c;最新互金新闻资讯等文章&#xff0c;期待与您共航世界之海。 在前些日子&#xff0c;给大家安利了我们在用的AS中文实时检测插…

服务器数据恢复—用raid6阵列磁盘组建raid5阵列如何恢复原raid数据?

服务器存储数据恢复环境&#xff1a; 华为OceanStor 5800存储&#xff0c;该存储中有一组由10块硬盘组建的raid6磁盘阵列&#xff0c;供企业内部使用&#xff0c;服务器安装linux操作系统EXT3文件系统&#xff0c;划分2个lun。 服务器存储故障&#xff1a; 管理员发现存储中rai…

Flask之表单

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、HTML表单 二、使用Flask-WTF处理表单 2.1、定义WTForms表单类 2.2、输出HTML代码 2.3、在模板中渲染表单 三、处理表单数据 3.1、提…

【数据结构与算法】静态查找表(顺序查找,折半查找,分块查找)详解

顺序查找、折半查找、分块查找算法适合的场合。 顺序查找&#xff1a;顺序存储或链式存储的静态查找表均可。 折半查找&#xff08;二分查找&#xff09;&#xff1a;采用顺序存储结构的有序表。 分块查找&#xff08;索引顺序查找 &#xff09;&#xff1a;分块有序表。 …

项目管理计划(DOC原件)

本文档为XXX系统项目管理计划&#xff0c;本计划的主要目的是通过本方案明确本项目的项目管理体系。方案的主要内容包括&#xff1a;明确项目的目标及工作范围&#xff0c;明确项目的组织结构和人员分工&#xff0c;确立项目的沟通环境&#xff0c;确立项目进度管理方法&#x…

【ai】trition:tritonclient yolov4:部署ubuntu18.04成功

X:\05_trition_yolov4_clients\01-python server代码在115上,client本想在windows上, 【ai】trition:tritonclient.utils.shared_memory 仅支持linux 看起来要分离。 client代码远程部署在ubuntu18.04上 ubuntu18.04 创建yolov4-trition python=3.7 环境 (base) zhangbin@ub…

MCU复位时GPIO是什么状态?

大家一定遇到过上电或者复位时外部的MOS电路或者芯片使能信号意外开启&#xff0c;至此有经验的工程师就会经常关心一个问题&#xff0c;MCU复位时GPIO是什么状态&#xff1f;什么电路需要外部加上下拉&#xff1f; MCU从上电到启动&#xff0c;实际可分为复位前和复位后、初始…