C++基础语法——多态

目录

1.什么是多态?

2.多态的定义与实现

①多态的构成条件

②虚函数

③虚函数的重写

1.协变

2.析构函数的重写

④final和override

1.final

2.override

⑤重载、重写(覆盖)与重定义(隐藏)的区别

3.什么是抽象类

4.多态的底层原理

①虚函数表

②多态的原理与底层

③动态绑定与静态绑定

5.继承关系中的虚表

①单继承中的虚表

②多继承中的虚表

6.一些继承与多态中的问题

①概念题

参考答案与解析:

1. A  

2. D   

3. C   

4. A   

5. B

6. D

7. D

8. A   

9. C   

10. B

②问答题

参考答案与解析:

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.


1.什么是多态?

多态是面向对象编程中的一个概念,它允许不同的对象对同一个消息作出不同的响应。简单来说,多态是指同一种操作或方法可以在不同的对象上产生不同的行为。这种灵活性使得代码更加可扩展和可维护。在多态中,对象的类型可以是其父类或接口的类型,而实际执行的方法则是对象自身的方法。这样,通过多态性,我们可以在不改变代码的情况下,通过替换对象的类型来改变程序的行为。

举个例子,在买票时,同样是买票,不同的人买票有不同的结果,比如普通人去买得到的结果就是全价,而学生去买得到的结果就是半价,退伍军人去买则是免费。 

2.多态的定义与实现

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了
Person,Person对象买票全价,Student对象买票半价。代码实现如下

class Person 
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};class Student : public Person 
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
};void Func1(Person& p)
{p.BuyTicket();
}void Func2(Person* p)
{p->BuyTicket();
}int main()
{Person ps;Student st;Func1(ps);Func1(st);Func2(&ps);Func2(&st);return 0;
}

①多态的构成条件

在继承中要构成多态需要两个条件:

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

②虚函数

虚函数:

即被virtual修饰的类成员函数称为虚函数。

如 

class Person 
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

中的BuyTicket函数 

③虚函数的重写

虚函数的重写(覆盖):

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

(注:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用)即在派生类中可以如此书写

void BuyTicket() 
{ cout << "买票-半价" << endl; 
}

但是在重写的时候有几个例外:

1.协变

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

class A 
{};class B : public A 
{};class Person 
{
public:virtual A* f() { return new A; }
};class Student : public Person 
{
public:virtual B* f() { return new B;}
};

2.析构函数的重写

为什么需要对析构函数进行重写呢?我们先来看下面这个例子

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

在这里由于切片,p1和p2指针指向的都是基类这个部分,他指向的有可能是派生类的一部分,也有可能就是基类,那么在释放空间的时候就无法保证做到先析构派生类再析构基类。因此,只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。而为了构成多态,编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。此外,如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。

④final和override

不难发现,C++对于重写的要求是比较严格的,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写:

1.final

修饰虚函数,表示该虚函数不能再被重写,意思也很明显,就是“最后”的意思,即这之后不能再对该函数进行重写

以下面的代码为例

class Car
{
public:virtual void Drive() final {}
};class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};

 观察有

2.override

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

 以下面的代码为例

class Car 
{
public:void Drive() {}
};class Benz :public Car 
{
public:virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

 观察有

⑤重载、重写(覆盖)与重定义(隐藏)的区别

3.什么是抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。

以下面的代码为例

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}int main()
{Test();return 0;
}

 抽象类的设计强制重写虚函数,更加体现了接口封装这一特性(即只使用接口而不需要知道内部的操作)。

4.多态的底层原理

①虚函数表

在多态这一部分常常会考下面这类题

//问:sizeof(Base)?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};

在32位平台下我们运行发现 那么这是为什么呢?我们经过调试可以发现

在b1这个对象中,除了_b这个成员外,还有一个_vfptr指针,这个指针我们称之为虚函数表指针(vf表示virtual function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表(注:不要与虚基表混淆)。我们再往后分析,既然基类有虚基表指针,那么它的派生类又如何呢?虚表与虚表指针有何作用?当函数变多时虚表中该如何存放?针对这些问题我们对上述代码作出一些改进

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;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}

首先我们观察d,

可以发现

1. d中也有虚表指针,但是这个指针指向的虚表与父类不同,且可以看到在各自的虚表中

2. d对象的Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,因此虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法

3. 而Func2继承下来后是虚函数,因此放进了虚表,Func3也继承下来了,但不是虚函数,所以不会放进虚表,而放进代码段

4. 还可以看到_vfptr本质上是一个函数指针数组,一般而言会在最后一个位置放上nullptr

