C++多态的认识与理解

多态的定义

多态其实就是同一操作在不同的对象上可以有不同的实现方式。

多态的类型

多态分为静态多态和动态多态两种,而静态多态其实我们之前就了解过,今天主要是讲解一下动态多态。

  • 静态多态(编译时多态):静态多态其实就是在函数进行编译阶段根据函数的参数列表来确定调用哪个具体的函数的,其中主要就是函数重载和运算符重载。        
  • 动态多态(运行时多态):运行时多态是在程序运行阶段才能确定具体调用哪个函数(通过对象的虚表指针)。它是基于函数重写实现的。当通过基类指针或引用调用虚函数时,实际调用的是派生类中重写后的虚函数。

构成多态的条件

  1. 虚函数重写
  2. 必须通过父类的指针或引用去调用虚函数

虚函数重写

虚函数就是被virtual关键字修饰的成员函数

虚函数的重写就是派生类中有一个跟基类完全相同的虚函数,这就称子类的虚函数重写了基类的虚函数。而这里的完全相同是派生类虚函数与基类虚函数的返回值类型、函数名、参数列表类型完全相同 

class Person 
{
public:
	virtual void test() { ... }//虚函数
};
class Student : public Person 
{
public:
	virtual void test() { ... }//虚函数重写
};

注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,也是可以构成重写,因为继承时基类的虚函数被继承下来了,而在派生类依旧保持虚函数属性,所以可以不加virtual关键字修饰。但是有点不咋规范

 父类的指针或引用调用虚函数

首先我们要知道指针是一个变量,指向的是一个地址,而指针的类型恰恰就决定了通过该地址可以访问的空间大小。而引用是取别名(声明时初始化,之后不能绑定其他对象)。基类的指针或引用指向派生类的话,就会使得基类的对象指向的是派生类的前部分空间的内容。所以当基类指针或引用的对象去调用虚函数时就形成了多态,从而调用的实际就是派生类的虚函数。而内部实现机制后文有解释。

多态调用实例

class Person 
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; return nullptr; }//
};
class Student : public Person 
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; return nullptr; }//
};
void Func(Person& p)//父类的引用为形参
{
	p.BuyTicket();
}
int main()
{
	Person ps;
	Student st;
	Func(ps);//传递父类对象
	Func(st);//传递子类对象(形成多态)
	return 0;
}

通过指针或引用调用虚函数看的并不是通过参数的类型去调用对应的函数方法,而是该参数所实际指向的对象,指向父类对象就调用父类的虚函数,指向子类对象就调用子类的虚函数。

虚函数重写的两个例外

协变

协变就是虚函数重写的时候,基类和派生类的函数返回值类型可以不同但是必须是父子类关系的指针或引用。如下:

class A {};
class B :public A{};

class Person 
{
public:
	virtual A* test() {return nullptr; }//
};
class Student : public Person 
{
public:
	virtual B* test() {return nullptr; }//
};

就像上面的例子,父子类虚函数的返回值 必须是父子类关系的指针或引用,因为A类与B类也是父子关系,所以虚函数的返回值也应该将person类与student类的父子关系对应起来,即父类A对应父类Person,子类Student对应子类B。

析构函数的重写

其实基类与派生类的析构函数也是可以构成虚函数重写的,因为针对一些特定的情况,必须要使得基类与派生类实现重写才可以实现代码的正确安全性。

如以下情况:

看以下代码运行:

class Person {
public:
    Person()
    {
        cout << "Person()" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
};
class Student : public Person {
public:
    Student()
    {
        cout << "Student()" << endl;
    }
    ~Student()
    {
        cout << "~Student()" << endl;
    }
};
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;
    delete p1;
    delete p2;

    return 0;
}


 首先我们知道new对象时,会为该对象分配空间,所以会自动调用该对象的构造函数。但是student类继承了person类,所以在创建student的对象时,会先调用父类的构造函数,然后再调用自己的构造函数。

同样当我们进行delete时也会先进行调用析构函数然后在进行空间的释放。但是delete是根据具体的delete对象的类型去调用析构函数的,并不是根据该对象原先new的方式进行delete的。所以出现以上现象:delete p2时只会调用的就是Person类的析构函数,并不会调用Student的析构。

