[C++基础]-多态

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

本期学习目标:认识什么是多态, 认识抽象类,理解多态的原理,理解多承和多态常见的面试问题。 

目录

一、认识多态

1、什么是多态

2、虚函数

3、多态的定义

4、多态中虚函数的二种特殊情况

二、抽象类

1、概念

2、 接口继承和实现继承

三、多态的原理

1、虚函数表

2、多态原理剖析

3、单继承和多继承关系的虚函数表

四、多态的其他知识

1、C++11 override 和 final

2、重载、覆盖(重写)、隐藏(重定义)的对比

3、动态绑定与静态绑定

五、分享继承和多态常见的面试问题


一、认识多态

1、什么是多态

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

举个例子来说:某宝常年到大学的开学季就会举办扫描领红包活动,但是我们发现每个领到的金额都不同,而且相对来是非活跃用户红包金额更大,这种我们不同角色,分到吧同金额的红包我们就可以称为多态。

2、虚函数

在认识多态前,我们首先要认识一一下什么是虚函数:

在类中被关键字virtual修饰的函数我们就称为虚函数,就如red_packet函数,在继承中我用关键字virtual解决的菱形继承问题,这里要注意区分二则的用法,一个是在继承这作用于类,一个是在多态中作用于函数。

class Peson
{virtual void red_packet(){cout << "小额红包" << endl;}
protected:string _name;int _age;
};

3、多态的定义

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

1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

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

下面我们来看一段代码:

class Person
{
public://虚函数virtual void red_packet(){cout << "普通红包" << endl;}
protected:string _name;int _age;
};class  active_people:public Person
{
public:virtual void red_packet(){cout << "小额红包" << endl;}
};void fun(Peson& p)
{p.red_packet();
}
int main()
{Person p;active_people ap;fun(p);fun(ap);return 0;
}

 

这里我们通过父类的引用形成了多态。

特别注意子类函数可以不加victual,但父类必须加victual才构成虚函数

4、多态中虚函数的二种特殊情况

我们都知道,要构成多态就用就要满足构成多态的二个条件:其中被调用的函数必须是虚函数,也就是说必须加是virtual。

但是存在二种特殊情况也构成多态:

析构函数(函数名字不同构成的重写)

class Person
{
public:~Person(){cout << " Person delete" << endl;delete[]_p;}
protected:int* _p = new int[10];};class  active_people:public Person
{
public:~active_people(){cout << " active_people delete" << endl;delete[]_d;}
protected:int* _d = new int[15];};int main()
{Person p;active_people ap;return 0;
}

通过这段代码我们可以看出,父子类在调用析构函数的时候,是先调用子类的析构函数,子类的析构函数调用完成后,子类会自动调用父类的析构函数。 

但是如果我对代码进行一下更改:

int main()
{Person* ptr1 = new Person;Person* ptr2 = new active_people;delete ptr1;delete ptr2;return 0;
}

这里我们发现子类并没有被析构掉。为什么呢?

这就得提一下delete的特性:

1:使用指针调用析构

2:operator delete(ptr) 

也就是说这时候是一个普通调用 (是什么类型就调用什么用的析构函数),所以才只会调用父类的析构,但这样的行为不是我们期望的(存在内存泄露),其实,我们期望应该是一个多态调用,指向父类调用父类的析构,指向子类调用子类的析构函数,只要我们在父类的析构函数上加上virtual就可以解决这个问题了。

virtual ~Person()
{cout << " Person delete" << endl;delete[]_p;
}

就是为了让父类的析构函数为虚函数: 

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
理,编译后析构函数的名称统一处理成destructor。

