探索多态的本质【C++】

文章目录

  • 多态的构成条件
    • 虚函数
    • 虚函数的重写(覆盖)
  • 虚函数重写的两个例外
  • C++11 override和final
  • 区分重载、覆盖(重写)、隐藏(重定义)
  • 抽象类
  • 接口继承和实现继承
  • 多态的原理
    • 虚函数表
  • 动态绑定和静态绑定
    • 动态绑定
    • 静态绑定
  • 单继承中的虚函数表
  • 多继承中的虚函数表
  • 菱形继承、菱形虚拟继承

多态的构成条件

在继承中要构成多态还有两个条件:
1、必须通过基类的指针或者引用调用虚函数。

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

//父类
class Person
{
public://父类的虚函数virtual void BuyTicket() const{cout << "买票-全价" << endl;}
};//子类
class Student : public Person
{
public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket() const{cout << "买票-半价" << endl;}
};引用
//void func(const Person &  p)
//{
//	p.BuyTicket();
//}//指针
void func(const Person *  p)
{p->BuyTicket();
}int main()
{    //多态条件// 1、调用函数必须是重写的虚函数//基类必须是指针或者引用//多态,不同对象传递过去,调用不同参数//多态调用看指向的对象//普通对象,看当前类型//引用/*func(Person());func(Student());*///指针Person pp;func(&pp);Student st;func(&st);return 0;
}

虚函数

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

class A
{
public:virtual void func(){cout << "virtual void func() " << endl;}
};

注意:
只有类的非静态成员函数前可以加virtual,普通函数前不能加virtual

虚函数这里的virtual和虚继承中的virtual虽然是同一个关键字,但是它们之间没有任何关系。

虚函数的virtual是为了实现多态
虚继承的virtual是为了解决菱形继承的数据冗余和二义性。

虚函数的重写(覆盖)

如果派生类中有一个和基类完全相同的虚函数(返回值类型相同、函数名相同以及参数列表完全相同,这里所说的参数列表是指参数类型要相同)
此时我们称该派生类的虚函数重写了基类的虚函数。