因为我们new的是Student对象,所以释放肯定是想着要释放Student对象的。但是delete是根据具体的delete对象的类型去调用析构函数的,是父类的指针就调用父类的析构,是子类的指针就调用子类的析构。而并不会根据你原先new的空间进行析构。但是我们创建的是子类的对象:因为实现了多态,所以父类的指针不仅仅是源于父类也可能经过子类赋值给父类的。所以此时析构的调用实际上就与我们意想中的不同了,这种情况就极有可能造成了内存泄漏。

 

解决:

首先我们要知道在子类中是不能显示调用析构函数的,其原因就是父子类的析构函数的函数名其实是被修饰过成一样的:destructor。而父子类的函数名相同则会构成隐藏。

所以可以通过以下方式在子类中进行显示的调用父类的析构函数:

Person::~Person();

其实C++这一设计(父子类的析构函数名相同)其实就是为了解决以上的问题,所以既然函数名相同,且都没有参数。那么通过virtual进行修饰的话,那么父子类的析构函数不就构成了重载吗。所以回到题目中delete p2,而p2指向的是子类的对象,那么不就会调用子类Student的析构函数吗。又因为父子类的继承关系,调用子类的析构结束以后不就也会调用父类的析构吗。

如下:

class Person {
public:
    Person()
    {
        cout << "Person()" << endl;
    }
    virtual ~Person()//virtual构成重写
    {
        cout << "~Person()" << endl;
    }
};
class Student : public Person {
public:
    Student()
    {
        cout << "Student()" << endl;
    }
    virtual ~Student()
    {
        cout << "~Student()" << endl;
    }
};
int main()
{
    Person* p = new Student;
    delete p;

    return 0;
}


 多态例题解析

class A
{
public:
    virtual void func(int val = 1)
    {
        cout << "A->" << val << endl;
    }
    virtual void test() //隐藏的this指针类型A*
    {
        func();
    }
};
class B : public A
{
public:
    void func(int val = 0)
    {
        cout << "B->" << val << endl;
    }
};
int main()
{
    B* p = new B;
    p->test();//p的类型是B*
    return 0;
}


解析:首先A类的test函数会被B类继承下来,但是B类并没有对test进行重写,而func函数在父子类之间形成了重写。

我们知道p的类型是B*的,然后调用了test函数父类的test函数。而test函数内部又调用了func()函数,但是别忘了test()成员函数内部有一个隐藏的this指针,类型是A*,所以在test内部调用func函数的过程就形成了父类对象的指针调用虚函数,同时虚函数进行了重写。此时就构成了多态的条件。

但最关键的一点是:子类在虚函数重写时重写的是函数内部的实现,而函数接口声明的部分是会从父类继承下来的(也就是会复用父类的函数接口参数)

所以参数部分就取决于A类的func()函数,而内部的实现就看B类的func()函数。

重写隐藏重载的区别 

函数重载:重载函数发生在同一作用域里,函数名相同,参数列表不同。

函数隐藏(重定义):发生在基类和派生类中,只要求函数名相同即可。

函数重写(覆盖):发生在基类和派生类中,且都为虚函数,同时函数名相同,函数参数列表相同,返回值相同(除了协变)


基类和派生类的同名函数不是重写就是隐藏(绝不是函数重载)。

认识抽象类 

在了解抽象类之前要知道什么是纯虚函数,纯虚函数就是在虚函数后面写上=0;而该纯虚函数所在的类就是抽象类。(包含纯虚函数的类就是抽象类)

抽象类的特点:不能实例化出对象,而且继承了抽象类的子类也不能实例化对象,除非该子类重写纯虚函数,才可以实例化类对象。

所以纯虚函数规范了派生类必须进行重写。故纯虚函数用于定义一个通用的接口,让不同的派生类去实现这个接口,以适应不同的具体需求。纯虚函数主要体现接口继承。 

而接口继承其实就是派生类继承了基类纯虚函数的接口,目的是为了重写达成多态。

class Shape //图形抽象类
{
public:
    virtual void draw() = 0;
    virtual double area() = 0;
};

class Circle : public Shape 
{
public:
    void draw() override 
    {
        // 绘制圆形的具体代码
    }
    double area() override 
    {
        // 计算圆形面积的具体代码
    }
};

class Rectangle: public Shape 
{
public:
    void draw() override 
    {
        // 绘制长方形的具体代码
    }
    double area() override 
    {
        // 计算长方形面积的具体代码
    }
};

多态的原理(X64)

虚函数表

