C++面向对象多态篇

目录

1. 什么是多态?

2. 多态的概念

3. 函数覆盖

4. 虚函数的定义

5. 多态实现

6. 多态的原理

7. 虚析构函数(掌握)

8. 类型转换

8.1 static_cast

8.2 dynamic_cast

8.3 const_cast(了解)

8.4 reinterpret_cast

9、抽象类(掌握)

10、纯虚析构函数(熟悉)

11、私有析构函数(熟悉)


1. 什么是多态?

在面向对象编程中,我们通常将多态分为两种类型:静态多态(静态多态,也被称为编译时多态)和动态多态(动态多态,也被称为运行时多态)。这两种多态性都是多态概念的不同表现方式。

静态多态

● 静态多态是指在编译时就能确定要调用的方法,通过函数重载和运算符重载来实现。

动态多态

● 动态多态是指在运行时根据对象的实际类型来确定要调用的函数,通过继承和函数覆盖来实现。

静态多态发生在编译时,因为在编译阶段编译器就可以确定要调用的函数。

动态多态发生在运行时,因为具体调用那个函数是在程序运行时根据实际对象的实际类型来确定的。

注:本文后续说的多态均为动态多态。

2. 多态的概念

多态可以理解为"一种接口,多种状态"只需要编写一个函数接口,根据传入的参数类型,执行不同的策略代码。

多态的使用有三个前提条件:

● 公有继承

● 函数覆盖

● 基类的引用/指针指向派生类对象

多态的优点:多态的优势包括代码的灵活性、可扩展性和可维护性更好。它能使代码更具有通用性,减少重复代码的编写,并且能够轻松的添加新的派生类或拓展现有的功能。
多态的缺点:缺点包括代码的复杂性,运行效率、不易读。当类的继承关系复杂使,理解和维护多态的代码会变得困难。多态在运行时会产生一些额外的开销。

3. 函数覆盖

函数覆盖、函数隐藏。这两个比较相似,但是函数隐藏不支持多态,而函数覆盖是多态的前提条件。函数覆盖比函数隐藏有一下几点区别:

● 函数隐藏是派生类中存在与基类中同名同参的函数,编译器会将基类的同名同参数的函数进行隐藏。

● 函数覆盖是基类中定义了一个虚函数,派生类编写一个同名同参数的函数将基类中的虚函数进行重写并覆盖。注意:覆盖的基类函数必须是虚函数。

4. 虚函数的定义

一个函数使用virtual关键字修饰,就是虚函数。虚函数是函数覆盖的前提。在Qt Creator中虚函数的函数名称使用斜体字。

#include <iostream>
using namespace std;class Animal
{
public:// 虚函数virtual void eat(){cout << "动物爱吃饭" << endl;}
};int main()
{return 0;
}
虚函数具有以下性质:

● 虚函数具有传递性,基类中的覆盖的函数是虚函数,派生类中新覆盖的函数也是虚函数。