协变(基类与派生类虚函数返回值类型不同

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

举例:

class A
{};class B : public A
{};
class Person
{
public:virtual A* red_packet(){cout << "普通红包" << endl;return nullptr;}
};class  active_people :public Person
{
public:virtual B* red_packet(){cout << "小额红包" << endl;return nullptr;}
};void fun(Person& p)
{p.red_packet();
}
int main()
{Person p;active_people ap;fun(p);fun(ap);return 0;
}

 这里我们只要简单知道在协变这要求:三同中,返回值可以不同,但是要求返回值必须是一个父子类关系的指针或者引用

二、抽象类

1、概念

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

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

那这有什么用呢?

其实就是强制我们重写子类的虚函数

2、 接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。

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

三、多态的原理

1、虚函数表

在了解虚函数表前,我们先来看一道笔试题

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;char _ch;
};

我想不少同学会认为是8,可能认为 成员函数存放在公共的代码段,我们就只要计算成员变量大小就可以了,根据内存对齐就可以得到大小为8个字节。

但真的是这样吗?

我们打印出类的大小:

cout << sizeof(Base) << endl;

为什么是16个字节呢?

int main()
{Base b;cout << sizeof(b) << endl;
}

这时我们进行调试打开监视窗口 

发现b对象中存放了_vfptr的东西,这又是什么呢?

其实是一个指针, 对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

所以说根据内存对其很容易得到类的大小为16。

那我们在思考一个问题虚函数表到底存放在哪里呢?

下面我们通过一份代码大致推断一下:

int main()
{int a = 0;printf("栈:%08lxp \n",& a);const char* str = "hello world";printf("代码段 / 常量区:%08lxp \n", str);static int b = 0;printf("静态区/数据段: %08lxp\n", &b);//取类这前4个字节的地址,也就是虚表地址Base be;printf("虚表: %08lxp\n", *((int*)&be));}

通过打印我们发现虚表位于代码段/常量区中。 

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;char _ch;
};class Derive : public Base
{
public://子类重写的虚函数virtual void Func1(){cout << "Derive::Func1()" << endl;}//非虚函数,重定义void Func3(){cout << "Derive::Func3()" << endl;}
private:int _d = 2;
};

虚表当中存储的就是虚函数的地址,因为父类当中的Func1和Func2都是虚函数,所以父类对象b的虚表当中存储的就是虚函数Func1和Func2的地址。

而子类虽然继承了父类的虚函数Func1和Func2,但是子类对父类的虚函数Func1进行了重写,因此,子类对象d的虚表当中存储的是父类的虚函数Func2的地址和重写的Func1的地址。这就是为什么虚函数的重写也叫做覆盖,覆盖就是指虚表中虚函数地址的覆盖,重写是语法的叫法,覆盖是原理层的叫法。

其次需要注意的是:Func2是虚函数,所以继承下来后放进了子类的虚表,而Func3是普通成员函数,继承下来后不会放进子类的虚表。此外,虚函数表本质是一个存虚函数指针的指针数组,一般情况下会在这个数组最后放一个nullptr。

那我们不由的思考到底虚表是那个阶段就开始初始化?

  • 其实虚表指针是在构造函数初始化列表的时候填入对象的,虚表是在编译的时候就生成了。
  • 虚表里面存放是虚函数地址,同普通函数一样编译完成就放在代码段这
  • 一个类这所有的虚函数,都会放在虚表这。
  • 子类会将父类的虚表拷贝一份,然后用重写的虚函数地址覆盖到原来虚表中的函数地址因此虚函数的重写,也叫虚函数的覆盖。

2、多态原理剖析

前面我们说了那么多,但是虚表是如何实现多态的呢?

class Person {
public://父类虚函数virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person* p)
{//通过调用父类的引用/指针,指向父类调用父类,指向子类调用子类p->BuyTicket();
}
int main()
{Person Mike;Student Johnson;Person* p1 = &Mike;Person* p2 = &Johnson;Func(p1);Func(p2);return 0;
}
  •  p1指针指向Mike对象,当我们调用Func函数,且将p1传给Func时,p1->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
  • p2指针指向johnson对象时p2->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket.
  •  这样就实现出了不同对象去完成同一行为时,展现出不同的形态