虚函数是形成多态的重要条件,我们知道类的成员函数是存在代码段的,其实虚函数也不例外,但是虚函数的地址是单独被拿出来了,放到了一个虚函数表(相当于一个函数指针数组)当中,而一个有虚函数的类中不仅仅存放成员变量,还存放这个虚函数表的地址即虚表指针

在VS的X64环境下演示

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Func2()" << endl;
	}
	void Func3()//非虚函数
	{
		cout << "Func3()" << endl;
	}
private:
	int _b = 1;
};
int main()
{
	Base b;
	cout << sizeof(Base) << endl;
}

重写后的的虚函数表

 我们只知道,有成员虚函数就有虚函数表,在多态中我们知道虚函数的重写是构成多态的重要条件,下面我们就来看一下重写后的的虚函数表是什么样子:

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
	virtual void Func2()//非虚函数
	{
		cout << "Func2()" << endl;
	}
private:
	int _a = 1;
};
class Child:public Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 3;
};

首先我们能发现父子类的虚函数表不是共有的,是各自独有一份的。

父类的虚函数表就如之前演示的一样,因为父类中有两个虚函数,所以虚函数表中就有两个数据(虚函数指针)。而子类继承了父类,但是子类的func1()函数与父类的func1()函数构成重写,同时子类也会继承父类未重写的虚函数。子类的虚函数表就是上图的样子,虚函数表中子类的非重写虚函数继承了父类的非重写虚函数,所以函数指针不变。而对于重写的虚函数则会进行覆盖父类的虚函数,因此重写后的虚函数指针发生改变。所以可以说子类重写虚函数就相当于父类虚函数被子类覆盖。


所以言归正传,当我们将子类对象给父类的指针或引用时,此时父类对象所指向的内容就会发生改变,从而指向的就是子类那一部分空间(红框框部分),所以当父类对象调用重写后的虚函数时,其实访问就是子类的虚函数表指针,故而访问的就是子类的虚函数,所以就形成了多态。

 其实尽管子类中没有虚函数,全是继承父类的虚函数的情况下(父子类虚表存放的函数指针是一模一样的),父类也不会套用子类的虚表指针,也是单独存一份虚函数表指针。虚表指针不同,所以虚表也不同,但是虚表里面的虚函数指针是一模一样的。而且一个类无论创建多少个对象,所存储的的虚表指针都是一样的。    所以说一个类的所有对象都共用一张虚表

 

通过切片无法形成多态

根据前面的认识我们知道当一个类中有虚函数时,编译器会为该类创建一个虚函数表。虚函数表是一个存储虚函数指针的数组,每一个元素对应着一个虚函数。

首先我们要知道动态多态的实现是依靠对象中的虚表指针所指向的虚表。指向父类就调用父类的虚函数,指向子类就调用子类的虚函数。

首先假设我们有两个类,父类:Animal,子类Dog。当Dog d = Animal();时:

此时就会进行切片处理,将子类的成员拷贝给父类,但是虚表指针并不会拷贝过去。所以d对象在调用虚函数时,只会调用Dog类的成员虚函数,而不是Animal类。因为编译器在编译阶段,对于通过对象(而非指针或引用)调用虚函数的情况,会按照对象的静态类型来确定虚函数表的入口。因为 animal 的静态类型是 Animal,所以它的 vptr 会指向 Animal 类的虚函数表,即使它是从 Dog 对象切片得来的。其实说实话就是虚表指针并不会随着切片的过程中赋值给父类对象。因为非指针或引用的对象在编译阶段就已经通过对象的静态类型确定好了哪个类的虚表。还有就是因为如果简单的赋值切片的过程就会赋值虚表指针的话,那么仅通过够通过父类对象就会形成多态,那么后续的父类对象就无法保证父类的虚表。后续使用会十分紊乱。

所有虚函数都存在虚表中

我们知道如果一个类中有虚函数的话就存在虚函数表。所以而我们监视窗口中还可以看到类中是存放有一个指向虚表首元素地址的指针,虚表中存放的是虚函数的指针,所以当我们计算类的大小时要将虚指针也计算在内。

回到问题,我们先用监视窗口查看一下虚表的内容:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
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;
};

此时从监视窗口中可以看出Derive类中虚表只存放了两个虚函数一个是继承并重写的func1 虚函数,另一个是继承下来的func2 函数,但是为什么不见Derive类中的func3和func4呢。其实监视窗口只会展开部分内容,最终想查看详细信息还是直接看内存空间的内容:(X64平台)

 很明显全0的位置就是分界线的地方(仅猜测),而且虚表中存放了四个函数指针的值,自然知道分别是重写的func1继承的func2,以及原有的func3和func4.为了验证这一问题我们可以先初步的通过函数指针去调用对应的虚函数,如果调用没问题则证明我们的猜想是正确的,反之则是错误的:

