C++程序诗篇的灵动赋形:多态

文章目录

  • 1.什么是多态?
  • 2.多态的语法实现
    • 2.1 虚函数
    • 2.2 多态的构成
    • 2.3 虚函数的重写
      • 2.3.1 协变
      • 2.3.2 析构函数的重写
    • 2.4 override 和 final
  • 3.抽象类
  • 4.多态原理
    • 4.1 虚函数表
    • 4.2 多态原理实现
    • 4.3 动态绑定与静态绑定
  • 5.继承和多态常见的面试问题
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

本篇将开启 C++ 三大特性中的多态篇章,多态允许你以统一的方式处理不同类型的对象,通过相同的接口来调用不同的实现方法。这意味着你可以编写通用的代码,而这些代码可以在运行时根据对象的实际类型来执行特定的操作

1.什么是多态?

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

✏️举个例子:

比如买高铁票的时候,我们都属于 Person 类,买的时候会显示为全价,那么我们又属于 Student 类,继承于 Person 类,这时买的时候又会显示为半价,假设两个类都有 BuyTicket 函数,那么相同的函数在继承的基础上,能够实现不同的功能,这就是多态

2.多态的语法实现

2.1 虚函数

class Person 
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

virtual 修饰的类成员函数称为虚函数,注意这里和菱形虚拟继承的 virtual 没有关系,不过使用了同一个关键字而已

🔥值得注意的是:

  • 内联函数一般不能是虚函数。内联函数是在编译时将函数体插入到调用处,而虚函数是在运行时进行动态绑定的,两者特性冲突
  • 静态成员不可以是虚函数,虚函数是通过对象的虚函数表指针来实现动态绑定的,也就是在运行时根据对象的实际类型来确定调用哪个虚函数。而静态成员函数是属于类的,不依赖于具体对象,没有对象的概念,也没有虚函数表指针,无法通过动态绑定来调用
  • 构造函数不可以是虚函数,对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

2.2 多态的构成

虚函数是实现多态的重要组成部分,将上面举的例子以代码形式实现如下:

class Person 
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};class Student : public Person 
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为,比如 Student 继承了 PersonPerson 对象买票全价,Student 对象买票半价

那么在继承中要构成多态还有两个条件:

  1. 必须通过父类的指针或者引用调用虚函数

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

🔥值得注意的是: 多态构成条件缺一不可,如果多态产生问题,子类没有对某个方法进行重写,那么子类对象在调用该方法时,就会沿着继承链向上查找,找到父类中对应的方法并调用

2.3 虚函数的重写

class Person 
{
public:virtual void BuyTicket() { cout << "买票全价" << endl; }
};class Student : public Person 
{
public:virtual void BuyTicket() { cout << "买票半价" << endl; }
};void Func(Person& people)
{people.BuyTicket();
}int main()
{Person Mike;Func(Mike);Student Johnson;Func(Johnson);return 0;
}

Person 类的 BuyTicketStudent 类的 BuyTicket 构成重写

虚函数的重写: 又叫覆盖,派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型函数名字参数列表完全相同),称子类的虚函数重写了基类的虚函数

在这里插入图片描述

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

2.3.1 协变

class A {};
class B : public A {};class Person 
{
public:virtual A* f() { return new A; }
};class Student : public Person 
{
public:virtual B* f() { return new B; }
};

协变是重写的一种特殊情况,简单来说协变就是派生类重写基类虚函数时,与基类虚函数返回值类型不同,且要求父类虚函数类型和子类虚函数类型必须是父子关系的引用和指针

🔥值得注意的是: 必须都是引用或者都是指针,不能一个是引用一个是指针

2.3.2 析构函数的重写

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 = new Person;delete p;p = new Student;delete p;return 0;
}

这里单纯讲解很难理解,所以以一段代码场景+一些提问来解析:

🚩析构函数+virtual,是不是虚函数重写?

是,虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成 destructor

🚩为什么要处理成统一名字?

因为要让两个析构函数构成重写

🚩为什么要让他们构成重写?

假设我们上面的这个代码没有加 virtual,运行代码如下:

在这里插入图片描述

观察可以发现子类 Student 部分没有得到释放,那么 ptr 指向的空间就会造成内存泄漏