 我们都知道达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调
用虚函数,
但这是为什么呢?下面我们通过反汇编了解一下

void Func(Person* p)
{
p->BuyTicket();
}
int main()
{
Person mike;
Func(&mike);
mike.BuyTicket();
return 0;
}
// 以下汇编代码中跟你这个问题不相关的都被去掉了
void Func(Person* p)
{
...
p->BuyTicket();
// p中存的是mike对象的指针,将p移动到eax中
001940DE mov eax,dword ptr [p]
// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx
001940E1 mov edx,dword ptr [eax]
// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax
00B823EE mov eax,dword ptr [edx]
// call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来
以后到对象的中取找的。
001940EA call eax
00头1940EC cmp esi,esp
}
int main()
{
...
// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数的调
用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call 地址
mike.BuyTicket();
00195182 lea ecx,[mike]
00195185 call Person::BuyTicket (01914F6h)
...
}

看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的 .

3、单继承和多继承关系的虚函数表

单继承的虚函数表

这里没什么可以过多分析,上面我们都是多单继承虚表的分析,但是我们要注意一个现象。

class Base
{
public://父类虚函数virtual void fun1(){cout << "Base::fun1" << endl;}virtual void fun2(){cout << "Base::fun2" << endl;}
private:int a;
};class Derive :public Base
{//重写的虚函数virtual void fun1(){cout << "Derive::fun1" << endl;}//虚函数virtual void fun3(){cout << "Derive::fun3" << endl;}//普通函数void fun4(){cout << "DERIVE::fun4" << endl;}
private:int b;
};int main()
{Base b;Derive d;return 0;
}

这里虽然从监视窗口我们并没有从虚表中看到fun3虚函数的地址,但是我们通过查询虚表指针的地址,因为虚表本质是一个函数指针数组,我们可以发现fun3函数的地址是存在的,也就说明,只要是虚函数就会进虚表,但是vs编译器可能会对非重写的虚函数优化,从而在监视窗口中我们不能发现他。

为了更好的验证,我们对多继承关系的虚函数表的讨论,我们写一个函数来打印虚函数的地址:

//为函数指针数组重新取个名字
// 写法1函数指针数组
//void PrintVFTbale(VFPtr vft[], int n)
//{
//	for (int i = 0; i < n; ++i)
//	{
//		printf("[%d]:%p\n", i, vft[i]);
//	}
//}
typedef void(*VFPtr)();
//写法2
void PrintVFTbale(VFPtr vft[])
{for (int i = 0; vft[i] != nullptr; ++i){printf("[%d]:%p->", i, vft[i]);vft[i]();}cout << endl;

 

这里我们要注意,要想打印虚表,就要取类这前4个字节(32位平台)/前8个字节(64位平台), 

我们上面的取法是通用的,(void*)在32位平台4个字节,64位平台8个字节。而我们*(void**)就自然的找到了void*从而在不同平台上形成自适应。

多继承虚表函数(在vs2013上测试)

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://父类重写了func1virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }
private:int d1;
};// 函数指针数组
typedef void(*VFPtr)();
void PrintVFTbale(VFPtr vft[])
{for (int i = 0; vft[i] != nullptr; ++i){printf("[%d]:%p->", i, vft[i]);vft[i]();}cout << endl;
}int main()
{Base1 b1;Base2 b2;PrintVFTbale((VFPtr*)(*(void**)&b1));PrintVFTbale((VFPtr*)(*(void**)&b2));Derive d;PrintVFTbale((VFPtr*)(*(void**)&d));//打印父类的第二张虚表    写法1//PrintVFTbale((VFPtr*)(*(void**)((char*)&d+sizeof(Base1))));//写法2Base2* ptr2 = &d;PrintVFTbale((VFPtr*)(*(void**)ptr2));return 0;
}