typedef void(*VF)();//将无参的函数指针类型重命名成VF
void Print(VF* p)//该函数接收虚表指针,然后依次调用虚函数
{
	int i = 1;
	while (*p)//vs结束标记:函数指针内容是全0
	{
		printf("func%d = %p->",i++, *p);
		(*p)();//*p就是虚表里的函数指针,可以代替函数名直接调用函数
		p++;//跳过当前函数指向下一个函数
	}

}
int main()
{
	Base b;
	Derive d;
	Print((VF*)*(long long*)&b);//传虚表的地址
	cout << endl;
	Print((VF*)*(long long*)&d);
	//*(long long*)&b 是拿到该对象前八个字节的内容,也就是虚表指针
	//防止传参时类型不匹配,强转

 	return 0;
}

所以可以证明:一个类中所有的虚函数都会存在该类对应的虚函数表中

 多继承下的虚表

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};
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;
};

typedef void(*VF)();//将无参的函数指针类型重命名成VF
void Print(VF* p)//该函数是为了实现打印虚表内的函数指针并且直接调用
{
	int i = 1;
	while (*p)
	{
		printf("func%d = %p->", i++, *p);
		(*p)();//*p就是虚表里的函数指针,可以代替函数名直接调用函数
		p++;
	}

}

首先我们要了解,多继承的派生类中不是只有一个虚表。就拿上面的例子来说,Derive类同时先后继承了Base1类和Base2类,而Base1和Base2类都有各自对应的虚表,同时三个类都有同一个func1函数,被Derive继承以后也就构成了重写,所以说将两个基类的func1函数都进行了重写。但是问题是Derive类对象中会有几张虚表指针呢?而且Derive类对象的func3虚函数该存放到哪里呢?通过打印内存情况展示:

其实经过虚表内容的打印后,不难看出func3其实是放在第一个继承下来的虚表内部的。对于重写的func1函数,我们可以看到Derive类中对继承的两个Base类的func1函数都进行了重写。

所以说多继承中子类继承了几个父类(都有虚表指针)就有几个虚表指针,内存布局情况取决于继承顺序。

可以为虚函数吗?

1.构造函数可以是虚函数吗?

编译报错,因为对象中的虚函数表指针是在构造函数阶段才开始初始化的,虚函数调用要在虚表中去寻找函数指针,但此时虚表指针还未初始化。

2.inline函数可以是虚函数吗?

可以,符合内联的函数没有函数地址,是直接在调用处进行展开的。但是多态调用时会忽略inline的作用,只有普通调用inline才会起作用。所以准确说:内联函数不可以是虚函数,但是多态调用时会忽略inline,即不再是被当做内联函数。

3.析构函数可以是虚函数吗?

可以,而且最好是实现虚函数的重写,此时内存空间可以正确的进行释放。

4.静态函数可以是虚函数吗?

编译报错,因为虚函数实际是通过类内的虚表指针去访问的,而static修饰的静态成员函数没有this指针,所以没有虚表指针,也就无法访问虚表。


经典题目 

test_1

class A
{
public:

    A() 
        :m_iVal(0) 
    { 
        test();
    }
    virtual void func() 
    {
        cout << m_iVal << ' '; 
    }
    void test() 
    {
        func(); 
    }

public:
    int m_iVal;

};

class B : public A
{
public:
    B() 
    {
        test();
    }
    virtual void func()
    {
        ++m_iVal;
        cout << m_iVal << ' ';
    }

};
int main(int argc, char* argv[])
{
    A* p = new B;
    p->test();
    return 0;
}

 

在 A 类的构造函数中调用 test() 函数,而 test() 函数又调用了虚函数 func()。当创建 B 类的对象时,会先调用 A 的构造函数,此时 B 类对象的虚表指针(vptr)还未被正确设置为指向 B 类的虚函数表即虚函数表指针还未初始化),因为在 A 的构造函数中,对象被认为是 A 类对象,其虚表指针指向 A 类的虚函数表。因此在 A 的构造函数中调用 test() 并调用 func() 时,会调用 A::func() 而不是 B::func()。此后开始调用类的构造函数,此时 B 类对象的虚表指针(vptr)已经被正确设置为指向 B 类的虚函数表,所以之后的调用就符合多态。