根据 C++ 内存管理学的知识可知

p -> destructor() + operator delete

这里只能调用 p 这个类型的析构函数,但是我们为了实现能够调用指向空间的析构函数,期望是个多态调用,而不是普通调用,所以必须让这两个析构函数构成重写

🔥值得注意的是:

  • 当使用父类指针指向子类对象,析构该指针时,如果父类的析构函数不是虚函数,那么将按指针本身的类型(即父类)来析构。这可能会导致子类部分的资源没有被正确释放,产生内存泄漏等问题

  • 如果父类的析构函数是虚函数,那么会按照指针实际指向的对象类型(即子类)来析构

2.4 override 和 final

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

class Car
{
public:virtual void Drive() final {}
};class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};

🔥值得注意的是:

假设有个 A 类和 B 类,不想让 B 类继承 A 类,那么可以写做:class A final,避免 A 类被继承,这是 C++11 才支持的,在这之前使用的是将 A 的构造函数私有化的方法

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

class Car 
{
public:virtual void Drive() {}
};class Benz :public Car 
{
public:virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

3.抽象类

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{Car* pBenz = new Benz;pBenz->Drive();//访问Benz的虚函数Car* pBMW = new BMW;pBMW->Drive();//访问BMW的虚函数
}

在虚函数的后面写上 = 0 ,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类

抽象类不能实例化出对象,即只要有纯虚函数就不能实例化出对象,派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承

🔥值得注意的是:

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数

4.多态原理

4.1 虚函数表

✏️以下我们通过多个例子进行详细解析:

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

sizeof(Base)是多少?

想必大部分人第一次做这道题都会觉得是 1,但运行后发现答案是 8

很奇怪,所以我们转到调试查看

在这里插入图片描述

发现除了 _b 以外,还多一个 _vfptr 放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针( v 代表 virtualf 代表 function)

通常虚函数都被放在代码段_vfptr 就是虚函数的地址,被存放在虚函数表,虚函数表放在只读数据段,也就是常量区,所以虚函数表本质上是个函数指针数组,虚函数表是在编译期间生成的

✏️那么多个虚函数是怎样实现多态的,举个例子:

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:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}

还是转到监视窗口调试查看:

在这里插入图片描述

实际上虚函数表是按照一定规则实现的:

  • 🚩复制基类虚表内容
    子类在生成虚表时,首先会把父类虚表中的内容完整地复制一份。这意味着子类虚表初始状态下包含了基类所有虚函数的地址,保证了子类对象可以调用父类的虚函数,这是因为子类继承了基类的接口,在某些情况下可能会使用到基类定义的虚函数实现

  • 🚩重写虚函数的替换
    如果子类对父类中的某个虚函数进行了重写,那么在子类虚表中,对应父类虚函数的地址会被替换为子类自己重写后的虚函数地址。当通过父类指针或引用调用该虚函数时,程序会根据对象的实际类型(即子类类型),从子类虚表中找到并重写后的虚函数来执行,从而实现多态性

  • 🚩新增虚函数的添加
    对于子类自己新定义的虚函数,会按照它们在子类中声明的先后顺序依次添加到子类虚表的末尾。这些新增的虚函数是子类特有的,父类中并不存在。因此,它们会被单独添加到虚表中,以确保子类对象能够调用这些专属的虚函数

🔥值得注意的是:

  • 父类 b 对象和子类 d 对象虚表是不一样的,这里我们发现 Func1 完成了重写,所以 d 的虚表中存的是重写的 Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法
  • Func2 继承下来后是虚函数,所以放进了虚表,Func3 也继承下来了,但是不是虚函数,所以不会放进虚表
  • 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个 nullptr
  • 一个类的不同对象共享同一个类的虚表

4.2 多态原理实现

那么回归到多态的实现条件:

  1. 必须通过父类的指针或者引用调用虚函数

  2. 被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写

我们可以提出两个问题:

🚩为什么不是子类指针或者引用?