  •  base1h和base2都有一张虚表,都被Derive继承(有二张虚表),但是没有被重写的func3通常是放在第一张虚表中的。
  • 我们在找父类的第二张虚表的时候,可以通过字节偏远的方法找到,也可以直接用    Base2* ptr2 = &d;切片的方式自动偏移。
  • 这里我们打印虚函数地址结束调条件是最后一个元素为nullptr,但是这种情况在大部分情况下是适应的,要注意的是,虚表的具体实现方式可能因编译器而异,不同的编译器可能会有不同的实现细节。因此,在特定的编译器和环境中,虚表的最后一个元素是否为nullptr可能会有所不同。但根据常见的编译器实现,将最后一个元素设为nullptr是一种常见的做法。

四、多态的其他知识

1、C++11 override 和 final

在学习override和final时,我们先思考一个问题如何实现一个不能被继承的类:

方法1:将构造函数私有(c++98)

class A
{
private:A(){}
};class B : public A
{};

这时候因为对象对无法建立,自然就无法被继承

方法2 类定义的时候加final

这时候我们称类为最终类,类不能被继承。

class A final
{};

其实final还有一个功能修饰函数,这该函数就不能被重写 

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

2、重载、覆盖(重写)、隐藏(重定义)的对比

重载

二个函数在同一个作用域

函数名相同/参数不同

重定义(隐藏)

二个函数分别在父类和子类的作用域

函数名相同

二个父类和子类的同名函数不构成重写就是重定义

重写(覆盖)

二个函数分别在父类和子类的作用域

函数名/参数/返回值必须相同(协变除外)

二个函数必须是虚函数

3、动态绑定与静态绑定

  • 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载.
  • 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

五、分享继承和多态常见的面试问题

1. 什么是多态

它指的是同一种操作或接口可以被不同类型的对象以不同的方式实现和处理的能力。具体来说,多态性使得我们可以使用基类(父类)的指针或引用来引用子类(派生类)的对象,并且根据具体的对象类型执行对应的方法或操作。

多态有两种表现形式:静态多态(编译时多态)和动态多态(运行时多态)。

  1. 静态多态:通过函数重载和运算符重载实现,编译器在编译阶段根据参数的静态类型决定调用哪个函数或操作符。

  2. 动态多态:通过虚函数和基类指针/引用实现,运行时根据对象的动态类型来确定调用哪个函数。即使使用基类指针或引用,也能够在运行时确定实际调用的是子类的方法。

2. 什么是重载、重写(覆盖)、重定义(隐藏)?

重载:同一作用域内,函数名相同参数不同
重写:子类和父类的虚函数,名称、返回值、参数都相同,称子类重写了父类的虚函数
重定义:子类和父类的函数名相同,称子类隐藏了父类的某个函数。

3. 多态的实现原理?

父类和子类之中保存的虚表指针是不一样的,通过传入指针或者引用(本质也是指针)确定去子类还是父类之中去寻找虚表指针,最后达到调用不同虚函数的目的。 

4. inline函数可以是虚函数吗

 可以,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去,如果不构成多态直接调用,则内联展开。在类里面定义的函数默认内联

5. 静态成员可以是虚函数吗

 不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

6. 构造函数可以是虚函数吗

 不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

 7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?

 可以,并且最好把基类的析构函数定义成虚函数。

通过基类指针或引用来管理派生类对象。如果基类中的析构函数不是虚函数,当通过基类指针或引用删除派生类对象时,可能只会调用到基类的析构函数,而不会调用派生类的析构函数,从而导致派生类可能存在资源泄漏或未被正确清理的问题

8. 对象访问普通函数快还是虚函数更快?

 先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。

9. 虚函数表是在什么阶段生成的,存在哪的?

 虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

10. C++菱形继承的问题?虚继承的原理? 