//父类
class Person
{
public://父类的虚函数virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
//子类
class Student : public Person
{
public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket(){cout << "买票-半价" << endl;}
};
//子类
class Soldier : public Person
{
public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket(){cout << "优先-买票" << endl;}
};

通过父类Person的指针或者引用调用虚函数BuyTicket,此时不同类型的对象,调用的就是不同的函数,产生的也是不同的结果,进而实现了函数调用的多种形态。

void Func(Person& p)
{//通过父类的引用调用虚函数p.BuyTicket();
}
void Func(Person* p)
{//通过父类的指针调用虚函数p->BuyTicket();
}
int main()
{Person p;   //普通人Student st; //学生Soldier sd; //军人Func(p);  //买票-全价Func(st); //买票-半价Func(sd); //优先买票Func(&p);  //买票-全价Func(&st); //买票-半价Func(&sd); //优先买票return 0;
}

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

虚函数重写的两个例外

协变(基类与派生类虚函数的返回值类型不同)

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

//基类
class A
{};
//派生类
class B : public A
{};//基类
class Person
{
public://虚函数virtual  A* fun(){cout << "A* Person::f()" << endl;return new A;}
};
//派生类
class Student : public Person
{
public:// 虚函数virtual  B * fun(){cout << "B* Person::f()" << endl;return new B;}};
int main()
{Person p;Student st;//基类指针指向基类对象Person* ptr1 = &p;//基类指针指向子类对象Person* ptr2 = &st; //切片//父类指针ptr1指向的p是父类对象,调用父类的虚函数ptr1->fun();//父类指针ptr2指向的st是子类对象,调用子类的虚函数ptr2->fun();return 0; 
}

析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。

//析构函数加上virtual ,是虚函数重写,为什么?
// 因为析构函数都被处理成了destructor这个统一的名字,为什么统一处理成destructor?
//因为统一处理成destructor,是要将派生类和基类的析构函数构成重写,而重写是构成多态的一个重要条件
class Person
{
public:virtual  ~Person(){cout << "~Person()" << endl;}
};
//子类
class Student : public Person
{
public:virtual  ~Student(){cout << "~Student()" << endl;delete[]ptr;}
protected :	int* ptr = new int[10];
};int main()
{//Person p;//Student s;//析构顺序:先子后父Person* p = new Person;delete p;p = new Student;delete p;//p->destuctor() +  operator delete (p)//这里我们希望p->destuctor()是一个多态调用 ,而不是普通调用return 0;
}

在继承当中,子类和的析构函数和父类的析构函数构成隐藏的原因就在这里,这里表面上看子类的析构函数和父类的析构函数的函数名不同,但是为了构成重写,编译后析构函数的名字会被统一处理成destructor();

C++11 override和final

C++11提供了override和final两个关键字,可以帮
助用户检测是否重写。

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

//基类
class Person
{
public://虚函数//final:修饰虚函数,表示该虚函数不能再被重写virtual void BuyTicket() final{cout << "买票-全价" << endl;}
};//派生类
class Student : public Person
{
public:virtual void BuyTicket()//err{cout << "买票-半价" << endl;}
};

override:检查派生类虚函数是否重写了基类的某个虚函数,如果没有重写则编译报错

//基类
class Person
{
public://虚函数virtual void BuyTicket() {cout << "买票-全价" << endl;}
};//派生类
class Student : public Person
{
public://override,派生类完成基类的重写,就不报错virtual  void BuyTicket() override{cout << "买票-半价" << endl;}
};

区分重载、覆盖(重写)、隐藏(重定义)

在这里插入图片描述

抽象类

在虚函数的后面写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。

class Car
{
public://纯虚函数virtual void Drive() = 0;
};
int main()
{Car c; //errreturn 0; 
}

派生类继承抽象类后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。

//抽象类class Car
{
public://纯虚函数virtual void Drive() = 0;
};//派生类继承抽象类class Benz : public Car
{
public://重写纯虚函数virtual void Drive(){cout << "Benz-舒适" << endl;}
};int main()
{//派生类重写了纯虚函数,可以实例化对象Benz b1;Car* p1 = &b1;p1->Drive();return 0;
}

抽象类不能实例化出对象,那抽象类存在的意义是什么?

抽象类体现了虚函数的继承是一种接口继承,强制子类去重写纯虚函数,因为子类若是不重写从父类继承下来的纯虚函数,那么子类也是抽象类也不能实例化出对象。

接口继承和实现继承

实现继承: 普通函数的继承是一种实现继承,派生类继承了基类函数的实现,可以使用该函数。

接口继承: 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态。

建议: 所以如果不实现多态,就不要把函数定义成虚函数。

多态的原理

虚函数表

看下面的代码

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main()
{Base b;cout << sizeof(b) << endl;return 0; 
}

b对象当中除了_b成员外,实际上还有一个_vfptr(虚函数表指针简称虚表指针)放在对象的前面(有些平台可能会放到对象的最后面,这个跟平台有关)。虚函数
的地址要被放到虚函数表中,虚函数表也简称虚表
在这里插入图片描述

#include <iostream>
using namespace std;
//父类
class Base
{
public://虚函数virtual void Func1(){cout << "Base::Func1()" << endl;}//虚函数virtual void Func2(){cout << "Base::Func2()" << endl;}//普通成员函数void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
//子类
class Derive : public Base
{
public://重写虚函数Func1virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}

通过观察,我们发现:
基类对象b和基类对象d当中除了自己的成员变量之外,基类和派生类对象都有一个虚表指针,分别指向属于自己的虚表。
在这里插入图片描述
实际上虚表当中存储的就是虚函数的地址

派生类虽然继承了基类的虚函数Func1和Func2,但是派生类对基类的虚函数Func1进行了重写。所以,派生类对象d的虚表当中存储的是基类的虚函数Func2的地址和重写的Func1的地址。这就是为什么虚函数的重写也叫做覆盖,覆盖就是指虚表中虚函数地址的覆盖,重写是语法的叫法,覆盖是原理层的叫法。

注意:Func2是虚函数,所以继承下来后放进了子类的虚表,而Func3是普通成员函数,继承下来后不会放进子类的虚表。并且,虚函数表本质是一个存虚函数指针的指针数组,一般情况下会在这个数组最后放一个nullptr。

总结:派生类的虚表生成步骤如下
1、先将基类中的虚表内容拷贝一份到派生类的虚表

2、如果派生类重写了基类中的某个虚函数,则用派生类自己的虚函数地址覆盖虚表中基类的虚函数地址

3、派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后

虚表是什么阶段初始化的?虚函数存在哪里?
虚表实际上是在构造函数初始化列表阶段进行初始化的,注意虚表当中存的是虚函数的地址不是虚函数,虚函数和普通函数一样,都是存在代码段的,只是他的地址又存到了虚表当中。另外,对象中存的不是虚表而是指向虚表的指针。

那虚表是存在哪里的?

class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}virtual void Func1() { }virtual void Func2() { }
//protected:int _a = 0;
};
class Student : public Person
{
public://派生类重写基类虚函数virtual void BuyTicket(){cout << "买票-半价" << endl;}virtual void Func3() { }
protected:int _b = 1;
};
int main()
{Person ps;Student st; //栈int a = 0;printf("栈:%p\n", &a);//静态区static  int b = 0;printf("静态区:%p\n", &b);//堆int* p = new int;printf("堆:%p\n", p);const char* str = "hello world";printf("常量区(代码段):%p\n", str);printf("虚表1:%p\n",*(   (int*)&ps ) );printf("虚表2:%p\n", *( (int*) &st ) );return  0; 
}

从上述代码可以发现虚表地址与代码段的地址非常接近,由此可以得出虚表是存在代码段的

详细分析下面的代码
为什么当父类Person指针指向的是父类对象Mike时,调用的就是父类的BuyTicket?
当父类Person指针指向的是子类对象Johnson时,调用的就是子类的BuyTicket?

class Person 
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}int _a = 1;
};class Student : public Person 
{
public://派生类重写基类虚函数virtual void BuyTicket() { cout << "买票-半价" << endl;}int _b = 1;
};int main()
{Person Mike;Student Johnson;Johnson._b = 3; //以便观察是否完成切片Person* p1 = &Mike;Person* p2 = &Johnson;p1->BuyTicket(); //买票-全价p2->BuyTicket(); //买票-半价return 0;
}

