当前位置: 首页 > 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…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

.Net框架,除了EF还有很多很多......

文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...