 菱形虚拟继承因为子类对象当中会有两份父类的成员,因此会导致数据冗余和二义性的问题。
虚继承对于相同的虚基类在对象当中只会存储一份,若要访问虚基类的成员需要通过虚基表获取到偏移量,进而找到对应的虚基类成员,从而解决了数据冗余和二义性的问题。

11.什么是抽象类?抽象类的作用?

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

抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

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

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

相关文章

数据库配置mysql5.7

1 创建数据库 """ 1.管理员连接数据库 mysql -uroot -proot2.创建数据库 create database hello default charsetutf8;3.查看用户 select user,host,password from mysql.user;# 5.7往后的版本 select user,host,authentication_string from mysql.user; "…

力扣-345.反转字符串中的元音字母

Idea 将s中的元音字母存在字符串sv中&#xff0c;并且使用一个数组依次存储元音字母的下标。 然后将字符串sv进行反转&#xff0c;并遍历元音下标数组&#xff0c;将反转后的字符串sv依次插入到源字符串s中 AC Code class Solution { public:string reverseVowels(string s) {…

【进阶C语言】数组笔试题解析

本节内容以刷题为主&#xff0c;大致目录&#xff1a; 1.一维数组 2.字符数组 3.二维数组 学完后&#xff0c;你将对数组有了更全面的认识 在刷关于数组的题目前&#xff0c;我们先认识一下数组名&#xff1a; 数组名的意义&#xff1a;表示数组首元素的地址 但是有两个例外…

强化学习环境 - robogym - 学习 - 3

强化学习环境 - robogym - 学习 - 3 文章目录 强化学习环境 - robogym - 学习 - 3项目地址为什么选择 robogymObservation - 观测信息Action - 动作信息Initialization - 初始状态设置 项目地址 https://github.com/openai/robogym 为什么选择 robogym 自己的项目需要做一些机…

微信小程序使用路由传参和传对象的方法

近期在做微信小程序开发&#xff0c;在页面跳转时&#xff0c;需要携带参数到下一个页面&#xff0c;尤其是将对象传入页面。为了方便重温&#xff0c;特此记录。 路由传字符串参数 原始页面 传递字符串参数比较简单。路由跳转有两种方式&#xff0c;一种是通过navigator组件…

企业AI工程化之路:如何实现高效、低成本、高质量的落地?

MLOps工程实践 概述面临挑战目的内容简介读者对象专家推荐目录 写在末尾&#xff1a; 主页传送门&#xff1a;&#x1f4c0; 传送 概述 作为计算机科学的一个重要领域&#xff0c;机器学习也是目前人工智能领域非常活跃的分支之一。机器学习通过分析海量数据、总结规律&#x…

Flask实现注册登录模块

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言1.…

机器学习之SGD, Batch, and Mini Batch的简单介绍

文章目录 总述SGD(Stochastic Gradient Descent)(随机梯度下降&#xff09;Batch &#xff08;批量&#xff09;mini Batch (迷你批量&#xff09; 总述 SGD, Batch, and Mini Batch是可用于神经网络的监督学习计算权重更新的方案&#xff0c;即∆wij。 SGD(Stochastic Gradi…

el-tree中插入图标并且带提示信息

<template><div class"left"><!-- default-expanded-keys 默认展开 --><!-- expand-on-click-node 只有点击箭头才会展开树 --><el-tree :data"list" :props"defaultProps" node-click"handleNodeClick" :…

c语言进阶部分详解(详细解析字符串常用函数,并进行模拟实现(下))

上篇文章介绍了一些常用的字符串函数&#xff0c;大家可以跳转过去浏览一下&#xff1a;c语言进阶部分详解&#xff08;详细解析字符串常用函数&#xff0c;并进行模拟实现&#xff08;上&#xff09;&#xff09;_总之就是非常唔姆的博客-CSDN博客 今天接着来介绍一些&#x…

Linux防火墙之--SNAT和DNAT

1.SNAT是什么 SNAT又称源地址转换。源地址转换是内网地址向外访问时&#xff0c;发起访问的内网ip地址转换为指定的ip地址&#xff08;可指定具体的服务以及相应的端口或端口范围&#xff09;&#xff0c;这可以使内网中使用保留ip地址的主机访问外部网络&#xff0c;即内网的多…

腾讯云轻量和CVM有啥区别?怎么选择服务器配置?

腾讯云轻量服务器和云服务器有什么区别&#xff1f;为什么轻量应用服务器价格便宜&#xff1f;是因为轻量服务器CPU内存性能比云服务器CVM性能差吗&#xff1f;轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程序或开发测试环境&#xff0c;云服务器CV…

TDengine OSS 与 qStudio 实现无缝协同,革新数据分析和管理方式

在数字化转型如火如荼的当下&#xff0c;海量爆发的时序数据处理成为转型成功的关键因素之一。为了帮助社区用户更好地进行数据分析和管理&#xff0c;丰富可视化解决方案的多样性&#xff0c;我们将开源的时序数据库&#xff08;Time Series Database&#xff09; TDengine OS…

一文看懂光模块的工作原理

你们好&#xff0c;我的网工朋友 光模块有很多类别&#xff0c;是我们经常要用到的PHY层器件。虽然封装&#xff0c;速率&#xff0c;传输距离有所不同&#xff0c;但是其内部组成基本是一致的。 以太网交换机常用的光模块有SFP&#xff0c;GBIC&#xff0c;XFP&#xff0c;X…

运行软件找不到mfc140u.dll怎么解决,mfc140u.dll是什么文件

"找不到 mfc140u.dll"是一条错误信息&#xff0c;表示您的计算机上缺少一个名为 mfc140u.dll 的动态链接库&#xff08;DLL&#xff09;文件。这个文件通常与 Microsoft Visual C Redistributable 相关。Mfc140u.dll 是 Microsoft 基础类库&#xff08;MFC&#xff0…

GD32F103x 定时器

1. 定时器的基本介绍 STM32的定时器主要分为三种&#xff1a;高级定时器、通用定时器、基本定时器。 即&#xff1a;高级定时器具有捕获/比较通道和互补输出&#xff0c;死区时间&#xff0c;通用定时器只有捕获/比较通道&#xff0c;基本定时器没有以上两者。 1. 基本定时…

【深度学习】UNIT-DDPM核心讲解

文章目录 大致介绍&#xff1a;扩散损失&#xff1a;转换损失&#xff1a;循环一致性损失&#xff1a;推理过程&#xff1a;优缺点&#xff1a; 参考文章&#xff1a; https://blog.csdn.net/ssshyeong/article/details/127210086 这篇文章对整个文章 UNIT-DDPM: UNpaired Imag…

Python为Excel中每一个单元格计算其在多个文件中的平均值

本文介绍基于Python语言&#xff0c;对大量不同的Excel文件加以跨文件、逐单元格平均值计算的方法。 首先&#xff0c;我们来明确一下本文的具体需求。现有一个文件夹&#xff0c;其中有如下所示的大量Excel文件&#xff0c;我们这里就以.csv文件为例来介绍。其中&#xff0c;每…

网工内推 | IT高级运维工程师,周末双休,包吃包住,14-20k

01 深圳朗特智能控制股份有限公司 招聘岗位&#xff1a;IT高级运维工程师 职责描述&#xff1a; 1、对集团网络基础架构的建设、运维、安全制定相关标准和准则&#xff1b; 2、负责集团数据中心、核心设备、信息安全的管理和运维&#xff1b; 3、执行网络、服务器、核心交换机…

21.6 CSS 弹性布局

1. 弹性盒子 CSS弹性盒子(Flexbox)是一种布局模型, 用于创建灵活的, 自适应的网页布局. 它的目的是在不同屏幕尺寸和设备上实现一致的布局效果.引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列, 对齐和分配空白空间.弹性容器通过设置display…