所以说:虚函数指针是在对象构造阶段才开始初始化,而虚表是在编译阶段就已经生成(存储在代码段)

 test_2

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

};

class B : public A
{
private://私有限定符
	virtual void f()
	{
		cout << "B::f()" << endl;
	}
};
int main()
{
	A* pa = (A*)new B;
	pa->f();
	B b;

	return 0;
}

多态不会受到访问限定符的限制。

在C++中,访问限定符(如public、private、protected)用于控制类中成员的访问权限。

但是,在多态中,派生类必须能够访问基类中的虚函数,以便实现多态性,无论这个虚函数的访问限定符是什么派生类都可以通过继承方式获取基类中的所有成员函数,包括私有成员函数。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu安装搜狗输入法

Ubuntu安装搜狗输入法 官网完整安装步骤调整默认输入法 官网 搜狗输入法 - 官网&#xff1a;https://shurufa.sogou.com/ 搜狗输入法Linux版 - 首页&#xff1a;https://shurufa.sogou.com/linux 搜狗输入法Linux版 - 安装指南&#xff1a;https://shurufa.sogou.com/linux/gu…

ElasticSearch下

DSL查询 叶子查询&#xff1a;在特定字段里查询特定值&#xff0c;属于简单查询&#xff0c;很少单独使用复合查询&#xff1a;以逻辑方式组合多个叶子查询或更改叶子查询的行为方式 在查询后还可以对查询结果做处理&#xff1a; 排序&#xff1a;按照1个或多个字段做排序分页…

【个人开发】nginx域名映射及ssl证书配置踩坑记录

1. 背景 买了个域名&#xff0c;申请了ssl证书&#xff0c;买都买了&#xff0c;不得部署点东西。于是开始一堆踩坑之旅。 2. 踩过的坑 2.1 报错1:域名访问 Invalid Host header 项目是一个简单的vue项目&#xff0c;启动方式如下 npm run serve但浏览器出现报错&#xff…

Go-Zero整合Goose实现MySQL数据库版本管理

推荐阅读 【系列好文】go-zero从入门到精通&#xff08;看了就会&#xff09; 教程地址&#xff1a;https://blog.csdn.net/u011019141/article/details/139619172 Go-Zero整合Goose实现MySQL数据库版本管理的教程 在开发中&#xff0c;数据库迁移和版本管理是必不可少的工作。…

每日一题洛谷P1427 小鱼的数字游戏c++

#include<iostream> using namespace std; int main() {long long s[100] { 0 };int i 0;while (1) {cin >> s[i];if (s[i]0) {break;}i;}for (i; i > 0;i--) {if(s[i]!0)cout << s[i] << " ";}return 0; }

力扣 子集

回溯基础&#xff0c;一题多解&#xff0c;不同的回朔过程。 题目 求子集中&#xff0c;数组的每种元素有选与不选两种状态。因此在使用dfs与回溯时把每一个元素分别进行选与不选的情况考虑即可。可以先用dfs跳过当前元素即不选然后一直深层挖下去&#xff0c;直到挖到最深了即…

宇泰串口卡驱动在Ubuntu22.04编译、安装汇总

从官网下载驱动官网地址 上传到Ubuntu, 目录结构如下&#xff1a; 驱动源代码: 驱动代码是基于开源项目编译来的 编译路径不能有中文路径&#xff0c;否则可能有类似错误 源码是基于Linux2.3内核编译&#xff0c;我当前是6.8.0-51&#xff0c;数据结构有升级&#xff0c;需要调…

LSA更新、撤销

LSA的新旧判断&#xff1a; 1.seq&#xff0c;值越大越优先 2.chksum&#xff0c;值越大越优先 3.age&#xff0c;本地的LSA age和收到的LSA age作比较 如果差值<900s&#xff0c;认为age一致&#xff0c;保留本地的&#xff1a;我本地有一条LSA是100 你给的是400 差值小于…

运行fastGPT 第四步 配置ONE API 添加模型

上次已经装好了所有的依赖和程序。 下面在网页中配置One API &#xff0c;这个是大模型的接口。配置好了之后&#xff0c;就可以配置fastGPT了。 打开 OneAPI 页面 添加模型 这里要添加具体的付费模型的API接口填进来。 可以通过ip:3001访问OneAPI后台&#xff0c;**默认账号…

