当前位置: 首页 > 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的智能体 他们的行为呢 比人类角…...

Java设计模式-单例模式(Singleton)

Java中实现单例模式有几种不同的方式,每种方式都有其特点和适用场景。下面是两种常用的实现方式:懒汉式和饿汉式。 懒汉式(线程安全) 懒汉式单例是指在第一次被引用时才会创建实例。为了确保线程安全,可以使用同步方法或同步块。 public class SingletonLazy {private sta…...

若依vue如何展示一个HTML页面(或者展示Markdown文档)

一. 前言 ⚠ 本文是展示Markdown的方法,不能直接前端编辑Markdown文档. 二. 准备部分 用Typora编辑器打开需要导出html页面,我这里使用Typora来导出 1. 先将md文件导出成html 2. 将导出好的文件放在若依vue的pubilc下(文件可以是中文) 三. 代码部分 1.使用v-html来展示HT…...

优化for循环(js的问题)

性能优化 var array [];for (let index 0; index < array.length; index) {// do something }// 优化后 for (let index 0, len array.length; index < len; index) {// do something } 算法优化 // 求和&#xff1a;1 2 3 4 ... 100 var sum 0; for (let i …...

如何更好的去理解源码

前言 这篇文章我准备来聊一聊如何去阅读开源项目的源码。 在聊如何去阅读源码之前&#xff0c;先来简单说一下为什么要去阅读源码&#xff0c;大致可分为以下几点原因&#xff1a; 最直接的原因&#xff0c;就是面试需要&#xff0c;面试喜欢问源码&#xff0c;读完源码才可以…...

c# opencv 获取多边形中心点

在C#中使用OpenCV获取多边形的中心点&#xff0c;可以按照以下步骤进行&#xff1a; 首先&#xff0c;你需要找到图像中的轮廓。这可以通过FindContours方法实现&#xff1a; using OpenCvSharp;Mat src new Mat("your_image_path", ImreadModes.Grayscale); Mat …...

Redis数据一致解决方案

文章目录 前言技术积累查询缓存业务流程更新缓存业务流程 更新缓存问题解决方案写在最后 前言 当前的应用服务很多都有着高并发的业务场景&#xff0c;对于高并发的解决方案一般会用到缓存来降低数据库压力&#xff0c;并且还能够提高系统性能减少请求耗时&#xff0c;比如我们…...

安捷伦DSOX2024A示波器

参考波形 示波器的非易失参考波形存储器可以存储两个波形。比较这些参考波形与实时波形&#xff0c;并对已存储数据进行后分析和测量。您也可将波形数据存储到移动USB 存储器设备。这些数据还能调用到示波器的两个参考存储器的其中一个&#xff0c;进行全面的波形测量和分析。为…...

Leetcode算法系列| 4. 寻找两个正序数组的中位数

目录 1.题目2.题解C# 解法一&#xff1a;合并List根据长度找中位数C# 解法二&#xff1a;归并排序后根据长度找中位数C# 解法三&#xff1a;方法二的优化&#xff0c;不真实添加到listC# 解法四&#xff1a;第k小数C# 解法五&#xff1a;从中位数的概念定义入手 1.题目 给定两个…...

Java整合APNS推送消息-IOS-APP(基于.p12推送证书)

推送整体流程 1.在开发者中心申请对应的证书&#xff08;我用的是.p12文件&#xff09; 2.苹果手机用户注册到APNS&#xff0c;APNS将注册的token返回给APP&#xff08;服务端接收使用&#xff09;。 3.后台服务连接APNS&#xff0c;获取连接对象 4.后台服务构建消息载体 5.后台…...

C语言strcpy函数用法

C语言strcpy函数用法 大家好&#xff0c;我是免费搭建查券返利机器人赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;让我们一起深入了解C语言中的strcpy函数&#xff0c;这是一个在字符串处理中非…...

汽车服务品牌网站建设的作用是什么

汽车服务涵盖多个层面&#xff0c;在保修维护这一块更是精准到了车内车外&#xff0c;无论是品牌商还是市场中各维修部&#xff0c;都能给到车辆很好的维修养护服务。如今车辆的人均拥有量已经非常高&#xff0c;也因此市场中围绕汽车相关的从业者也比较多。 首先就是拓客引流…...

【iOS】UICollectionView

文章目录 前言一、实现简单九宫格布局二、UICollectionView中的常用方法和属性1.UICollectionViewFlowLayout相关属性2.UICollectionView相关属性 三、协议和代理方法&#xff1a;四、九宫格式的布局进行升级五、实现瀑布流布局实现思路实现原理代码调用顺序实现步骤实现效果 总…...

Linux poll 和 select 机制

poll select 介绍 使用非阻塞 I/O 的应用程序常常使用 poll, select, 和 epoll 系统调用. poll, select 和 epoll 本质上有相同的功能: 每个允许一个进程来决定它是否可读或者写一个 或多个文件而不阻塞. 这些调用也可阻塞进程直到任何一个给定集合的文件描述符可用来 读或写.…...

【JVM基础】 JVM 如何加载一个类以及类加载机制

文章目录 1、什么时候一个类会被加载&#xff1f;1、包含 main 方法的主类2、非 包含 main 方法的主类&#xff0c;什么时候去加载&#xff1f; 3、类加载器如何加载一个类&#xff1f;1、验证阶段&#xff1a;2、准备阶段&#xff1a;3、解析阶段&#xff1a;4、初始化&#x…...

Android Studio使用Genymotion

1. Genymotion介绍 GenyMotion速度之快令人发指&#xff0c;模拟效果堪比真机调试&#xff0c;支持绝大部分的模拟器功能&#xff0c;甚至包括语音&#xff0c;Google Now&#xff0c;支持eclipse, android studio。非常适合用来开发和演示效果。 2. Genymotion下载 Genymotio…...

Mysql sql_mode参数配置

今天在使用数据库查询时使用了Group语句&#xff0c;遇到问题&#xff1a; SELECT t1.UnderlyingInstrumentID, t2.* FROM t_OptionInstrument t1 LEFT JOIN t_Instrument t2 ON t2.InstrumentID t1.UnderlyingInstrumentID GROUP BY t1.UnderlyingInstrumentID > 1055 - …...

SpringIOC之AbstractMessageSource

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…...

详解Vue3中的基础路由和动态路由

本文主要介绍Vue3中的基础路由和动态路由。 目录 一、基础路由二、动态路由 Vue3中的路由使用的是Vue Router库&#xff0c;它是一个官方提供的用于实现应用程序导航的工具。Vue Router在Vue.js的核心库上提供了路由的功能&#xff0c;使得我们可以在单页应用中实现页面的切换、…...

Mysql四种事务隔离级别(简易理解)

读未提交&#xff1a;简单理解就是读到没有提交事务的执行结果&#xff1b;读已提交&#xff1a;简单理解就是只能读到已经提交的事务执行结果&#xff1b;可重复读&#xff1a;简单理解就是确保并发读取数据库时&#xff0c;读到的数据一致&#xff0c;这是mysql默认隔离级别&…...

react中使用redux最简单最方便的方式,配合rematch简化操作,5分钟学会

react中使用状态管理的方式也很多&#xff0c;比如redux和mobx等&#xff0c;今天这一片就讲一下redux的入门到熟练使用&#xff0c;主要是要理解它redux的组成有哪些&#xff0c;到怎么创建&#xff0c;和组建中怎么使用三个问题。这里先放上官网文档&#xff0c;不理解的地方…...

vmware安装中标麒麟高级服务器操作系统软件 V7.0操作系统

vmware安装中标麒麟高级服务器操作系统软件 V7.0操作系统 1、下载中标麒麟高级服务器操作系统软件 V7.0镜像2、安装中标麒麟高级服务器操作系统软件 V7.0操作系统 1、下载中标麒麟高级服务器操作系统软件 V7.0镜像 官方提供使用通道 访问官网 链接: https://www.kylinos.cn/ 下…...

OpenCV | 霍夫变换:以车道线检测为例

霍夫变换 霍夫变换只能灰度图&#xff0c;彩色图会报错 lines cv2.HoughLinesP(edge_img,1,np.pi/180,15,minLineLength40,maxLineGap20) 参数1&#xff1a;要检测的图片矩阵参数2&#xff1a;距离r的精度&#xff0c;值越大&#xff0c;考虑越多的线参数3&#xff1a;距离…...

【C#与Redis】--目录

1. 介绍 2. Redis 数据结构 3. Redis 命令 3.1 基本命令 3.2 字符串命令 3.3 哈希命令 3.4 列表命令 3.5 集合命令 3.6 有序集合命令 4. C# 操作 Redis 4.1 使用 Redis 库 4.2 连接 Redis 服务器 4.3 操作 Redis 数据结构 4.5 执行 Redis 命令 5. 高级主题 5.1 Redis 事…...

html旋转相册

一、实验题目 做一个旋转的3d相册&#xff0c;当鼠标停留在相册时&#xff0c;相册向四面散开 二、实验代码 <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" con…...

Plantuml之对象图语法介绍(十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

深度学习(八):bert理解之transformer

1.主要结构 transformer 是一种深度学习模型&#xff0c;主要用于处理序列数据&#xff0c;如自然语言处理任务。它在 2017 年由 Vaswani 等人在论文 “Attention is All You Need” 中提出。 Transformer 的主要特点是它完全放弃了传统的循环神经网络&#xff08;RNN&#x…...

R语言中的函数28:Reduce(), Filter(), Find(), Map(), Negate(), Position()

文章目录 介绍Reduce()实例 Filter()实例 Find()实例 Map()实例 Negate()实例 Position()实例 介绍 R语言中的Reduce(), Filter(), Find(), Map(), Negate(), Position()是base包中的一些高级函数。随后&#xff0c;很多包也给这些函数提供了更多的扩展。 Reduce() 该函数根…...

RTP/RTCP/RTSP/SIP/SDP/RTMP对比

RTP&#xff08;Real-time Transport Protocol&#xff09;是一种用于实时传输音频和视频数据的协议。它位于传输层和应用层之间&#xff0c;主要负责对媒体数据进行分包、传输和定时。 RTCP&#xff08;Real-Time Control Protocol&#xff09;是 RTP 的控制协议&#xff0c;…...

Centos安装vsftpd:centos配置vsftpd,ftp报200和227错误

一、centos下载安装vsftpd&#xff08;root权限&#xff09; 1、下载安装 yum -y install vsftpd 2、vsftpd的配置文件 /etc/vsftpd.conf 3、备份原来的配置文件 sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.backup 4、修改配置文件如下&#xff1a;vi /etc/vsftpd.conf …...

软件测试职业规划

软件测试人员的发展误区【4】 公司开发的产品专业性较强&#xff0c;软件测试人员需要有很强的专业知识&#xff0c;现在软件测试人员发展出现了一种测试管理者不愿意看到的景象&#xff1a; 1、开发技术较强的软件测试人员转向了软件开发(非测试工具开发)&#xff1b; 2、业务…...