对象Mike中包含一个成员变量_a和一个虚表指针,对象Johnson中包含两个成员变量_a和_b以及一个虚表指针,这两个对象当中的虚表指针分别指向自己的虚表。

在这里插入图片描述

通过上图可分析:
1、父类指针p1指向Mike对象,p1->BuyTicket在Mike的虚表中找到的虚函数就是Person::BuyTicket。

2、父类指针p2指向Johnson对象,p2>BuyTicket在Johnson的虚表中找到的虚函数就是Student::BuyTicket。

这样就实现出了不同对象去完成同一行为时,展现出不同的形态,即多态

多态构成的两个条件,
1、完成虚函数的重写,
2、必须使用父类的指针或者引用去调用虚函数。
完成虚函数的重写是因为需要完成子类虚表当中虚函数地址的覆盖,这样才能做到指针指向父类,调用父类对象,指针指向子类,调用子类对象

为什么多态的设计必须使用父类的指针或者引用,不使用父类的对象?
指针和引用的切片不存在拷贝问题 ,但是对象的切片需要拷贝
子类赋值给父类对象切片,不会拷贝虚表,如果拷贝虚表,那么父类对象虚表中是父类虚函数还是子类虚函数就不确定了

在这里插入图片描述

使用父类的指针或者引用时,实际上是一种切片行为,切片时只会让父类指针或者引用得到父类对象或子类对象中切出来的那一部分。

在这里插入图片描述
用p1和p2调用虚函数时,p1和p2通过虚表指针找到的虚表是不一样的,最终调用的函数也是不一样的。

Person p1 = Mike;
Person p2 = Johnson;

使用父类对象时,切片得到部分成员变量后,会调用父类的拷贝构造函数对那部分成员变量进行拷贝构造,而拷贝构造出来的父类对象p1和p2当中的虚表指针指向的都是父类对象的虚表。因为同类型的对象共享一张虚表,他们的虚表指针指向的虚表是一样的。

总结:

对象的调用:
如果是普通对象的调用(不符合多态),看调用者的类型,普通对象的调用在编译时就确定好了地址
如果调用符合多态,看指向的对象,在运行时到指向对象的虚函数表中找调用函数的地址从而完成调用

动态绑定和静态绑定

动态绑定

动态绑定又称为后期绑定(晚绑定),在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

