当前位置: 首页 > news >正文

C++ 学习系列 -- C++ 中的多态行为

一    多态是什么?

多态是面向对象三大特征中重要一项,另外两项分别是封装与继承。

所谓多态,指的是多种不同的形态,也就是去完成某个具体的行为,多个不同的对象去操作同一个函数时,会产生不同的行为,进而出现不同的状态。

二   C++ 类中的普通成员函数

普通成员函数面临着两个问题:

            1.  无法实现多态行为

            2.  派生类同名函数会覆盖基类的同名函数,即使函数的参数不同也会导致覆盖

// base.h
#include<iostream>class Base
{
public:Base(){}~Base(){}void func1(){std::cout << "Base::func1()---" << std::endl;}void func2(){std::cout << "Base::func2()---" << std::endl;}
};class Derive1 : public Base
{
public:Derive1(){}~Derive1(){}void func1(){std::cout << "Derive1::func1()---" << std::endl;}void func2(int num){std::cout << "Derive1::func2(int num)---" << std::endl;}
};// main.cpp
#include"base.h"int main()
{// 多态三要素// 1. 指针 2. 向上转型 3. 调用虚函数// 1. 普通函数 是没有多态行为的// 2. 派生类的同名函数会覆盖基类同名函数,即使参数不同也会被覆盖Base* b1 = new Derive1;b1->func1(); // 调用的是 Base::func1() 函数delete b1;// b1->func2(2); // 编译不过,因为 Base::func2() 函数是不带参数的Derive1* d1 = new Derive1;// d1->func2(); // 编译不过,Derive1::func2(int num) 覆盖了同名函数 Base::func2() 函数,delete d1;return 0;
}

输出:

Base::func1()---

Derive1::func1()---

三   C++ 中的虚函数

1. virtual 关键字

c++ 中的 virtual 关键字来常有以下两种使用,

  1.1 修饰函数

被 virtual 关键字修饰的类成员函数,被称为虚函数。虚函数分为两种:

1.  纯虚函数 (也叫做抽象函数)

       含有纯虚函数的类是无法被实例化的,因为在编译器看来,纯虚函数并不是一个完整的函数,它没有具体的实现,若是在使用时调用它,编译器也不知道到底该怎么办

2.  非纯虚函数

      若是类的虚函数都是非纯虚函数,那么这个类是可以实例化的

            友元函数、构造函数与 static 函数 是不能被 virtual 关键字修饰的。

      虚函数具有继承性,基类中 virtual关键字修饰的虚函数,派生类重写时,可以不再用 virtual 修饰,重写的基类虚函数会自动成为虚函数。 

// A.h
#include<iostream>class A
{
public:A() { }virtual ~A() { }// 虚函数virtual void vfunc1(){std::cout << "A::vfunc1()---" << std::endl;}// 纯虚函数virtual void vfunc() = 0;private:int m_data1;
};class B : public A
{
public:B(){ }~B() { }void vfunc1(){std::cout << "B::vfunc1()---" << std::endl;}void vfunc(){std::cout << "B::vfunc()---" << std::endl;}
private:int m_data2;
};// main.cpp
#include"A.h"int main()
{// A a1;          // 类中有纯虚函数,无法实例化,编译不过// A* a2 = new A; // 类中有纯虚函数,无法实例化,编译不过B b1;             // B class 实现了 A class  的纯虚函数,可以实例化B* b2 = new B;    // B class 实现了 A class  的纯虚函数,可以实例化delete b2;return 0;
}

1.2 修饰类继承

C++虚继承和虚基类详解 - 知乎 (zhihu.com)   

1.2.1 public、protected 与 private 继承的区别

我们知道 类之间的继承有三种:public、protected 与 private

   三者区别为:

  •   public 继承
  •  基类中原 public 的成员 在派生类中仍然是 public
  •  基类中原 protected 的成员 在派生类中仍然是 protected
  •  基类中原 private 的成员 在派生类中仍然是 private   
  •  protected
  •  基类中原 public 的成员 在派生类中变为了 protected
  •  基类中原 protected 的成员 在派生类中仍然是 protected
  •  基类中原 private 的成员 在派生类中仍然是 private   
  • private
  •  基类中原 public 的成员 在派生类中变为了 private
  •  基类中原 protected 的成员 在派生类中仍然是 private
  •  基类中原 private 的成员 在派生类中仍然是 private   