#include <iostream>
using namespace std;class Animal
{
public:// 虚函数virtual void eat(){cout << "动物爱吃饭" << endl;}
};class Dog :public Animal
{// 覆盖基类中的虚函数,派生类的virtual关键字可写可不写void eat(){cout << "狗爱吃骨头" << endl;}
};int main()
{return 0;
}
● 只有普通成员函数与析构函数可以声明为虚函数
#include <iostream>
using namespace std;class Animal
{
public:// 错误 构造函数不能声明为虚函数
//    virtual Animal()
//    {
//        cout << "测试:构造函数虚函数" << endl;
//    }// 错误 静态函数不能为虚函数
//    virtual static void testStatic()
//    {
//        cout << "测试:静态成员虚函数" << endl;
//    }// 虚函数virtual void eat(){cout << "动物爱吃饭" << endl;}
};class Dog :public Animal
{// 覆盖基类中的虚函数,派生类的virtual关键字可写可不写void eat(){cout << "狗爱吃骨头" << endl;}
};int main()
{return 0;
}
● 在C++11中,可以在派生类的新覆盖的函数上使用override关键字验证覆盖是否成功。
#include <iostream>
using namespace std;class Animal
{
public:// 错误 构造函数不能声明为虚函数
//    virtual Animal()
//    {
//        cout << "测试:构造函数虚函数" << endl;
//    }// 错误 静态函数不能为虚函数
//    virtual static void testStatic()
//    {
//        cout << "测试:静态成员虚函数" << endl;
//    }// 虚函数virtual void eat(){cout << "动物爱吃饭" << endl;}void funHide(){cout << "测试:override关键字" << endl;}};class Dog :public Animal
{// 覆盖基类中的虚函数,派生类的virtual关键字可写可不写void eat()override{cout << "狗爱吃骨头" << endl;}// 错误,标记覆盖,但是没覆盖// 这是函数隐藏,并不是函数覆盖。// override:验证覆盖是否成功// 覆盖成功:程序正常运行// 覆盖失败:运行失败
//    void funHide()override
//    {
//        cout << "测试:override关键字" << endl;
//    }
};int main()
{return 0;
}

5. 多态实现

我们在开篇是提到过,要实现动态多态,需要有三个前提条件。

● 公有继承(已经实现)

● 函数覆盖(已经实现)

● 基类的指针/引用指向派生类对象

【思考】为什么要基类的指针指向派生类的对象那?

● 实现多态:当使用基类的指针或引用指向派生类对象时,程序在运行时会根据对象的实际类型来调用相应的函数,而不是根据指针或引用的类型。

● 统一接口:基类的指针可以作为一个通用的接口,用于操作不同类型的派生类对象。这样可以让代码更灵活,减少重复代码的编写。

#include <iostream>
using namespace std;class Animal
{
public:// 虚函数virtual void eat(){cout << "动物爱吃饭" << endl;}
};class Dog :public Animal
{
public:void eat()override{cout << "狗爱吃骨头" << endl;}
};class Cat :public Animal
{
public:void eat()override{cout << "猫爱吃鱼" << endl;}
};int main()
{Animal *a1 = new Dog;Animal *a2 = new Cat;a1->eat();  // 狗爱吃骨头a2->eat();  // 猫爱吃鱼return 0;
}
        我们也可以提供接口,参数设计为基类的指针或者是引用,这样这个函数就可以访问到此基类所有派生类的虚函数了。
#include <iostream>
using namespace std;class Animal
{
public:// 虚函数virtual void eat(){cout << "动物爱吃饭" << endl;}
};class Dog :public Animal
{
public:void eat()override{cout << "狗爱吃骨头" << endl;}
};class Cat :public Animal
{
public:void eat()override{cout << "猫爱吃鱼" << endl;}
};// 提供通用函数,形参为基类指针
void animal_eat1(Animal *a1)
{// 100 代码// 需要用到派生类的虚函数参与逻辑处理a1->eat();
}// 提供通用函数,形参为基类引用
void animal_eat2(Animal &a1)
{a1.eat();
}int main()
{Dog d1;Cat c1;animal_eat2(d1);    // 狗爱吃骨头animal_eat2(c1);    // 猫爱吃鱼Dog *d2 = new Dog;Cat *c2 = new Cat;animal_eat1(d2);    // 狗爱吃骨头animal_eat1(c2);return 0;
}

6. 多态的原理

具有虚函数的类会存在一张虚函数表,每个类的对象内部都会有一个隐藏的虚函数表指针成员,指向当前类的虚函数表。

多态实现流程

在代码运行时,通过对象的虚函数表指针找到虚函数表,在表中定位到虚函数的调用地址,从而执行对应的虚函数的内容。

7. 虚析构函数(掌握)

如果不适用虚析构函数,且基类指针或引用指向派生类对象,使用delete销毁对象时,只能触发基类的析构函数,如果在派生类中申请内存等资源,则会导致无法释放,出现内存泄漏的问题。

解决方案给基类的析构函数使用virtual修饰为虚析构函数,通过传递性可以把各个派生类的析构函数都变为虚析构函数,因此建议给一个可能为基类类中的析构函数设置成虚析构函数。

#include <iostream>
using namespace std;class Animal
{
public:// 虚析构函数virtual ~Animal(){cout << "析构函数Animal" << endl;}
};class Dog :public Animal
{
public:~Dog(){cout << "析构函数Dog" << endl;}
};int main()
{Animal *a1 = new Dog;delete a1;return 0;
}
        在上一节中除了虚析构函数,还可以使用类型转换解决内存泄漏的问题,以下时传统的类型转换写法:
#include <iostream>
using namespace std;class Animal
{
public:~Animal(){cout << "析构函数Animal" << endl;}
};class Dog :public Animal
{
public:~Dog(){cout << "析构函数Dog" << endl;}
};int main()
{Animal *a1 = new Dog;Dog* d= (Dog*)a1;delete d;return 0;
}

8. 类型转换

在 C++11中不建议使用以上C风格的类型转换,因为可能会带来一些安全隐患,让程序的错误难以发现。

C++11提供了一组适用于不同场景的强制转换函数。

● static_cast(静态转换)

● dynamic_cast(动态转换)

● const_cast(常量转换)

● reinterpret_cast(重解释转换)

8.1 static_cast

● 主要用于基本数据类型之间的转换。

#include <iostream>
using namespace std;
int main()
{int x = 1;double y = static_cast<double>(x);cout << y << endl;return 0;
}
static_cast没有运行时检查来保证转换的安全性,需要程序员手动判断转换知否安全。
#include <iostream>
using namespace std;int main()
{double x = 3.14;int y = static_cast<int>(x);cout << y << endl;return 0;
}
static_cast也可以用于类层次转换中,即基类和派生类指针或引用之间的转换。

● static_cast进行上行转换是安全的,即把派生类指针或引用转换为基类的。
● static_casti进行下行转换是不安全的,即把基类的指针或者引用转换为派生类的。

static_cast仅仅可以完成上述转换,但是不建议。

指针转换:

#include <iostream>
using namespace std;class Father
{
public:string a= "Father";
};class Son:public Father
{
public:string b = "Son";
};int main()
{// 上行转换:派生类->基类Son *s1 = new Son;Father *f1 = static_cast<Father*>(s1);cout << f1->a << endl;  // Father// 下行转换:基类->派生类Father *f2 = new Father;Son *s2 = static_cast<Son*>(f2);cout << s2->a << endl;   // Fathercout << s2->b << endl;   // 结果不定return 0;
}

引用转换:

#include <iostream>
using namespace std;class Father
{
public:string a= "Father";
};class Son:public Father
{
public:string b = "Son";
};int main()
{Son s1;Father f1 = static_cast<Father>(s1);Father &f2 = static_cast<Father&>(s1);cout << f1.a << endl;   // Fathercout << f2.a << endl;   // Fathercout << &s1 << endl;    // 0x61fe84cout << &f1 << endl;    // 0x61fe80cout << &f2 << endl;    // 0x61fe84// 下行转换:基类->派生类Father f3;
//    Son s2 = static_cast<Son>(f3); // 错误Son &s3 = static_cast<Son&>(f3);cout << s3.a << endl;   // Fathercout << s3.b << endl;   // 不合法return 0;
}

static_cast和C语言的强制类型转换相比:

● static_cast的表达式更清晰,方便管理。

● static_cast会在编译时进行类型检查。

8.2 dynamic_cast

dynamic_cast主要用于类层次的上行与下行转换

在进行上行转换时,dynamic_cast与static_cast效果相同。但是进行下行转换时,dynamic_cast会比static_cast更加安全。

关于下行转换的类型检查如下:

#include <iostream>
using namespace std;class Father
{
public:virtual void func(){cout <<"Father" << endl;}};class Son:public Father
{
public:void func(){cout << "Son" << endl;}
};int main()
{// 指针且形成多态Father *f0 = new Son;Son *s0 = dynamic_cast<Son*>(f0);f0->func(); // Sons0->func(); // Son// 指针未形成多态Father *f1 = new Father;Son* s1 = dynamic_cast<Son*>(f1);cout << f1 << " " << s1 << endl;    // 0x10127d8 0f1->func(); // Father
//    s1->func(); // 非法调用// 引用且形成多态Son s;Father &f2 = s;Son &s2 = dynamic_cast<Son&>(f2);cout << &s2 << " " << &f2 << " " <<&s << endl;  // 0x61fe74 0x61fe74 0x61fe74s2.func();  // Sonf2.func();  // Sons.func();   // SonFather f;
//    Son &s3 = dynamic_cast<Son&>(f);    // 运行终止cout << &s3 << " " << &f << endl;return 0;
}

8.3 const_cast(了解)

cosnt_cast可以添加或者移除对象的const限定符。

主要用于改变指针或引用的const效果,以便于在一定的情况下修改原本被声名为常量的对象,应该避免使用const_cast,而是考虑通过设计良好的接口或者是其他正常手段,避免需要进行此种转换。

#include <iostream>
using namespace std;class Test
{
public:string str = "A";
};int main()
{const Test* t1 = new Test;
//    t1->str = "B";Test *t2 = const_cast<Test*>(t1);t2->str = "B";cout << t1 << " " << t2 << endl;    // 0x8327c8 0x8327c8cout << t2->str << " " << t1->str << endl;  // B Breturn 0;
}

8.4 reinterpret_cast

reinterpret_cast可以把内存里的值重新解释,这种转换方式风险极高。慎用。

#include <iostream>
using namespace std;class A
{
public:void print(){cout << "A" << endl;}
};class B
{
public:void print(){cout << "B" << endl;}
};int main()
{A* a = new A;a->print();B *b  = reinterpret_cast<B*>(a);b->print();return 0;
}

9、抽象类(掌握)

如果基类指向表达一些抽象的概念,并不与实际的对象相关联。这时候就可以使用抽象类。

如果一个类中有纯虚函数,则这个类是一个抽象类。

如果一个类是抽象类,则这个类中一定有纯虚函数。

纯虚函数是虚函数的一种,这种函数只有声明没有定义。

virtual 返回值类型 函数名(参数列表) = 0;

不能直接使用抽象类作为声明类型,因为不存在抽象类类型的对象。不能创建抽象类的对象。

抽象类作为基类时,具有两种情况

● 派生类继承抽象类,覆盖并实现其所有的纯虚函数,此时派生类可以作为普通类使用,即不再是抽象类。

● 派生类继承抽象类,没有把基类的所有纯虚函数覆盖并实现,此时派生类也变为抽象类,等待他的派生类覆盖并实现剩余的纯虚函数。

#include <iostream>
using namespace std;// 抽象类:形状
class Shape
{
public:// 纯虚函数virtual void area() = 0;    // 面积virtual void perimeter() = 0;   // 周长
};// 圆形
class Circle:public Shape
{
public:// 函数覆盖并实现所有纯虚函数void area(){cout << "圆形计算面积" << endl;}void perimeter(){cout << "圆形计算周长" << endl;}
};// 多边形
class Polygon:public Shape
{
public:void perimeter(){cout << "多边形计算周长" << endl;}
};// 矩形
class Rectangle:public Polygon
{
public:void area(){cout << "矩形计算面积" << endl;}
};int main()
{
//    Shape s;    // 错误,抽象类无法实例化对象(形状类)Circle c;c.area();c.perimeter();//    Polygon p;  // 多边形类 抽象类无法实例化对象 错误Rectangle r;r.area();r.perimeter();return 0;
}
使用抽象类需要注意以下几点:

● 抽象类的析构函数必须是虚析构函数

● 抽象类支持多态,可以存在引用或者指针的声明格式。

● 因为抽象类的作用是指定算法框架。因此在一个继承体系中,抽象类的内容相对丰富且重要。

10、纯虚析构函数(熟悉)

纯虚析构函数的定义:

纯虚析构函数的本质:是析构函数,作用是各个类的回收工作。而且析构函数不能被继承。

必须为纯虚析构函数提供一个函数体。

纯虚析构函数,必须在类外实现。

#include <iostream>using namespace std;// 抽象类
class Animal
{
public:Animal(){cout << "基类的构造函数被调用了" << endl;}// 纯虚析构函数virtual ~Animal() = 0;
};// 实现
Animal::~Animal()
{cout << "基类的析构函数被调用了" << endl;
}class Dog:public Animal
{
public:Dog(){cout << "Dog类的构造函数被调用了" << endl;}~Dog(){cout << "Dog类的析构函数被调用了" << endl;}
};int main()
{
//    Animal a1;   // 错误 基类是纯虚析构函数,抽象类,无法实例化对象Animal *a1 = new Dog;delete a1;return 0;
}
虚析构函数与纯虚析构函数的区别:

虚析构函数:virtual关键字修饰,有函数体,不会导致基类为抽象类。

纯虚析构函数:virtual 关键字修饰,结果=0,函数体需要在类外实现, 会导致基类为抽象类。

11、私有析构函数(熟悉)

析构函数无法正常执行时,会引发一些对象销毁的问题。

析构函数私有化后,会出现两种情况:

1、  外部的堆内存对象只能new,无法正常delete。

2、  外部的栈内存对象无法创建。

#include <iostream>using namespace std;class Test
{
private:~Test(){}
};int main()
{Test *t1 = new Test;
//    delete t1; // 错误//    Test t2; // 错误return 0;
}

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

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

相关文章

3D打印矫形器市场报告:未来几年年复合增长率CAGR为10.8%

3D 打印矫形器是指使用 3D 打印技术制作的定制外部支撑装置。它们有助于稳定、引导、缓解或纠正肌肉骨骼状况&#xff0c;并根据个体患者的解剖结构进行设计&#xff0c;通常使用 3D 扫描和建模技术。3D 打印在矫形器方面的主要优势是能够生产精确适合患者解剖结构的定制装置&a…

sherpa-ncnn 语言模型简单对比

在昨天把系统搞崩溃前&#xff0c;对sherpa-ncnn的中文模型做了一个简单的对比。这次使用的分别是sherpa-ncnn-streaming-zipformer-bilingual-zh-en-2023-02-13&#xff08;以下简称bilingual-zh-en-2023-02-13&#xff09;和sherpa-ncnn-streaming-zipformer-small-bilingual…

STM32学习--5-1 对射式红外传感器计次

接线图 原理图&#xff1a; CountSensor.c #include "stm32f10x.h" // Device headeruint16_t CountSensor_Count;void CountSensor_Init(void) {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 开启APB2Periph外设GPIOB时钟RCC_APB2Pe…

生产报工信息化全流程大讲解

在企业的生产管理中&#xff0c;生产报工是一个关键环节&#xff0c;但传统的生产报工方式存在诸多痛点&#xff0c;制约了企业的发展。随着数字化技术的发展&#xff0c;多个平台为企业提供了有效的解决方案。基于生产报工信息化方案报告》白皮书&#xff0c;本文深入探讨生产…

复位电路的亚稳态

复位导致亚稳态的概念&#xff1a; 同步电路中&#xff0c;输入数据需要与时钟满足setup time和hold time才能进行数据的正常传输&#xff08;数据在这个时间段内必须保持不变&#xff1a;1不能变为0&#xff0c;0也不能变为1&#xff09;&#xff0c;防止亚稳态&#xff1b; …

ZStack ZROP首个商用版本发布,打造云的可持续发展框架

经过长时间的研发和测试&#xff0c;ZStack ZROP IT服务中台V4.2.0版本正式发布。ZROP 是针对ZStack全系列产品运营、运维、一体化的自研平台。作为第一个商用版本&#xff0c;ZROP V4.2.0支持包含ZStack Cloud、ZStack Cube、ZStack ZStone、ZStack Zaku、ZStack Edge、ZStack…

【隐私计算篇】使用GPU加速计算联邦学习XGBOOST算法以及对NVIDIA FLARE(NVIDIA 联邦学习应用运行环境)的介绍

1. 背景介绍 借着最近在搞GPU相关的项目契机&#xff0c;来介绍一下英伟达Nvidia FLARE项目【1】&#xff0c;并且利用GPU硬件来加速联邦学习XGBOOST算法。感觉开源的机器学习、深度学习已经开始出现拥抱隐私计算的趋势&#xff0c;比如近期我正在关注Andrew Ng的联邦学习用…

C语言 | 第十六章 | 共用体 家庭收支软件-1

P 151 结构体定义三种形式 2023/3/15 一、创建结构体和结构体变量 方式1-先定义结构体&#xff0c;然后再创建结构体变量。 struct Stu{ char *name; //姓名 int num; //学号 int age; //年龄 char group; //所在学习小组 float score; //成绩 }; struct Stu stu1, stu2; //…

STM32学习--5-2 旋转编码器计次

接线图 按键按下&#xff0c;旋转编码器输出低电平 Encoder.c #include "stm32f10x.h" // Device headerint16_t Encoder_Count; void Encoder_init(void) {RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); // 开启APB2Periph外设GPIOB时钟…

【寻找one piece的算法之路】前缀和(一)

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;寻找one piece的刷题之路 什么是前缀和&#xff1f; 主要是通过预先计算数组或矩阵的前缀和&#xff0c;来快速查询子数组或子矩阵的和。这种算法可以用空间换时间&#xff0c;提高查询效率。 概念…

leetcode 3217 从链表中移除在数组中的结点

1.题目要求: 给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后&#xff0c;返回修改后的链表的头节点。 示例 1&#xff1a; 输入&#xff1a; nums [1,2,3], head [1,2,3,4,5] 输出&#xff1a; [4,5] 解释&#xff1a; 移除数值…

PCL点云处理之求法向量

求法向量干什么&#xff1f;将点渲染成面 1、一个点垂直于一个曲线的切线叫法线 2、在点云中取一块区域&#xff0c;用最小二乘将区域中的点云拟合成一个面&#xff08;贴合在曲面上的一个切面&#xff09;在相近的区域计算出n个这样的面&#xff0c;用这个面求出法向量&#…

最新开源:智源BGE登顶Hugging Face月度榜!北大快手开源Pyramid Flow!Rhymes AI发布首款开源多模态AI模型Aria!

文章目录 1. 国产AI模型登顶全球TOP 1&#xff01;智源BGE下载破亿成Hugging Face月榜冠军2. 北大&快手开源视频生成模型Pyramid Flow&#xff0c;1分钟生成5秒视频3. Rhymes AI发布首款开源多模态AI模型Aria&#xff0c;性能超越GPT-4o mini4. Mistral AI发布 Pixtral-12B…

华为 静态路由和bfd 侦测的实验

实验要求 sw1 上业务地址192.168.1.1/24 SW3 业务地址192.168.2.1/24 正常情况下走主链路&#xff0c;不正常的情况下走备份链路 2 配置 这是基本地址配置 开启了bfd 本端地址为 10.1.1.1 对端地址是10.1.1.2 关键是discrimination 分辨参数 …

【JavaScript】LeetCode:61-65

文章目录 61 课程表62 实现Trie&#xff08;前缀树&#xff09;63 全排列64 子集65 电话号码的字母组合 61 课程表 Map BFS拓扑排序&#xff1a;将有向无环图转为线性顺序。遍历prerequisites&#xff1a;1. 数组记录每个节点的入度&#xff0c;2. 哈希表记录依赖关系。n 6&a…

基于深度学习的细粒度图像分析综述【翻译】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 基础信息0 摘要1 INTRODUCTION2 识别与检索 RECOGNITION VS. RETRIEVAL3 问题和…

牛客SQL练习详解 06:综合练习

牛客SQL练习详解 06&#xff1a;综合练习 SQL34 统计复旦用户8月练题情况SQL35 浙大不同难度题目的正确率SQL39 21年8月份练题总数 叮嘟&#xff01;这里是小啊呜的学习课程资料整理。好记性不如烂笔头&#xff0c;今天也是努力进步的一天。一起加油进阶吧&#xff01; SQL34 统…

Python100道新手练习题(附答案)

基础语法 1.打印 “Hello, World!” print("Hello, World!")2.定义一个变量并打印其值 message "Hello, Python!" print(message)3.定义两个整数变量并计算它们的和 a 5 b 3 sum a b print(sum)4.使用条件语句判断一个数是否为正数 num 10 if n…

初知C++:AVL树

文章目录 初知C&#xff1a;AVL树1.AVL树的概念2.AVL树的是实现2.1.AVL树的结构2.2.AVL树的插入2.3.旋转2.4.AVL树的查找2.5.AVL树平衡检测 初知C&#xff1a;AVL树 1.AVL树的概念 • AVL树是最先发明的自平衡⼆叉查找树&#xff0c;AVL是⼀颗空树&#xff0c;或者具备下列性…

node.js服务器基础

node.js的事件循环 node.js是基于事件驱动的&#xff0c;通常在代码中注册想要等待的事件&#xff0c;设定好回调函数&#xff0c;当事件触发的时候就会调用回调函数。如果node.js没有要处理的事件了&#xff0c;那整个就结束了;事件里面可以继续插入事件&#xff0c;如果有事…