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

[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 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类 。

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

相关文章:

[C++基础]-多态

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 本期学习目标&am…...

【Kubernetes】当K8s出现问题时,我们可以从哪些方面排查出

前言 kubernetes&#xff0c;简称K8s&#xff0c;是用8代替名字中间的8个字符“ubernete”而成的缩写。是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes的目标是让部署容器化的应用简单并且高效&#xff08;powerful&#xff09;,Kub…...

SentenceTransformer 之论文解读

摘要 原文标题&#xff1a;Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks 链接&#xff1a;https://arxiv.org/pdf/1908.10084.pdf 尽管Bert和RoBERTa在句子对回归任务上&#xff0c;例如语义文本相似度&#xff08;Semantic Text Similarity&#xff09;…...

AI发展历史

一、AI的发展历史 二、AI发展的第五阶段 &#xff08;一&#xff09;、第一阶段 1.艾伦图灵与模仿游戏 艾伦•图灵&#xff08;Alan Turing&#xff0c;1912~1954&#xff09;是英国数学家、逻辑学家&#xff0c;被称为计算机科学之父&#xff0c;人工智能之父。二战中协助军…...

想要精通算法和SQL的成长之路 - 简化路径

想要精通算法和SQL的成长之路 - 简化路径 前言一. 简化路径 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 简化路径 原题连接 思路如下&#xff1a; 我们根据 "/" 去拆分字符串&#xff0c;得到每个子目录。这里拿到的子目录可能是空字符串&#xff0c;需要…...

【哈士奇赠书活动 - 41期】- 〖产品设计软技能:创业公司篇〗

文章目录 ⭐️ 赠书 - 《产品设计软技能&#xff1a;创业公司篇》⭐️ 内容简介⭐️ 作者简介⭐️ 编辑推荐⭐️ 赠书活动 → 获奖名单 ⭐️ 赠书 - 《产品设计软技能&#xff1a;创业公司篇》 ⭐️ 内容简介 在创业公司设计产品与在成熟公司设计产品存在明显差异。《产品设计软…...

MARS: An Instance-aware, Modular and Realistic Simulator for Autonomous Driving

MARS: An Instance-aware, Modular and Realistic Simulator for Autonomous Driving&#xff08;基于神经辐射场的自动驾驶仿真器&#xff09;https://github.com/OPEN-AIR-SUN/marshttps://arxiv.org/pdf/2307.15058.pdfhttps://mp.weixin.qq.com/s/6Ion_DZGJwzs8JOoWMMbPw …...

关联规则挖掘(上):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…...

centos7 + citus12 + postgresql 14 安装

1 安装及编译 yum install -y centos-release-scl-rh epel-release yum update -y yum groupinstall -y Development Tools yum install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm yum install -y postg…...

MySQL、Oracle、SQL Server / MS Access 中的 NULL函数用法

一、MySQL&#xff1a; isnull(exper) 判断exper是否为空&#xff0c;是则返回1&#xff0c;否则返回0 ifnull(exper1,exper2)判断exper1是否为空&#xff0c;是则用exper2代替 nullif(exper1,exper2)如果expr1 expr2 成立&#xff0c;那么返回值为NULL&#xff0c;否则返回值…...

App Store审核被拒原因与解决方案

为了避免不必要的上线延迟及成本增加&#xff0c;了解App Store审核被拒的常见原因以及对应的解决方案是开发人员以及营销人员的必修课。 CSDN相关的解决方案 App Store审核被拒的12个常见原因与快速过审解决方法...

​LeetCode解法汇总121. 买卖股票的最佳时机

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 描述&#xff1a; 给定一个数…...

【Go】go-es统计接口被刷数和ip访问来源

go-es模块统计日志中接口被刷数和ip访问来源 以下是使用go的web框架gin作为后端&#xff0c;展示的统计页面 背景 上面的数据来自elk日志统计。因为elk通过kibana进行展示&#xff0c;但是kibana有一定学习成本且不太能满足定制化的需求&#xff0c;所以考虑用编程的方式…...

debian 安装 pg --chatGpt

pgt: 要在Debian上安装PostgreSQL (通常缩写为PG) 数据库&#xff0c;您可以使用apt包管理器来执行安装操作。以下是安装PostgreSQL的步骤&#xff1a; 1. 打开终端。 2. 使用sudo权限以管理员身份运行以下命令&#xff0c;以更新包信息并安装PostgreSQL&#xff1a; bash …...

商城小程序代客下单程序开发演示

一款专为传统电商、实体商家开发的商城系统小程序&#xff0c;做私域、做留存、做社交必备功能全都有。 1、丰富的营销玩法&#xff1a;拼团、秒杀、定金预售、分销、社区团购、积分商城、支付有礼等主流获客玩法都有。 2、强大的会员体系&#xff1a;普通会员、付费会员、会…...

SpringBoot 整合 jetcache缓存

目前 jetcache 支持的本地缓存方案有两种&#xff0c;远程缓存支持两种&#xff0c;分别如下&#xff1a; 本地缓存&#xff08;Local&#xff09; LinkedHashMapCaffeine 远程缓存&#xff08;Remote&#xff09; Redis Tair 依赖导入 <dependency><groupId>…...

HTML5+CSS3+移动web 前端开发入门笔记(二)HTML标签详解

HTML标签&#xff1a;排版标签 排版标签用于对网页内容进行布局和样式的调整。下面是对常见排版标签的详细介绍&#xff1a; <h1>: 定义一级标题&#xff0c;通常用于标题栏或页面主要内容的标题。<p>: 定义段落&#xff0c;用于将文字分段展示&#xff0c;段落之…...

Maven 配置阿里云镜像

1. 查找maven setting.xml配置文件 find / -name "setting.xml" 2. 添加阿里云镜像 修改maven根目录下的conf文件夹中的setting.xml文件中的mirrors下添加mirror标签 <settings> <localRepository>E:\Maven\repository</localRepository> <…...

矢量图绘制软件EazyDraw mac中文版软件介绍

EazyDraw mac是一款功能强大且易于使用的矢量绘图软件。 EazyDraw mac软件介绍 矢量绘图工具&#xff1a;EazyDraw 提供了一套全面的矢量绘图工具&#xff0c;包括直线、曲线、多边形、文本框、图形填充等。用户可以使用这些工具创建和编辑精确的矢量图形&#xff0c;无论是简…...

Cocos Creator3.8 项目实战(四)巧用九宫格图像拉伸

一、为什么要使用九宫格图像拉伸 相信做过前端的同学都知道&#xff0c;ui &#xff08;图片&#xff09;资源对包体大小和内存都有非常直接的影响。 通常ui 资源都是图片&#xff0c;也是最占资源量的资源类型&#xff0c;游戏中的ui 资源还是人机交互的最重要的部分&#xff…...

怎么使用jenkins设置web自动打包

在Jenkins中设置Web自动打包需要完成以下步骤&#xff1a; 1.环境基础 安装Jenkins&#xff1a;首先&#xff0c;你需要在服务器上安装Jenkins。 你可以从Jenkins官网下载Jenkins的安装包&#xff0c;并按照官方指导进行安装。 2.使用jenkins设置web自动打包步骤 创建Jenk…...

完美解决 flex 实现一行三个,显示多行,左对齐

效果图 代码 <body><section class"content"><div class"item">元素</div><div class"item">元素</div><div class"item">元素</div><div class"item">元素</di…...

初识Spring

目录 1.Spring 基础 2.传统程序开发 3.IoC程序开发&#xff08;解耦&#xff09; 4.DI 4.1 IoC 和 DI 有什么区别 1.Spring 基础 Spring 指的是 Spring Framework&#xff08;Spring 框架&#xff09;&#xff0c;它是⼀个开源框架&#xff0c;有着活跃⽽庞⼤的社区&…...

Mybatis 使用参数时$与#的区别

之前我们介绍了mybatis中参数的使用&#xff0c;本篇我们在此基础上介绍Mybatis中使用参数时$与#的区别。 如果您对mybatis中参数的使用不太了解&#xff0c;建议您先进行了解后再阅读本篇&#xff0c;可以参考&#xff1a; Mybatis参数(parameterType)https://blog.csdn.net…...

java基本数据类型和包装类型区别

...

解锁Spring Boot的强大配置功能:@ConfigurationProperties与@PropertySources详解

解锁Spring Boot的强大配置功能&#xff1a;ConfigurationProperties与PropertySources详解 前言什么是ConfigurationProperties和PropertySourcesConfigurationProperties的使用步骤 1: 创建 Java POJO 类步骤 2: 配置类步骤 3: 配置文件步骤 4: 注入配置属性 PropertySources…...

Java和Vue字符串加密

字符串加密 AES 加密算法 在 Java 中&#xff0c;可以使用不同的加密算法来对字符串进行加密。以下是使用 AES 加密算法的示例代码&#xff0c;演示如何对一个字符串进行加密&#xff1a; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java…...

Java:java版结巴分词:jieba-analysis

文档 https://github.com/huaban/jieba-analysishttps://mvnrepository.com/artifact/com.huaban/jieba-analysis 依赖 <!-- https://mvnrepository.com/artifact/com.huaban/jieba-analysis --> <dependency><groupId>com.huaban</groupId><art…...

java生成一个符合密码学和安全性的随机秘钥

有时 我们在生成token 或者完成某种加密形式时会需要一个秘钥 但是 有些时候 项目开发并没有规定用什么秘钥 但是 秘钥都是要有一定格式规范的 我们可以通过以下代码生成一个随机秘钥 import java.security.SecureRandom; import java.util.Base64;public class TokenGenerat…...

C++ - 右值引用 和 移动拷贝

右值引用 我们先来了解什么是左值&#xff0c;什么是右值&#xff1a; 左值 和 有值 区分 首先&#xff0c;左值 和 右值 并不是完全意味着 在 "" 左边的就是 左值 &#xff1b; 在 "" 右边的就是右值。这是不一定的。只能说&#xff0c;在左边的大概率是…...

项目成员积分规则

在当下的项目/团队管理种&#xff0c;如何让成员能清晰的看到&#xff0c;自己的工作、努力在团队种属于那个段位&#xff0c;通过这个形式&#xff0c;并配合其他方式去点燃成员的进步之心。以积分的形式&#xff0c;代替绩效考核&#xff0c;一些零散的想法&#xff0c;欢迎各…...

Linux CentOS7 vim多窗口编辑

我们在用vim编辑文件时&#xff0c;有各种需求。如有时需要在多个文件之间来回操作&#xff0c;一会关闭一个文件&#xff0c;一会再打开另外一个文件&#xff0c;这样来回操作显得太笨拙。有时&#xff0c;vim编辑多行的大文件&#xff0c;来回查看、编辑前面一部分及最后一部…...

git使用,一点点

查看自己有没有安装git git --version 如果没有安装请执行sudo yum install -y git来安装 git 指令 git log 查看日志 git pull 同步远端和本地仓库 这就是冲突的报错&#xff1a; 所以这个时候你要同步一下git pull...

第五章:最新版零基础学习 PYTHON 教程—Python 字符串操作指南(第八节 - 如何在 C/C++、Python 和 Java 中分割字符串?)

通过某些分隔符分割字符串是一项非常常见的任务。例如,我们有一个文件中以逗号分隔的项目列表,并且我们希望数组中包含各个项目。 几乎所有编程语言都提供按某些分隔符分割字符串的函数。 目录 在C中: 在 C++ 中 方法一:使用C++的stringstream API...

【Java】语法特性篇

语法特性篇 Java对象的比较 1. 对象比较的问题 Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。那为什么可以比较&#xff1f; 因为&#xff1a;对于用户实现自定义类型&#xff0c;都默认继承自Object类&#xff0c;而Object类中提供了equal方法&#xf…...

Vim教程

目录 vim 介绍 常用的四种模式 首先先学会如何正确进入和退出vim&#xff1a; normal模式 insert模式&#xff1a; command模式&#xff1a; v-block模式&#xff1a; vim异常退出 vim配置 vim 介绍 Vim是一款高度可定制的文本编辑器&#xff0c;它的前身是Vi&#xf…...

selenium查找网页如何处理网站资源一直加载非常卡或者失败的情况

selenium查找网页如何处理网站资源一直加载失败的情况 selenium获取一个网页&#xff0c;某个网页的资源卡了很久还没有加载成功&#xff0c;如何放弃这个卡的数据&#xff0c;继续往下走 有2钟方式。通常可以采用下面的方式一来处理这种情况 方式一、WebDriverWait 这种方式…...

并发工具类库使用的常见问题

一、ThreadLocal在多线程环境中没有清理 由于ThreadLocal是和线程绑定的&#xff0c;如果线程被复用了&#xff0c;也即使用了线程池&#xff0c;那么ThreadLocal中的值是可能被复用的&#xff0c;这个特性如果是开发者没有预料到的&#xff0c;那么会产生很大的问题。例如&am…...

GD32F10X ----RTC

1. RTC的简介 STM32 的实时时钟&#xff08;RTC&#xff09;是一个独立的定时器。STM32 的 RTC 模块拥有一组连续计数的计数器&#xff0c;在相应软件配置下&#xff0c;可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。 RTC 模块和时钟配置…...

使用UiPath和AA构建的解决方案 1. 机器人过程自动化入门

你好!在这本系列,我们将指导您完成一些真实世界的机器人过程自动化(RPA)项目。感谢您的加入。当你完成本系列时,你将具备使用UiPath和Automation Anywhere在最低指导下进行简单到中等复杂度RPA项目的知识、技术和心态。 RPA是一项令人兴奋的新技术,被视为使用新的先进技…...

rust字面量

字面量就是值。值最终必须编码成二进制存储在某块内存上。 变量与字面量的关系就像杯子和水的关系。 字面量是有类型的。字面量类型有&#xff1a;布尔、数字、文本、字节 一、布尔 类型是bool true false 二、数字 通过后缀表示类型 通过前缀表示进制 通过 “_” 来分割数…...

Unix Network Programming Episode 79

‘gai_strerror’ Function The nonzero error return values from getaddrinfo have the names and meanings shown in Figure 11.7. The function gai_strerror takes one of these values as an argument and returns a pointer to the corresponding error string. #incl…...

Cesium展示——wkt 数据绘制

文章目录 需求分析1. 第一步,数据类型转换2. 第二步,数据渲染需求 WKT 是什么:WKT 简介 在这里,我选择将 Cesium 中将wkt数据转化为geoJSON格式后渲染至地球上 分析 1. 第一步,数据类型转换 npm install terraformer-wkt-parser --savelet wkts =...

打造完美家庭空间,让生活更加舒适

在现代繁忙的都市生活中&#xff0c;家是人们温暖而舒适的避风港。而如何打造一个恰到好处的家庭空间&#xff0c;成为了许多人心中的追求。今天&#xff0c;就让我们来探索一些空间布局方案&#xff0c;为您的家庭营造一个完美融合功能与美感的舒适空间。 &#x1f3e0;&…...

解决loadDep:omelette: sill install loadAllDepsIntoIdealTree

报错信息如下&#xff1a; 解决方案&#xff1a; 1、设置为淘宝的镜像源 npm config set registry https://registry.npm.taobao.org 2、 命令检验是否成功 npm config get registry 3、继续运行npm install即可 npm install 运行效果&#xff1a;...

【深蓝学院】手写VIO第2章--IMU传感器--作业

这次作业坑很多&#xff0c;作业说明的不清楚&#xff0c;摸索了很长时间才将此次作业完成&#xff0c;在这里进行记录。 1. T1 1.1 题干 1.2 解答 1.2.1 法1&#xff0c;ros related方法 不知道为什么我的launch不了&#xff0c;在imu_utils目录下面建立build后&#xff0…...

Android多线程学习:线程

一、概念 进程&#xff1a;系统资源分配的基本单位&#xff0c;进程之间相互独立&#xff0c;不能直接访问其他进程的地址空间。 线程&#xff1a;CPU调度的基本单位&#xff0c;线程之间共享所在进程的资源&#xff0c;包括共享内存&#xff0c;公有数据&#xff0c;全局变量…...

canvas 入门

canvas 入门 canvas是干什么的&#xff1f;canvas 绘制直线canvas画虚线canvas 绘制三角形canvas 绘制正方形canvas 绘制圆形、圆弧与椭圆canvas绘制文本canvas绘制图片 canvas是干什么的&#xff1f; <canvas> 是HTML5中的标签&#xff0c;它是一个容器&#xff0c;可以…...

建议收藏!混迹职场多年总结出的8大技巧!

1. 不要吃“哑巴”亏&#xff1a;不管在什么企业&#xff0c;一定要“会说话”&#xff0c;敢于表达自己&#xff0c;但是又兼顾身边人的感受&#xff0c;考虑好自己的言行将会带来的后果。良好的沟通技巧对于在职场中建立良好的人际关系和解决问题至关重要。学会倾听、表达和理…...

OpenCV4(C++)—— 视频和摄像头的加载、显示与保存

文章目录 一、加载与显示二、保存 一、加载与显示 视频或摄像头的加载是使用 cv::VideoCapture 类。&#xff08;这个类和 ifstream 类比较相似&#xff0c;视频或摄像头的加载和文本文件操作是大致相同。主要步骤&#xff1a;&#xff08;1&#xff09;加载&#xff08;打开&a…...