class A
{};class B1 : public A
{};class B2 : protected A
{};class B3 : private A
{};

那么 virtual 继承是用于解决什么问题呢?下面让我们看以下的场景:

有这样一种继承关系,菱形继承:

  

// AA.h
#include<iostream>class AA
{
public:AA(int a = 0):m_a(a){std::cout << "AA constructor." << std::endl;}~AA(){std::cout << "AA destructor." << std::endl;}public:int m_a;
};class BB : public AA
{
public:BB(int a = 0):m_b(a){std::cout << "BB constructor." << std::endl;}~BB(){std::cout << "BB destructor." << std::endl;}public:int m_b;
};class CC : public AA
{
public:CC(int a = 0):m_c(a){std::cout << "CC constructor." << std::endl;}~CC(){std::cout << "CC destructor." << std::endl;}public:int m_c;
};class DD : public BB, public CC
{
public:DD(int a = 0):m_d(a){std::cout << "DD constructor." << std::endl;}~DD(){std::cout << "DD destructor." << std::endl;}void set_a(int a){// m_a = a; // 编译不通过,这里有歧义,可能是 BB::m_a ,也可能是 CC::m_a}void set_b(int b){m_b = b;}void set_c(int c){m_c = c;}void set_d(int d){m_d = d;}public:int m_d;
};// main.cpp
#include"AA.h"int main()
{D d;return 0;
}

输出:

 通过以上代码与输出我们可以得出下面两点结论:      

    1.  AA 类的构造函数与析构函数分别被执行了两次,分别是 DD 继承 BB 类时执行,DD继承 CC 类时执行。

    2.  m_a = a 这行代码是无法通过编译的,因为编译器无法判断 m_a 是从路径 AA -> BB -> DD 还是从路径 AA -> CC -> DD 得来的,产生了歧义。

     那么是否有办法消除歧义呢?

     答案是有,可以通过如下的代码来消除歧义

// 使用 BB 类路径的 m_a
void set_a(int a)
{BB::m_a = a;
}
// 使用 CC 类路径的 m_a
void set_a(int a)
{CC::m_a = a;
}

  上面的多继承的方式主要有两点问题:

    1. 派生类 DD 有两个 m_a 成员变量,分别来自于 AA -> BB -> DD 于 AA -> CC -> DD,这样会造成冗余与内存的浪费

   2. 同名变量与函数会出现命名的冲突

1.2.2 虚继承

   

为了解决多继承这两点问题,c++引入了虚继承的概念

虚继承代码如下:

// AA.h
#include<iostream>class AA
{
public:AA(int a = 0):m_a(a){std::cout << "AA constructor." << std::endl;}~AA(){std::cout << "AA destructor." << std::endl;}public:int m_a;
};class BB : virtual public AA
{
public:BB(int a = 0):m_b(a){std::cout << "BB constructor." << std::endl;}~BB(){std::cout << "BB destructor." << std::endl;}public:int m_b;
};class CC : virtual public AA
{
public:CC(int a = 0):m_c(a){std::cout << "CC constructor." << std::endl;}~CC(){std::cout << "CC destructor." << std::endl;}public:int m_c;
};class DD : public BB, public CC
{
public:DD(int a = 0):m_d(a){std::cout << "DD constructor." << std::endl;}~DD(){std::cout << "DD destructor." << std::endl;}void set_a(int a){m_a = a; // 编译通过}void set_b(int b){m_b = b;}void set_c(int c){m_c = c;}void set_d(int d){m_d = d;}public:int m_d;
};// main.cpp
#include"AA.h"int main()
{DD dd;return 0;
}

 输出:

  

    通过输出可以看出,AA类的构造函数与析构函数只被执行了一次,这说明在虚继承时,不再存在两种路径 AA  -> BB -> DD 与 AA -> CC -> DD。

   上述的虚继承代码解决了菱形继承中 m_a 数据冗余的问题,所以 D 类中直接访问 m_a 就不再有歧义的问题了。

// 虚继承中访问 m_a 无冗余问题
void  set_a(int a)
{m_a = a;
}

总结

       虚继承的目的是为了表明某个类的基类是可以被这个类的所有派生类共享,这个基类也被称作虚基类(Virtual Base Class),上述代码中的 class AA 就是 class BB 与 class CC 的虚基类。

不管虚基类在派生类中出现多少次,最终在派生类中都只有一份虚基类的成员变量与成员函数。

2. 虚函数指针与虚函数表

     2.1 c++多态原理

      c++ 实现多态的原理就是利用 虚函数表指针与虚函数表。

      若是定义的类实现了一个或者多个虚函数,那么这个类会有一张对应的虚函数表,表中存放的是对应虚函数的指针。

      如下图所示:

若是派生类 B 重写了基类 A 中的 虚函数 vfunc1,那么在 class B 的虚函数表中对应的 vfunc1 虚函数的地址就是派生类 B 重写的虚函数 B::vfunc1 地址;

若是 派生类 B 没有重写了基类 A 中的 虚函数 vfunc2,那么在 class B 的虚函数表中对应的 vfunc2 虚函数的地址就是基类 A 的虚函数 A::vfunc2 地址。

    当新生成 class B或者 class C 对象时,会在对象内部自动生成一个指向虚函数表地址的虚函数表指针 vptr,如果我们用 sizeof(A)  发现其大小为 16 ,计算方式为 两个 int 类型的成员变量大小分别为 4 ,有一个默认的虚函数指针大小为 8  (这里是 64 位操作系统,如果是 32 位操作系统的话,指针大小为 4)所以 sizeof(B) = 4 * 2 + 8

// A.h
#include<iostream>class A
{
public:A() { }virtual ~A() { }virtual void vfunc1() override{std::cout << "A::vfunc1()---" << std::endl;}virtual void vfunc2(){std::cout << "A::vfunc2()---" << std::endl;}
private:int m_data1;int m_data2;
};class B : public A
{
public:B(){ }~B() { }void vfunc1() override{std::cout << "B::vfunc1()---" << std::endl;}
private:int m_data2;int m_data3;};class C : public B
{
public:C(){ }~C(){ }void vfunc1() override{std::cout << "C::vfunc1()---" << std::endl;}
private:int m_data1;int m_data4;
};// main.cpp
#include"A.h"int main()
{// 多态三要素// 1. 指针 2. 向上转型 3. 调用虚函数A* a  = new A;A* b = new B;A* c = new C;a->vfunc1(); // A::vfunc1a->vfunc2(); // A::vfunc2b->vfunc1(); // B::vfunc1b->vfunc2(); // A::vfunc2c->vfunc1(); // C::vfunc1c->vfunc2(); // A::vfunc2return 0;
}

 输出

     通过输出可以看出,指针为 A* 的 B 类对象调用的 vfunc1 函数为 B 类中重写的 vfunc1 虚函数; 指针为 A* 的 C 类对象调用的 vfunc1 函数为 C 类中重写的 vfunc1 虚函数。

2.2 虚函数表分析

    以下通过类的内存布局来看一下虚函数表是什么样的:

   通过debug 方式,查看  A类、B类 与 C 类生成对象中虚函数表中的内存分配,操作系统是 windows 10 ,64位。

   

   

  

       A类虚函数表 vptr 中的头两个元素存放的是 其析构函数 A::~A() 的地址,第三个元素是虚函数   A::vfunc1() 的地址,第四个元素 是虚函数   A::vfunc2() 的地址;

      B类虚函数表 vptr 中的头两个元素存放的是 其析构函数 B::~B() 的地址,第三个元素是虚函数   B::vfunc1() 的地址,第四个元素 是虚函数   A::vfunc2() 的地址;

      C类虚函数表 vptr 中的头两个元素存放的是 其析构函数 C::~C() 的地址,第三个元素是虚函数   C::vfunc1() 的地址,第四个元素 是虚函数   A::vfunc2() 的地址;

如何获取到类 Object 的虚函数表呢?对于有虚函数的对象 object 来说:

1. &object 代表对象 object 的起始地址

2. (intptr_t *)&object 代表获取对象起始地址的前 4 个字节(32 位操作系统位 4 字节,64位操作系统位 8 字节),而这前 4 个字节(32 位操作系统位 4 字节,64位操作系统位 8 字节)则是虚函数表的指针

3. *(intptr_t *)&object 则是取前 4 个字节(32 位操作系统位 4 字节,64位操作系统位 8 字节)中的数据,也就是虚函数表 vptr 的地址

4. 取 虚函数表 vptr 地址的前 4 个字节(32 位操作系统位 4 字节,64位操作系统位 8 字节),为虚函数表中第一个元素的地址  (intptr_t *) *(intptr_t *)&object,取出虚函数表中第一个元素 *((intptr_t *) *(intptr_t *)&object), 则取出虚函数表中第 n 个元素为  *((intptr_t *) *(intptr_t *)&object + n),所取元素即为 虚函数的地址

5. 定义一个函数指针:typedef void(*pFunc)(); 函数指针 pFunc 代表 形如 void print(); 的函数的指针类型。通过前面的 1 - 4 步,获取道虚函数的地址,将地址赋给函数指针 pFunc ,就可以通过 pFunc 调用对应的虚函数了。     深入浅出——理解c/c++函数指针 - 知乎 (zhihu.com)

我们可以把类对象的虚函数表中的虚函数指针获取出来,直接调用虚函数,代码如下所示:

// main.cpp
#include"a.h"int main()
{typedef void(*pFunc)() ;A a;printf("the addr of A::vfunc1 is 0x%p\n", &A::vfunc1);printf("the addr of the third function pointer in class A vptr is: 0x%p\n", *((intptr_t *)*(intptr_t *)&a+2));printf("the addr of the fourth function pointer in class A vptr is: 0x%p\n", *((intptr_t *)*(intptr_t *)&a+3));pFunc pa_vfunc1 = (pFunc)*((intptr_t *)*(intptr_t *)&a+2);pFunc pa_vfunc2 = (pFunc)*((intptr_t *)*(intptr_t *)&a+3);pa_vfunc1();pa_vfunc2();B b;printf("the addr of B::vfunc1 is 0x%p\n", &B::vfunc1);printf("the addr of the third function pointer in class B vptr is: 0x%p\n", *((intptr_t *)*(intptr_t *)&b+2));printf("the addr of the fourth function pointer in class B vptr is: 0x%p\n", *((intptr_t *)*(intptr_t *)&b+3));pFunc pb_vfunc1 = (pFunc)*((intptr_t *)*(intptr_t *)&b+2);pFunc pb_vfunc2 = (pFunc)*((intptr_t *)*(intptr_t *)&b+3);pb_vfunc1();pb_vfunc2();C c;printf("the addr of B::vfunc1 is 0x%p\n", &C::vfunc1);printf("the addr of the third function pointer in class C vptr is: 0x%p\n", *((intptr_t *)*(intptr_t *)&c+2));printf("the addr of the fourth function pointer in class C vptr is: 0x%p\n", *((intptr_t *)*(intptr_t *)&c+3));pFunc pc_vfunc1 = (pFunc)*((intptr_t *)*(intptr_t *)&c+2);pFunc pc_vfunc2 = (pFunc)*((intptr_t *)*(intptr_t *)&c+3);pc_vfunc1();pc_vfunc2();return 0;
}

输出:

2.3  c++ 多态实现条件

 C++ 多态的实现条件有三条:

1. 指针

2. 指针向上转型

3. 虚函数

   多态可以通过下面的例子来去实现:

// main.cpp
#include"A.h"int main()
{// 多态三要素:1. 指针 2. 向上转型 3. 调用虚函数A* a = new A; // 指针a->vfunc1();a->vfunc2();B b;a = &b;       // 向上转型a->vfunc1();  // 调用虚函数a->vfunc2();C c;a = &c;      // 向上转型a->vfunc1();  // 调用虚函数a->vfunc2();return 0;
}

输出:

四   常见问题

1. 虚函数指针属于类还是对象?

     答:虚函数指针属于对象,每个含有虚函数的类对象都有一个默认的虚函数指针,通过虚函数指针可以获取到虚函数表,进而实现动态调用虚函数,实现多态行为

2. 虚函数表属于类还是对象?

   答:虚函数表属于类,让我们站在设计者的角度思考一下,虚函数表本身是占有一定的内存空间的,而每个对象的虚函数表都是相同的,要是每个对象都有一个,那得耗费多少冗余内存啊,所有对象共用一份虚函数表即可。

3. 基类的析构函数为什么要加 virtual 关键字

答:若是基类的析构函数不加 virtual 关键字的话,则 delete 多态时的对象指针是,会出现只调用基类的析构函数,未调用派生类的析构函数,那么派生类的数据就可能未被释放掉,会出现内存泄漏的现象。

实验如下:

// base.h
#include<iostream>class Base
{
public:Base(){std::cout << "Base constructor." << std::endl;}~Base(){std::cout << "Base destructor." << std::endl;}virtual void func(){std::cout << "virtual Base::func()---" << std::endl;}};class Derive1 : public Base
{
public:Derive1(){std::cout << "Derive1 constructor." << std::endl;}virtual ~Derive1(){std::cout << "Derive1 constructor." << std::endl;}void func() override{std::cout << "virtual Derive1::func()---" << std::endl;}};// main.cpp
#include"base.h"int main()
{Base* d1 = new Derive1;d1->func();delete d1;return 0;
}

   输出:

    

相关文章:

C++ 学习系列 -- C++ 中的多态行为

一 多态是什么&#xff1f; 多态是面向对象三大特征中重要一项&#xff0c;另外两项分别是封装与继承。 所谓多态&#xff0c;指的是多种不同的形态&#xff0c;也就是去完成某个具体的行为&#xff0c;多个不同的对象去操作同一个函数时&#xff0c;会产生不同的行为&…...

Spring Cloud中实现Feign声明式服务调用客户端

可以通过OpenFeign从一个服务中调用另一个服务&#xff0c;我们一般采用的方式就是定义一个Feign接口并使用FeignClient注解来进行标注&#xff0c;feign会默认为我们创建的接口生成一个代理对象。 当我们在代码中调用Feign接口的方法的时候&#xff0c;实际上就是在调用我们Fe…...

【网络编程】网络通信基础——简述TCP/IP协议

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 一、ip地…...

观察者模式 Observer

观察者模式属于行为型模式。在程序设计中&#xff0c;观察者模式通常由两个对象组成&#xff1a;观察者和被观察者。当被观察者状态发生改变时&#xff0c;它会通知所有的观察者对象&#xff0c;使他们能够及时做出响应。 三要素&#xff1a;观察者&#xff08;Observer&#…...

Hadoop入门学习笔记——七、Hive语法

视频课程地址&#xff1a;https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接&#xff1a;https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记&#xff08;汇总&#xff09; 目录 七、Hive语法7.1. 数据库相关操作7.1.1. 创建数据库7.1.2…...

采用SpringBoot框架+原生HTML、JS前后端分离模式开发和部署的电子病历编辑器源码(电子病历评级4级)

概述&#xff1a; 电子病历是指医务人员在医疗活动过程中,使用医疗机构信息系统生成的文字、符号、图表、图形、数据、影像等数字化信息,并能实现存储、管理、传输和重现的医疗记录,是病历的一种记录形式。 医院通过电子病历以电子化方式记录患者就诊的信息&#xff0c;包括&…...

HTML表单

<!DOCTYPE html> <html><head><meta charset"utf-8"><title>招聘案列</title></head><body><h1>午睡操场传来蝉的声音</h1><hr /><form>昵称&#xff1a;<input type"text" …...

Http 请求体和响应体中重要的字段

Http 请求体 Accept&#xff1a;用于告诉服务器客户端能够处理哪些媒体类型。Accept 头中的值通常是一个或多个 MIME 类型&#xff0c;并按优先级排序。服务器会根据 Accept 头中的值来决定响应的内容类型。例如&#xff0c;Accept: text/plain, text/html。Content-Type&…...

最新国内可用使用GPT4.0,GPT语音对话,Midjourney绘画,DALL-E3文生图

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和用户进行创作交流。 然而&#xff0c;GP…...

【量化金融】证券投资学

韭菜的自我修养 第一章&#xff1a; 基本框架和概念1.1 大盘底部形成的技术条件1.2 牛市与熊市1.3 交易系统1.3.1 树懒型交易系统1.3.2 止损止损的4个技术 第二章&#xff1a;证券家族4兄弟2.1 债券&#xff08;1&#xff09;债券&#xff0c;是伟大的创新&#xff08;2&#x…...

【Bash】重点总结

文章目录 1. 总体认识1.1. Shell概述1.2. 第一个Shell脚本 2. 变量2.1. 定义变量2.2. 使用变量2.3. 只读变量2.4. 删除变量2.5. 变量类型2.5.1. 字符串变量 1. 总体认识 1.1. Shell概述 Shell是一个用C语言编写的程序&#xff0c;这个程序提供了一个界面&#xff0c;用户通过…...

Git安装和使用教程,并以gitee为例实现远程连接远程仓库

文章目录 1、Git简介及安装2、使用方法2.1、Git的启动与配置2.2、基本操作2.2.1、搭建自己的workspace2.2.2、git add2.2.3、git commit2.2.4、忽略某些文件不予提交2.2.5、以gitee为例实现git连接gitee远程仓库来托管代码 1、Git简介及安装 版本控制&#xff08;Revision cont…...

Hadoop入门学习笔记——一、VMware准备Linux虚拟机

视频课程地址&#xff1a;https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接&#xff1a;https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记&#xff08;汇总&#xff09; 目录 一、VMware准备Linux虚拟机1.1. VMware安装Linux虚拟机1.…...

CSS3新增特性

CSS3 CSS3私有前缀 W3C 标准所提出的某个CSS 特性&#xff0c;在被浏览器正式支持之前&#xff0c;浏览器厂商会根据浏览器的内核&#xff0c;使用私有前缀来测试该 CSS 特性&#xff0c;在浏览器正式支持该 CSS 特性后&#xff0c;就不需要私有前缀了。 查询 CSS3 兼容性的网…...

Unity中Shader观察空间推导

文章目录 前言一、本地空间怎么转化到观察空间二、怎么得到观察空间的基向量1、Z轴向量2、假设 观察空间的 Y~假设~ (0,1,0)3、X Y 与 Z 的叉积4、Y X 与 Z 的叉积 三、求 [V~world~]^T^1、求V~world~2、求[V~world~]^T^ 四、求出最后在Unity中使用的公式1、偏移坐标轴2、把…...

信息学奥赛一本通2034:【例5.1】反序输出

2034&#xff1a;【例5.1】反序输出 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 79280 通过数: 35643 【题目描述】 输入nn个数,要求程序按输入时的逆序把这nn个数打印出来&#xff0c;已知整数不超过100100个。也就是说&#xff0c;按输入相反顺序打印这nn个…...

使用教程之【SkyWant.[2304]】路由器操作系统,破解移动【Netkeeper】校园网【小白篇】

许多高校目前饱受Netkeeper认证的痛苦&#xff0c;普通路由器无法使用&#xff0c; 教你利用SkyWant的Netkeeper认证软件来使你的SkyWant路由器顺利认证上网&#xff0c;全宿舍又可以合作共赢了&#xff01; 步骤一&#xff1a;正确连接网线&#xff0c;插电开机 正确连接网…...

模式识别与机器学习(十):梯度提升树

1.原理 提升方法实际采用加法模型&#xff08;即基函数的线性组合&#xff09;与前向分步算法。以决策树为基函数的提升方法称为提升树&#xff08;boosting tree&#xff09;。对分类问题决策树是二叉分类树&#xff0c;对回归问题决策树是二叉回归树。提升树模型可以表示为决…...

《剑指offer》Java版--12.矩阵中的路径(DFS+剪枝)

剑指offer原题:矩阵中的路径 请设计一个函数&#xff0c;用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始&#xff0c;每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格&#xff0c;那么该路径不能再…...

AI智能体的介绍

最近几个月 随着大语言模型的持续火爆 利用大模型来构建AI智能体的研究呢 也陆续进入了人们的视野 AI智能体这个概念呢 也逐渐的流行开来 先是斯坦福大学谷歌的研究者们 成功的构建了一个虚拟小镇 小镇上的居民呢不再是人 而是25个AI的智能体 他们的行为呢 比人类角…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

PHP和Node.js哪个更爽?

先说结论&#xff0c;rust完胜。 php&#xff1a;laravel&#xff0c;swoole&#xff0c;webman&#xff0c;最开始在苏宁的时候写了几年php&#xff0c;当时觉得php真的是世界上最好的语言&#xff0c;因为当初活在舒适圈里&#xff0c;不愿意跳出来&#xff0c;就好比当初活在…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O…...