静态绑定

静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也成为静态多态,比如:函数重载

普通对象的调用

//基类
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};//派生类
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};int main()
{Student st;Person p = st;//不构成多态,函数的调用是在编译时就确定的p.BuyTicket();return 0;
}

在这里插入图片描述
将调用函数的那句代码翻译成汇编就只有以上两条汇编指令,也就是直接调用的函数。

使用多态调用

//基类
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
protected:int _a = 0;
};//派生类
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
protected:int _b = 1;
};int main()
{Student st;Person & p = st;//构成多态,看指向的对象p.BuyTicket();return 0;
}

在这里插入图片描述
构成多态时调用函数的那句代码翻译成汇编后就变成了八条汇编指令,原因就是我们需要在运行时,先到指定对象的虚表中找到要调用的虚函数,然后才能进行函数的调用。

体现了静态绑定是在编译时确定的,而动态绑定是在运行时确定的。

单继承中的虚函数表

//基类
class Base
{
public:virtual void func1() { cout << "Base::func1()" << endl;}virtual void func2() {cout << "Base::func2()" << endl; }
private:int _a = 0 ;
};
//派生类
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 =1 ;
};int main()
{Base b;Derive d;return 0;
}

派生类和基类的内存分布

在这里插入图片描述
单继承关系当中,派生类的虚表生成过程如下:

1、继承基类的虚表内容到派生类的虚表。
2、对派生类重写了的虚函数地址进行覆盖,比如func1。
3、虚表当中新增派生类当中新的虚函数地址,比如func3和func4。

在调试过程中,某些编译器的监视窗口当中看不到虚表当中的func3和func4,可能是编译器的监视窗口故意隐藏了这两个函数,也可以认为这是一个小bug,此时想要看到派生类对象完整的虚表有两个方法。

使用内存监视窗口
在这里插入图片描述
使用代码打印虚表内容