class Animal 
{
public:virtual void speak() {cout << "Animal makes a sound" << endl;}
};class Dog : public Animal 
{
public:void speak() override {cout << "Dog barks" << endl;}
};class Cat : public Animal 
{
public:void speak() override {cout << "Cat meows" << endl;}
};int main() {Dog dog;Animal* animalPtr = &dog;  // 父类指针指向子类对象animalPtr->speak();  // 运行时根据实际对象类型调用Dog的speak函数Cat cat;Animal& animalRef = cat;  // 父类引用绑定到子类对象animalRef.speak();  // 运行时根据实际对象类型调用Cat的speak函数return 0;
}

这里的子类 DogCat 都继承于父类 Animal,就是因为是父类的指针或引用才能想调用哪个子类都行

如果是子类的指针或引用,比如有个 Dog 类的指针 Dog* dogPtr,它只能指向 Dog 类对象,没办法指向 Cat 类对象。如果想用它去调用 speak 函数,不管怎样都是调用 Dog 类的 speak 函数,不能根据实际对象类型(Cat 或其他子类)来动态调用不同的 speak 函数,就实现不了多态了

🚩为什么不能是父类对象?

class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};int main()
{Person ps;Student st;ps = st;return 0;
}

如果是使用对象,而不是指针或引用,子类中特有的成员变量和函数将被截断,丢失子类的特性

而使用父类指针或引用指向子类对象时,不会发生切片,能够完整保留子类对象的所有信息,从而可以访问子类重写的虚函数以实现多态

🔥值得注意的是: 子类对象赋值给父类对象的时候,不会拷贝虚函数表过去,如果拷贝了,那么父类虚函数表中的虚函数就变成子类虚函数了,就失去多态的意义了

在这里插入图片描述

所以总结: 满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中去找的。不满足多态的函数调用时编译时确认好的

4.3 动态绑定与静态绑定

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

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

5.继承和多态常见的面试问题

  1. 下面哪种面向对象的方法可以让你变得富有( )
    A: 继承
    B: 封装
    C: 多态
    D: 抽象
  2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,
    而对方法的调用则可以关联于具体的对象。
    A: 继承
    B: 模板
    C: 对象的自身引用
    D: 动态绑定
  3. 面向对象设计中的继承和组合,下面说法错误的是?()
    A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复用,也称为白盒复用
    B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动态复用,也称为黑盒复用
    C:优先使用继承,而不是组合,是面向对象设计的第二原则
    D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封装性的表现
  4. 以下关于纯虚函数的说法,正确的是( )
    A:声明纯虚函数的类不能实例化对象
    B:声明纯虚函数的类是虚基类
    C:子类必须实现基类的纯虚函数
    D:纯虚函数必须是空函数
  5. 关于虚函数的描述正确的是( )
    A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型
    B:内联函数不能是虚函数
    C:派生类必须重新定义基类的虚函数
    D:虚函数可以是一个static型的函数
  6. 关于虚表说法正确的是( )
    A:一个类只能有一张虚表
    B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
    C:虚表是在运行期间动态生成的
    D:一个类的不同对象共享该类的虚表
  7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
    A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
    B:A类对象和B类对象前4个字节存储的都是虚基表的地址
    C:A类对象和B类对象前4个字节存储的虚表地址相同
    D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表

参考答案:1. A 2. D 3. C 4. A 5. B 6. D 7. D

  1. 下面程序输出结果是什么? ()
#include<iostream>
using namespace std;class A 
{
public:A(const char* s){ cout << s << endl; }~A() {}
};class B :virtual public A
{
public:B(const char* s1, const char* s2):A(s1) { cout << s2 << endl; }
};class C :virtual public A
{
public:C(const char* s1, const char* s2):A(s1) { cout << s2 << endl; }
};class D :public B, public C
{
public:D(const char* s1, const char* s2, const char* s3, const char* s4):B(s1, s2), C(s1, s3), A(s1){cout << s4 << endl;}
};int main() 
{D* p = new D("class A", "class B", "class C", "class D");delete p;return 0;
}

A:class A class B class C class D B:class D class B class C class A
C:class D class C class B class A D:class A class C class B class D

解析: 这是个菱形虚拟继承,所以 A 只会被调用一次,D 类里的初始化列表是按声明的顺序来初始化的,所以按 ABCD 的顺序,因此答案选 A

  1. 多继承中指针偏移问题?下面说法正确的是( )