5. 在这里我们便可以对派生类生成的虚表做一个总结:

        a.先将基类中的虚表内容拷贝一份到派生类虚表中 

        b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 

        c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

在这里我们还要探究一个具体的问题,就是虚函数与虚表存放的位置是哪里?首先我们要明白,虚函数与一般的成员函数类似都是存放在代码段的,只是这些虚函数的指针被取出存放在了虚表中,那我们具体要如何验证呢?以以下代码为例

int main()
{Base b;Derive d;// 栈区int a = 0;printf("栈区:%p\n", &a);// 堆区int* pc = new int[10];printf("堆区:%p\n", pc);// 静态区static int c = 1;printf("静态区:%p\n", &c);// 常量区const char* ch = "hello world";printf("常量区:%p\n", ch);// 虚表// &d:取得Derive类对象d的地址//(int*)&d:将Derive类对象的地址强制转换为int* 类型的指针//*((int*)&d):对强制转换后的指针进行解引用操作,获取指针所指向的整数值,也就是对象中虚函数表的起始地址printf("d虚表地址:%p\n", *((int*)&d));printf("b虚表地址:%p\n", *((int*)&b));return 0;
}

 在vs中运行可以发现

在g++运行有

那么我们基本可以确定虚表和虚函数类似,就存放在常量区(即代码段位置)

②多态的原理与底层

那我们分析了这么多,那多态到底是怎么做到传Student调用Student的函数,传Person调用Person的函数呢?以下列代码为例

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;Func(Mike);Student Johnson;Func(Johnson);return 0;
}

1. 观察上图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
2. 观察上图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中
找到虚函数是Student::BuyTicket。
3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

至此,我们就可以明确形成多态的条件:

1. 父类的指针或引用(注:不能是父类对象,因为如果使用切片拷贝,不会拷贝虚表,因为如果拷贝虚表的话,在其他地方使用父类时会产生bug)

2. 虚函数的重写

那么我们再来看看多态的底层,观察它到底是如何运行的,以以下代码为例

class Base 
{
public:virtual void func1() { cout << "Base::func1" << endl; }
private:int b1;
};class Derive :public Base
{
public:virtual void func1() { cout << "Derive::func1" << endl; }
private:int d1;
};int main()
{Base b;Derive d;Base* ptr1 = &b;ptr1->func1();Base* ptr2 = &d;ptr2->func1();return 0;
}

可以看到

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

③动态绑定与静态绑定

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

5.继承关系中的虚表

①单继承中的虚表

我们以下列代码为例

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;
};int main()
{Base b;Derive d;return 0;
}

在调试模式中我们可以看到

可以看到,这里的d对象中只有func1与func2函数,可以认为这里出了一点小bug,我们用以下代码来验证

typedef void(*vfptr)();void PrintVTable(vfptr* vTable)
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout << " 虚表地址: " << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);vfptr f = vTable[i];f();}cout << endl;
}int main()
{Base b;Derive d;// 思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质// 是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr// 1.先取d的地址,强转成一个int*的指针// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针// 3.再强转成vfptr*,因为虚表就是一个存vfptr类型(虚函数指针类型)的数组。// 4.虚表指针传递给PrintVTable进行打印虚表// 注:需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处// 理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需// 要点目录栏的 - 生成 - 清理解决方案,再编译就好了。vfptr* pd = (vfptr*)(*(int*)&d);vfptr* pb = (vfptr*)(*(int*)&b);PrintVTable(pb);PrintVTable(pd);return 0;
}

运行如下

可以发现,编译器确实是自动隐藏了Derive后面的几个虚函数

②多继承中的虚表

那么在知道了单继承的虚表后,多继承的虚表又是如何体现的呢?我们以以下代码为例

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(*vfptr) ();
void PrintVTable(vfptr* vTable)
{cout << " 虚表地址:" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);vfptr f = vTable[i];f();}cout << endl;
}int main()
{Derive d;// 找到d中的Base1,打印它的虚表vfptr* vTableb1 = (vfptr*)(*(int*)&d);PrintVTable(vTableb1);// 找到d中的Base2,打印它的虚表vfptr* vTableb2 = (vfptr*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTableb2);return 0;
}

运行结果如下

可以看出,多继承派生类的未重写的虚函数放在第一个继承基类部分的虚表中,而非放在两个虚表中

6.一些继承与多态中的问题

①概念题

1. 下面哪种面向对象的方法可以让你变得富有( ) 
A: 继承 B: 封装 C: 多态 D: 抽象


2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,
而对方法的调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定


3. 面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复
用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动
态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封
装性的表现