typedef void(*VFPTR)(); //虚函数指针类型重命名
//打印虚表地址及其内容
void PrintVFT(VFPTR* ptr)
{printf("虚表地址:%p\n", ptr);for (int i = 0; ptr[i] != nullptr; i++){printf("ptr[%d]:%p-->", i, ptr[i]); //打印虚表当中的虚函数地址ptr[i](); //使用虚函数地址调用虚函数}printf("\n");
}
int main()
{Base b;PrintVFT((VFPTR*)(*(int*)&b)); //打印基类对象b的虚表地址及其内容Derive d;PrintVFT((VFPTR*)(*(int*)&d)); //打印派生类对象d的虚表地址及其内容return 0;
}

多继承中的虚函数表

//基类1
class Base1
{
public:virtual void func1() { cout << "Base1::func1()" << endl; }virtual void func2() {cout << "Base1::func2()" << endl;}
private:int _b1;
};//基类2
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()
{Base1 b1;Base2 b2;Derive d;return 0;
}

在这里插入图片描述
多继承中,派生类的虚表生成过程如下:

1、分别继承各个基类的虚表内容到派生类的各个虚表当中。
2、对派生类重写了的虚函数地址进行覆盖(派生类中的各个虚表中存有该被重写虚函数地址的都需要进行覆盖),比如func1。
3、在派生类第一个继承基类部分的虚表当中新增派生类当中新的虚函数地址,比如func3。

看到派生类对象完整的虚表有两种方法。
一、使用内存监视窗口

在这里插入图片描述

二、使用代码打印虚表内容
在派生类第一个虚表地址的基础上,向后移sizeof(Base1)个字节即可得到第二个虚表的地址。

typedef void(*VFPTR)(); //虚函数指针类型重命名
//打印虚表地址及其内容
void PrintVFT(VFPTR* ptr)
{printf("虚表地址:%p\n", ptr);for (int i = 0; ptr[i] != nullptr; i++){printf("ptr[%d]:%p-->", i, ptr[i]); //打印虚表当中的虚函数地址ptr[i](); //使用虚函数地址调用虚函数}printf("\n");
}
int main()
{Base1 b1;Base2 b2;PrintVFT((VFPTR*)(*(int*)&b1)); //打印基类对象b1的虚表地址及其内容PrintVFT((VFPTR*)(*(int*)&b2)); //打印基类对象b2的虚表地址及其内容Derive d;PrintVFT((VFPTR*)(*(int*)&d)); //打印派生类对象d的第一个虚表地址及其内容PrintVFT((VFPTR*)(*(int*)((char*)&d + sizeof(Base1)))); //打印派生类对象d的第二个虚表地址及其内容return 0;
}

菱形继承、菱形虚拟继承

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频监控平台EasyCVR分组批量绑定/取消通道功能的后端代码设计逻辑介绍

视频监控平台/视频存储/视频分析平台EasyCVR基于云边端一体化管理&#xff0c;可支持视频实时监控、云端录像、云存储、磁盘阵列存储、回放与检索、智能告警、平台级联等功能。安防监控平台在线下场景中应用广泛&#xff0c;包括智慧工地、智慧工厂、智慧校园、智慧社区等等。 …

Ubuntu20.04同时安装ROS1和ROS2

Ubuntu20.04同时安装ROS1和ROS2 Excerpt 每版的Ubuntu系统版本都有与之对应ROS版本的&#xff0c;每一版ROS都有其对应版本的Ubuntu版本&#xff0c;不可随便装&#xff0c;ubuntu20.04对应ROS1 noetic和ROS2 foxy版本。_ros1和ros2共存 Ubuntu20.04同时安装ROS1和ROS2共存 文…

Vue+NodeJS+MongoDB实现邮箱验证注册、登录

一.主要内容 邮件发送用户注册用户信息存储到数据库用户登录密码加密JWT生成tokenCookie实现快速登录 在用户注册时,先发送邮件得到验证码.后端将验证进行缓存比对,如果验证码到期,比对不正确,拒绝登录;如果比对正确,将用户的信息进行加密存储到数据库. 用户登录时,先通过用…

[H5动画制作系列] Sprite及Text Demo

参考代码: sprite.js: var canvas, stage, container; canvas document.getElementById("mainView"); function init() {stage new createjs.Stage(canvas);createjs.Touch.enable(stage);var loader new createjs.LoadQueue(false);loader.addEventListener(&q…

Docker的开源容器镜像仓库Harbor安装

概述 Docker Hub是Docker官方提供的在线Docker镜像注册中心&#xff0c;其支持Docker镜像的查询&#xff08;search&#xff09;、提交&#xff08;push&#xff09;以及获取&#xff08;pull&#xff09;。目前&#xff0c;在云原生领域中&#xff0c;CNCF提供Harbor开源版本…

创建开机自启的脚本

在启动许多ros节点时有多种方式&#xff0c;我推荐使用launch来启动所有的节点&#xff0c;这也是一种规范的方式。以后会慢慢向这个方向靠。 除此之外还可以通过创建的脚本来启动&#xff1a; 脚本位置不限&#xff0c;只需要&#xff1a; sudo gedit xxx.sh在里面添加相应的…

UI设计师的发展前景是否超越了平面设计?

这是一个现代经济学的典型话题&#xff1a;应该跟随趋势追逐风口&#xff0c;还是坚守成熟的“夕阳产业” UI 设计行业发展短短不过 20 多年&#xff0c;但平面设计这个“夕阳产业”最早可以追溯到上世纪的二三十年代。显而易见的答案是&#xff0c;更新兴的 UI 设计师得到的好…

C语言_指针(1)

文章目录 前言一、指针数组1.1利用指针数组模拟出二维数组 二、数组指针2.1数组名是数组首元素的地址2.2 二维数组传参2.3 一级指针传参2.4 二级指针传参 三. 函数指针四 . typedef 重命名 前言 指针数组是由指针组成的数组。它的每个元素都是一个指针&#xff0c;可以指向任何…

java并发编程 ConcurrentLinkedQueue详解

文章目录 1 ConcurrentLinkedQueue是什么2 核心属性详解3 核心方法详解3.1 add(E e)3.2 offer(E e)3.3 poll()3.4 size()3.5 并发情况分析 4 总结 1 ConcurrentLinkedQueue是什么 ConcurrentLinkedQueue是一个无界的并发队列&#xff0c;和LinkedBlockingQueue相比&#xff0c…

对象临时中间状态的条件竞争覆盖

Portswigger练兵场之条件竞争 &#x1f984;条件竞争之对象临时中间状态的条件竞争 Lab: Partial construction race conditions&#x1f680;实验前置必要知识点 某些框架尝试通过使用某种形式的请求锁定来防止意外的数据损坏。例如&#xff0c;PHP 的本机会话处理程序模块…

【echarts】如何修改折线图X轴每个刻度的间隔宽度,让拥挤的空间变大,所有坐标点的文案可以显示得下,Echarts x轴文本内容太长的几种解决方案

Echarts 如何修改折线图X轴每个刻度的间隔宽度&#xff0c;让拥挤的空间变大&#xff0c;所有坐标点的文案可以显示得下&#xff0c;Echarts x轴文本内容太长的几种解决方案 有以下几种方案&#xff0c;堪称最全方案&#xff1a; 1、dataZoom进行坐标的比例缩放 通过调整dataZ…

uniapp 在 onLoad 事件中 this.$refs 娶不到的问题

现象 本人想在主页面加载的时候调用子组件的方法。示例代码如下&#xff1a; 运行&#xff0c;发现 this.$refs 取不到。如下图所示&#xff1a; 解决方法&#xff0c;把onLoad 换为 onReady 就可以了。

一笑的大型连续剧之第二集

开场白 各位小伙伴们大家晚上好&#xff0c;今天来和大家一起更新一下我的开发之旅的第二集。上周时间也已经匆匆过去了。今天也是周六晚上了&#xff0c;这个周末很充实但是又很空虚。 本周小结 本周完成了我开发旅途中的第一个模块&#xff0c;关于绩效面谈的一个模块的一…

亚马逊云科技与伊克罗德推出AI绘画解决方案——imAgine

在过去的数月中&#xff0c;亚马逊云科技已经推出了多篇介绍如何在亚马逊云科技上部署Stable Diffusion&#xff0c;或是如何结合Amazon SageMaker与Stable Diffusion进行模型训练和推理任务的内容。 为了帮助客户快速、安全地在亚马逊云科技上构建、部署和管理应用程序&#x…

Docker部署EMQX

1、简介 EMQ X (Erlang/Enterprise/Elastic MQTT Broker) 是基于 Erlang/OTP 平台开发的开源物联网 MQTT 消息服务器。 Erlang/OTP是出色的软实时 (Soft-Realtime)、低延时 (Low-Latency)、分布式 (Distributed)的语言平台。 MQTT 是轻量的 (Lightweight)、发布订阅模式 (Pu…

简明SQL条件查询指南:掌握WHERE实现数据筛选

条件查询是用于从数据库中根据特定条件筛选数据行的一种方式&#xff0c;它避免了检索整个表中的数据。通常&#xff0c;使用 WHERE 子句来定义过滤条件&#xff0c;只有符合这些条件的数据行才会被返回。 SQL中的运算符有&#xff1a;、!、<、> 等&#xff0c;用于进行…

Swift页面添加水印

本文主要讨论的是给图片或者视图添加全屏水印。比较常见的是添加单个水印,这个比较好处理,网络上也有很多参考的方法。本文实现的是铺满的全屏水印,具体参考效果如下: 实现思路: 1、根据水印文本以及相应样式生成水印图片,水印图大小根据文本计算而来 2、生成需要铺满水…

Java中如何获取一个字符串是什么类型

Java中如何获取一个字符串是什么类型&#xff1f; 在Java中&#xff0c;您可以使用一些方法来确定一个字符串的类型。下面是一些常用的方法&#xff1a; 使用正则表达式&#xff1a;您可以使用正则表达式来匹配字符串是否符合特定的模式或格式&#xff0c;以确定其类型。例如&…

nginx空字节漏洞复现

将nginx复制到C盘根目录 cmd运行startup.bat 安装完成后访问 输入info.php 输入info.png 抓包使用00截断 可以看到phpinfo成功执行 在PHP的底层C语言里&#xff0c;%00代表着字符串结束&#xff0c;00截断可以用来绕过后端验证&#xff0c;后端验证的时候因为00截断认为文件是…

C++中的红黑树

红黑树 搜索二叉树搜索二叉树的模拟实现平衡搜索二叉树(AVL Tree)平衡搜索二叉树的模拟实现红黑树(Red Black Tree)红黑树的模拟实现 红黑树的应用(Map 和 Set)Map和Set的封装 搜索二叉树 搜索二叉树的概念&#xff1a;二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&…