class Base1 
{ 
public:int _b1; 
};class Base2 
{ 
public:int _b2; 
};class Derive : public Base1, public Base2 
{ 
public: int _d; 
};int main() 
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

解析: 画图理解即可,选 C
在这里插入图片描述

  1. 以下程序输出结果是什么()
class A
{
public:virtual void func(int val = 1) { cout << "A->" << val << endl; }virtual void test() { func(); }
};class B : public A
{
public:void func(int val = 0) { cout << "B->" << val << endl; }
};int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确

解析: 这题绝大多数人肯定会选到 D,这题的知识点确实比较偏,首先我们要知道多态重写的是实现,即只有 {} 内的内容是多态的,实际上子类的函数头其实相当于是从父类拷贝过来的,因此函数头的内容还是调用的父类的,所以答案选 B


希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

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

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

相关文章

算法训练之动态规划(三)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…

$_GET变量

$_GET 是一个超级全局变量&#xff0c;在 PHP 中用于收集通过 URL 查询字符串传递的参数。它是一个关联数组&#xff0c;包含了所有通过 HTTP GET 方法发送到当前脚本的变量。 预定义的 $_GET 变量用于收集来自 method"get" 的表单中的值。 从带有 GET 方法的表单发…

jQuery多库共存

在现代Web开发中&#xff0c;项目往往需要集成多种JavaScript库或框架来满足不同的功能需求。然而&#xff0c;当多个库同时使用时&#xff0c;可能会出现命名冲突、功能覆盖等问题。幸运的是&#xff0c;jQuery提供了一些机制来确保其可以与其他库和谐共存。本文将探讨如何实现…

MySQL 中的聚簇索引和非聚簇索引有什么区别?

MySQL 中的聚簇索引和非聚簇索引有什么区别&#xff1f; 1. 从不同存储引擎去考虑 在MySIAM存储引擎中&#xff0c;索引和数据是分开存储的&#xff0c;包括主键索引在内的所有索引都是“非聚簇”的&#xff0c;每个索引的叶子节点存储的是数据记录的物理地址&#xff08;指针…

Java从入门到“放弃”(精通)之旅——启航①

&#x1f31f;Java从入门到“放弃 ”精通之旅&#x1f680; 今天我将要带大家一起探索神奇的Java世界&#xff01;希望能帮助到同样初学Java的你~ (๑•̀ㅂ•́)و✧ &#x1f525; Java是什么&#xff1f;为什么这么火&#xff1f; Java不仅仅是一门编程语言&#xff0c;更…

三相电为什么没零线也能通电

要理解三相电为什么没零线也能通电&#xff0c;就要从发电的原理说起 1、弧形磁铁中加入电枢&#xff0c;旋转切割磁感线会产生电流 随着电枢旋转的角度变化&#xff0c;电枢垂直切割磁感线 电枢垂直切割磁感线&#xff0c;此时会产生最大电压 当转到与磁感线平行时&#xf…

文件上传做题记录

1&#xff0c;[SWPUCTF 2021 新生赛]easyupload2.0 直接上传php 再试一下phtml 用蚁剑连发现连不上 那就只要命令执行了 2&#xff0c;[SWPUCTF 2021 新生赛]easyupload1.0 当然&#xff0c;直接上传一个php是不行的 phtml也不行&#xff0c;看下是不是前端验证&#xff0c;…