强化学习-蒙特卡洛方法

强化学习-数学理论 强化学习-基本概念强化学习-贝尔曼公式强化学习-贝尔曼最优公式强化学习-值迭代与策略迭代强化学习-蒙特卡洛方法 文章目录 强化学习-数学理论一、蒙特卡洛方法理论(Monte Carlo, MC)二、MC Basic2.1 算法拆解2.2 MC Basic算法 三、MC Exploring Starts3.1 …

【专题一 递归】21. 合并两个有序链表

1.题目解析 2.讲解算法原理 解法:递归-> 重复的子问题 重复子问题 ->函数头的设计 合并两个有序链表--->Node dfs(l1&#xff0c;l2) 只关心某一个子问题在做什么事情 ->函数体的设计 比大小l1→next dfs( l1.next, l2)return l1 递归的出口 if(l1null)return l2…

企业级NoSQL数据库Redis

1.浏览器缓存过期机制 1.1 最后修改时间 last-modified 浏览器缓存机制是优化网页加载速度和减少服务器负载的重要手段。以下是关于浏览器缓存过期机制、Last-Modified 和 ETag 的详细讲解: 一、Last-Modified 头部 定义:Last-Modified 表示服务器上资源的最后修改时间。 作…

FPGA车牌识别

基于FPGA的车牌识别主要包含以下几个步骤&#xff1a;图像采集、颜色空间转换、边缘检测、形态学处理&#xff08;腐蚀和膨胀&#xff09;、特征值提取、模板匹配、结果显示。先用matlab对原理进行仿真&#xff0c;后用vivado和modelsim进行设计和仿真。 一、1.图像采集采用ov…

客户案例:致远OA与携程商旅集成方案

一、前言 本项目原型客户公司创建于1992年,主要生产并销售包括糖果系列、巧克力系列、烘焙系列、卤制品系列4大类,200多款产品。公司具有行业领先的生产能力,拥有各类生产线100条,年产能超过10万吨。同时,经过30年的发展,公司积累了完善的销售网络,核心经销商已经超过1200个,超…

怎么修复损坏的U盘?而且不用格式化的方式!

当你插入U盘时&#xff0c;若电脑弹出“需要格式化才能使用”提示&#xff0c;且无法打开或读取其中的数据&#xff0c;说明U盘极有可能已经损坏。除此之外&#xff0c;若电脑在连接U盘后显示以下信息&#xff0c;也可能意味着U盘出现问题&#xff0c;需要修复损坏的U盘&#x…

贪心算法(题1)区间选点

输出 2 #include <iostream> #include<algorithm>using namespace std;const int N 100010 ;int n; struct Range {int l,r;bool operator <(const Range &W)const{return r<W.r;} }range[N];int main() {scanf("%d",&n);for(int i0;i&l…

2.使用Spring BootSpring AI快速构建AI应用程序

Spring AI 是基于 Spring Boot3.x 框架构建&#xff0c;Spring Boot官方提供了非常便捷的工具Spring Initializr帮助开发者快速的搭建Spring Boot应用程序,IDEA也集成了此工具。本文使用的开发工具IDEASpring Boot 3.4Spring AI 1.0.0-SNAPSHOTMaven。 1.创建Spring Boot项目 …

【Linux】Socket编程-TCP构建自己的C++服务器

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; Socket 编程 TCP &#x1f98b; TCP socket API 详解&#x1f98b; 多线程远程命令执行&#x1f98b; 网络版计算器&#xff08;应用层自定义协议与序列化…

森林网络部署,工业4G路由器实现林区组网远程监控

在广袤无垠的林区&#xff0c;每一片树叶的摇曳、每一丝空气的流动&#xff0c;都关乎着生态的平衡与安宁。林区监控正以强大的力量&#xff0c;为这片绿色家园筑起一道坚固的防线。 工业 4G 路由器作为林区监控组网的守护者&#xff0c;凭借着卓越的通讯性能&#xff0c;突破…

数据库管理-第284期 奇怪的sys.user$授权(20250116)

数据库管理284期 20245-01-16 数据库管理-第284期 奇怪的sys.user$授权&#xff08;20250116&#xff09;1 问题2 CDB与PDB3 跨实例3.1 通过scanip访问数据库3.2 通过节点1的VIP访问数据库3.3 通过节点3的VIP访问数据库3.4 在节点3赋权后测试3.5 小结 总结 数据库管理-第284期 …