4. 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类
C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数


5. 关于虚函数的描述正确的是( )
A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数
C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数 


6. 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表


7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B:A类对象和B类对象前4个字节存储的都是虚基表的地址
C:A类对象和B类对象前4个字节存储的虚表地址相同
D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
 

8. 下面程序输出结果是什么? ()

#include<iostream>using namespace std;class A 
{
public:A(const char* s) { cout << s << endl; }~A() {}
};class B :virtual public A
{
public:B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};class C :virtual public A
{
public:C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};class D :public B, public C
{
public:D(const char* s1, const char* s2, const char* s3, const char* s4):B(s1, s2), C(s1, s3), A(s1){cout << s4 << endl;}
};int main() 
{D* p = new D("class A", "class B", "class C", "class D");delete p;return 0;
}

A:class A class B class C class D      B:class D class B class C class A
C:class D class C class B class A      D:class A class C class B class D

9. 下面说法正确的是( )

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main() 
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

A:p1 == p2 == p3   B:p1 < p2 < p3 

C:p1 == p3 != p2    D:p1 != p2 != p3
 

10. 以下程序输出结果是什么()

class A
{
public:virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void test() { func(); }
};class B : public A
{
public:void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: 编译出错 F: 以上都不正确
 

参考答案与解析:

1. A  

继承是在已有的基类上做出改进,进而产生派生类,因此可以变得富有

2. D   

根据描述我们可以知道这里描述的就是多态,根据答案最贴切的就是D选项

3. C   

优先使用组合,而不是继承,是面向对象设计的第二原则

4. A   

B:声明纯虚函数的类是抽象类

C:子类可以不实现基类的纯虚函数

D:纯虚函数可以有具体的实现,也可以为空函数,具体实现取决于需求和设计。

5. B

B:内联函数在编译前就已经展开,无法再将此函数的地址存放在虚表中

A:派生类的虚函数与基类的虚函数不一定具有不同的参数个数和类型

C:派生类可以不重新定义基类的虚函数

D:虚函数有this指针,而static函数没有this指针,因此虚函数不能是static型的函数

6. D

A:一个类可能有多张虚表,如一个多继承类

B:就算不重写也会生成不同虚表,如下图所示

C:虚表是在编译期间动态生成的

// 代码示例:
class Base
{
public:virtual void func1() { cout << "Base::func1" << endl; };
private:int b;
};class Derive : public Base
{
public:virtual void func2() { cout << "Derive::func2" << endl; };
private:int d;
};int main()
{Base b;Derive d;return 0;
}

观察有

7. D

A:A类对象的前4个字节存储虚表地址,B类对象前4个字节也是是虚表地址

B:A类对象和B类对象前4个字节存储的都是虚表的地址
C:A类对象和B类对象前4个字节存储的虚表地址不同,因为B类对象重写了虚表

8. A   

运行结果如下

理解:

在构造D类对象时,先构造B类与C类对象,而在构造B类与C类对象时,又会先构造A类对象,因此最后的结果如图所示

9. C   

理解如图所示

首先由于切片,p1指向了d中的Base1的地址,p2指向了d中的Base2的地址,p3则直接指向d的地址,而由于继承关系,Base1的地址与d的首地址重合,因此最终可以得到p1 == p2 != p3

10. B

经过运行可以得到如下结果

理解:

这里是B类对A中的func函数进行了重写,但是重写仅仅只是重写了实现,即调用的是A类的func函数,因此val为1,实现是B类的func函数,因此最后显示B->

②问答题

1. 什么是多态?
2. 什么是重载、重写(覆盖)、重定义(隐藏)?
3. 多态的实现原理?
4. inline函数可以是虚函数吗?
5. 静态成员可以是虚函数吗?
6. 构造函数可以是虚函数吗?
7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
8. 对象访问普通函数快还是虚函数更快?
9. 虚函数表是在什么阶段生成的,存在哪的?
10. C++菱形继承的问题?虚继承的原理?
11. 什么是抽象类?抽象类的作用?

参考答案与解析:

1.

答:多态分为静态多态与动态多态。静态多态指的是函数重载,而动态多态指的是继承中派生类对虚函数的重写和父类指针(引用)的调用。

2.

答:重载是在同一作用域中,函数名相同,而参数或返回值不同;重写是在继承中,基类中某一函数为虚函数,而派生类中也有一个同名虚函数,且函数参数,返回值都与基类中的函数相同;重定义是在继承中,基类与派生类为同名函数,那么派生类就重定义了基类的函数。

3.

答:静态多态是根据C++进行编译时的函数名修饰规则形成不同的符号表来实现的,而动态多态是根据虚函数表来实现的。

4.

答:可以是虚函数,但是这个函数会默认地忽略掉inline的属性,因为如果要是inline函数会在编译时展开,这样就无法将虚函数存放到虚表中了。

5.

答:不能,因为静态函数没有this指针,在使用类::函数时,无法调用虚表,因此无法是虚函数。

6.

答:不能,因为虚表指针是在构造函数的初始化列表阶段产生的。

7.

答:可以,而且最好在基类中就将析构函数设置为虚函数,在如下场景Per为基类,Stu为子类,

Per* p = Stu s; delete p; 这里如果没有将析构函数设置为虚函数,那么最后会导致p不知道自己该调用哪个析构函数。

8.

答:如果是普通对象的调用,那么调用速度没有什么变化;如果是父类的指针(或引用)的调用,那么普通函数的调用速度更快,因为调用虚函数时会在虚函数表中去寻找。

9.

答:在编译阶段,正常情况存储在代码段(常量区)。

10.

答:菱形继承时会造成数据冗余和二义性;虚继承通过形成虚基表来存储偏移量,借此解决数据冗余的问题。

11.

答:抽象类是包含了纯虚函数在内的类;抽象类要求了在派生类中重写纯虚函数,体现出了接口继承关系。

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

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

相关文章

QTday3

1.保存文件 void Widget::on_save_clicked() {QString filename QFileDialog::getSaveFileName(this,"选择文件","F:\\study\\huaqing\\save","Text File(*.txt)");if(filename.isNull()){QMessageBox::information(this,"提示",&qu…

苹果macOS 14开发者预览版Beta 7发布 新增超过100款视频壁纸和屏保

8 月 31 日&#xff0c;苹果向 Mac 电脑用户推送了 macOS 14 开发者预览版 Beta 7 更新&#xff08;内部版本号&#xff1a;23A5337a&#xff09;&#xff0c;本次更新距离上次发布隔了 8 天。 苹果发布 Beta 7 更新的同时&#xff0c;还发布了第 6 个公测版&#xff0c;正式版…

性能测试(测试系列10)

目录 前言&#xff1a; 1.什么是性能测试 1.1生活中遇到的软件问题 1.2性能测试的定义 1.3性能测试和功能测试有什么区别 1.4性能的好坏的区分 1.5影响一个软件性能的因素 2.为什么要进行性能测试 3.性能测试常见的术语以及衡量指标 3.1并发 3.2用户数 3.3响应时间 …

神经网络--感知机

感知机 单层感知机原理 单层感知机:解决二分类问题&#xff0c;激活函数一般使用sign函数,基于误分类点到超平面的距离总和来构造损失函数,由损失函数推导出模型中损失函数对参数 w w w和 b b b的梯度&#xff0c;利用梯度下降法从而进行参数更新。让1代表A类&#xff0c;0代…

关于异或的小疑惑

今天写c&#xff0c;当我写出如下代码时&#xff0c;编译器报错了 #include<bits/stdc.h>using namespace std;int main(){int a1,b3,c2,d6;// cout<<(a^b^c^d)<<endl;cout<<a^b^c^d<<endl;return 0; } D:\sublineText\demo\demo.cpp: In funct…

C++ do...while 循环

不像 for 和 while 循环&#xff0c;它们是在循环头部测试循环条件。do…while 循环是在循环的尾部检查它的条件。 do…while 循环与 while 循环类似&#xff0c;但是 do…while 循环会确保至少执行一次循环。 语法 C 中 do…while 循环的语法&#xff1a; do {statement(s…

煤矿监管电子封条算法

煤矿监管电子封条算法基于yolov5网络模型深度学习框架&#xff0c;先进技术的创新举措&#xff0c;煤矿监管电子封条算法通过在现场运料运人井口、回风井口、车辆出入口等关键位置进行人员进出、人数变化和设备开停等情况的识别和分析。YOLO检测速度非常快。标准版本的YOLO可以…

MongoDB 的简介

MongoDB 趋势 对于 MongoDB 的认识 Q&A QA什么是 MongoDB&#xff1f; 一个以 JSON 为数据模型的文档数据库一个以 JSON 为数据模型的文档数据库文档来自于“JSON Document”&#xff0c;并非我们一般理解的 PDF&#xff0c;WORD谁开发 MongDB&#xff1f; 上市公司 MongoD…

SpringBoot核心原理与实践

第一章、SpringBoot简介 1、入门案例 2、官网创建压缩包程序 注意使用的版本pom文件中java --> 1.8、 springboot --> 2.5.0 3、SpringBoot快速启动 运行程序--找引导类 换技术、加技术--加starter 第二章、基础配置 1、配置文件格式 《1、端口号配置》 《2、将目录文…

DOCKER 部署 webman项目

# 设置基础镜像 FROM php:8.2-fpm# 安装必要的软件包和依赖项 RUN apt-get update && apt-get install -y \nginx \libzip-dev \libpng-dev \libjpeg-dev \libfreetype6-dev \&& rm -rf /var/lib/apt/lists/*# 安装 PHP 扩展 RUN docker-php-ext-configure gd …

leetcode 84. 柱状图中最大的矩形

2023.8.30 本题和接雨水 有点类似&#xff0c;依旧用双指针来解。但是本题要记录的是当前柱子 左右两侧第一个小于该柱子的索引。将其保存在两个数组中&#xff0c;最后再求最大面积。代码如下&#xff1a; class Solution { public:int largestRectangleArea(vector<int&g…

STM32f103入门(10)ADC模数转换器

ADC模数转换器 ADC简介AD单通道初始化代码编写第一步开启时钟第二步 RCCCLK分频 6分频 72M/612M第三步 配置GPIO 配置为AIN状态第四步&#xff0c;选择规则组的输入通道第五步 用结构体 初始化ADC第六步 对ADC进行校准编写获取电压函数初始化代码如下 Main函数编写 ADC简介 ADC…

AcWing 4405. 统计子矩阵(每日一题)

如果你觉得这篇题解对你有用&#xff0c;可以点点关注再走呗~ 题目描述 给定一个 NM 的矩阵 A&#xff0c;请你统计有多少个子矩阵 (最小 11&#xff0c;最大 NM) 满足子矩阵中所有数的和不超过给定的整数 K ? 输入格式 第一行包含三个整数 N,M 和 K。 之后 N 行每行包含 …

软件过程模型

软件过程模型 软件过程模型习惯上称为软件开发模型&#xff0c;它是软件开发全部过程、活动和任务的结构框架。典型的软件过程有瀑布模型、增量模型、演化模型&#xff08;原型模型、螺旋模型&#xff09;、喷泉模型、基于构件的开发模型和形式化方法模型等。 1. 瀑布模型 瀑…

DRM全解析 —— ADD_FB(2)

接前一篇文章&#xff1a;DRM全解析 —— ADD_FB&#xff08;1&#xff09; 本文参考以下博文&#xff1a; DRM驱动&#xff08;四&#xff09;之ADD_FB 特此致谢&#xff01; 上一回围绕libdrm与DRM在Linux内核中的接口&#xff1a; DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, d…

抽象轻松c语言

目 c语言 c程序 c语言的核心在于语言&#xff0c;语言的作用是进行沟通&#xff0c;人与人之间的信息交换 人与人之间的信息交换是会有信息空白&#xff08;A表达信息&#xff0c;B接受信息&#xff0c;B对信息的处理会与A所以表达的信息具有差距&#xff0c;这段差距称为信…

同步与互斥

硬件指令 实现互斥&#xff1a;硬件指令&#xff0c;硬件实现的原子操作&#xff0c;不会被打断 tsl指令和xchg指令 当前指令执行完&#xff0c;才会检测中断 If the signal comes while an instruction is being executed, it is held until the execution of the instructi…

使用SpaceDesk连接平板作为电脑副屏详细步骤教程

文章目录 下载安装PC端安装安卓端安装 配置步骤PC端安卓端 连接 SpaceDesk官网链接https://www.spacedesk.net/ (应该是需要科学上网才能进入) SpaceDesk它可以连接安卓,苹果的平板,手机等&#xff0c;也可以连接其他可以打开网页&#xff08;HTML5&#xff09;的设备。 这里我…

JDK8安装及系统变量配置(包含错误处理)

jdk安装 一.下载JDK二.安装三.配置系统变量四.可能遇到的问题1.显示已经安装的问题 或者 读取注册表项值失败2.原因3.解决 五.验证安装成功 一.下载JDK JDK下载官网 二.安装 双击之后&#xff0c;一直下一步就ok 三.配置系统变量 1.找到配置系统变量的地方 2.配置系统变…

千纸鹤APP云验证系统源码 APK注入引流弹窗

千纸鹤APP云验证系统是一款全面的验证系统&#xff0c;包括网络验证、APK注入、注册机、引流弹窗、更新弹窗等功能。该系统提供完整的源代码&#xff0c;方便开发者二次开发和定制化需求。 可以对用户进行多种验证&#xff0c;包括账号密码验证、短信验证码验证等。该系统还提供…