【Pandas】pandas DataFrame head

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行 pandas.DataFrame.head pandas.DataFrame.head 是一个方法&#xff0c;用于返回 DataFrame 的前几行。这个方法非常有用&#xff0c;特别是在需要快速查看 DataFrame 的前…

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(1):承上启下,继续上路

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(1):承上启下,继续上路 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)普通形(ふつうけい)と思います(2)辞書形ことができます(3)Vたことがあります。(4)Vた とき & Vる とき3、单词(1)日语单词(2…

码率自适应(ABR)相关论文阅读简报

标题&#xff1a;Quality Enhanced Multimedia Content Delivery for Mobile Cloud with Deep Reinforcement Learning 作者&#xff1a;Muhammad Saleem , Yasir Saleem, H. M. Shahzad Asif, and M. Saleem Mian 单位: 巴基斯坦拉合尔54890工程技术大学计算机科学与工程系 …

汇编语言:指令详解

零、前置知识 1、数据类型修饰符 名称解释byte一个字节&#xff0c;8bitword单字&#xff0c;占2个字节&#xff0c;16bitdword双字&#xff0c;占4个字节&#xff0c;32bitqword四字&#xff0c;占8个字节&#xff0c;64bit 2、关键词解释 ptr&#xff1a;它代表 pointer&a…

蓝桥杯c ++笔记(含算法 贪心+动态规划+dp+进制转化+便利等)

蓝桥杯 #include <iostream> #include <vector> #include <algorithm> #include <string> using namespace std; //常使用的头文件动态规划 小蓝在黑板上连续写下从 11 到 20232023 之间所有的整数&#xff0c;得到了一个数字序列&#xff1a; S12345…

【C++算法】54.链表_合并 K 个升序链表

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a; 题目链接&#xff1a; 23. 合并 K 个升序链表 题目描述&#xff1a; 解法 解法一&#xff1a;暴力解法 每个链表的平均长度为n&#xff0c;有k个链表&#xff0c;时间复杂度O(nk^2) 合并两个有序…

Java中的注解技术讲解

Java中的注解&#xff08;Annotation&#xff09;是一种在代码中嵌入元数据的机制&#xff0c;不直接参与业务逻辑&#xff0c;而是为编译器、开发工具以及运行时提供额外的信息和指导。下面我们将由浅入深地讲解Java注解的概念、实现原理、各种应用场景&#xff0c;并通过代码…

京东与喜茶关系破裂:切断所有合作 禁止进入办公场所

快科技4月10日消息&#xff0c;据报道&#xff0c;京东集团近日被曝出内部下发全员禁令&#xff0c;全面封杀喜茶产品进入办公区域。 据知情人士透露&#xff0c;京东人力行政部门发布的通知明确规定&#xff1a;全国各职场禁止与喜茶品牌开展任何形式的合作&#xff1b;员工不…

+++++背到厌倦。持续更新

Spring IoC 的工作流程: 读取 BeanDefinition: Spring 容器启动时&#xff0c;会读取 Bean 的配置信息 (例如 XML 配置文件、注解或 Java 代码)&#xff0c;并将这些配置信息转换为 BeanDefinition 对象。创建 Bean 实例: 根据 BeanDefinition 中的信息&#xff0c;Spring 容器…

如何在Git历史中抹掉中文信息并翻译成英文

如何在Git历史中抹掉中文信息并翻译成英文 在软件开发和版本控制领域&#xff0c;维护一个清晰、一致的代码历史记录是至关重要的。然而&#xff0c;有时我们可能会遇到需要修改历史提交的情况&#xff0c;比如删除敏感信息或修正错误。本文将详细探讨如何在Git历史中抹掉中文…

21 天 Python 计划:MySQL中DML与权限管理

文章目录 前言一、介绍二、MySQL数据操作&#xff1a;DML2.1 插入数据&#xff08;INSERT&#xff09;2.1.1 插入完整数据&#xff08;顺序插入&#xff09;2.1.2 指定字段插入数据2.1.3 插入多条记录2.1.4 插入查询结果 2.2 更新数据&#xff08;UPDATE&#xff09;2.3 删除数…

微信小程序 -- 原生封装table

文章目录 table.wxmltable.wxss注意 table.js注意 结果数据结构 最近菜鸟做微信小程序的一个查询功能&#xff0c;需要展示excel里面的数据&#xff0c;但是菜鸟找了一圈&#xff0c;也没发现什么组件库有table&#xff0c;毕竟手机端好像确实不太适合做table&#xff01; 菜鸟…

LangChain-输出解析器 (Output Parsers)

输出解析器是LangChain的重要组件&#xff0c;用于将语言模型的原始文本输出转换为结构化数据。本文档详细介绍了输出解析器的类型、功能和最佳实践。 概述 语言模型通常输出自然语言文本&#xff0c;但在应用开发中&#xff0c;我们经常需要将这些文本转换为结构化的数据格式…