如何给网站划分栏目,新昌建设局网站,高防服务器服务,免费行情软件app网站不下载一、多态
概念
多态#xff0c;就是多种状态#xff0c;即不同的对象去完成同一个行为时会产生出不同的状态。比如#xff1a;买票时#xff0c;成人要原价买#xff0c;学生和老人就可以享受优惠价便宜一点儿。同样是买票这个行为#xff0c;不同的对象来做就有不同的…一、多态
概念
多态就是多种状态即不同的对象去完成同一个行为时会产生出不同的状态。比如买票时成人要原价买学生和老人就可以享受优惠价便宜一点儿。同样是买票这个行为不同的对象来做就有不同的状态这就是多态的一种体现。 从代码实现上来说多态指的是通过一个父类指针 or引用调用一个虚函数时会根据具体对象的类型来调用该虚函数的不同实现。在多态中相同的操作可以作用于不同的对象而具体执行的操作则取决于对不同对象的类型判断。“看人下菜碟” 那怎么构成多态呢有两个条件 1.子类重写父类的虚函数 2.通过父类的指针或引用去调用虚函数 估计你现在是一头雾水啥是虚函数啥是重写多态到底是怎么用的
不急你现在对多态的概念一定还是一片混沌。下面我先讲解“虚函数”、“重写”的概念然后举出多态的代码实例你才能体会什么是多态。看到后面再回过头来看多态的概念会有更透彻的理解。 虚函数
先来学习一个知识点“虚函数”注意和菱形继承那里的 虚继承 是两个完全不同的概念它俩的关系就是金鱼和自行车之间的关系。 虚函数被virtual修饰的类成员函数称为虚函数。
虚函数必须是非静态的成员函数非成员函数和静态函数是无法成为虚函数的。
class Person {
public:virtual void BuyTicket() { …… }
};
虚函数的作用是实现多态机制。 重写覆盖
若子类定义了一个和父类的虚函数 一模一样的虚函数那么称子类中的虚函数重写 / 覆盖了父类的虚函数。拥有相同的名字、返回值、形参列表
重写是针对虚函数的概念。普通函数是没有重写的说法的。
例
class Person
{
public:virtual void BuyTicket() {cout 成人票10r endl;}
};
class Student:public Person
{
public:virtual void BuyTicket() { //子类中的虚函数重写了父类的虚函数cout 学生票5r endl;}
}; 其实在重写基类虚函数时派生类的虚函数即使 不加virtual关键字也是可以构成重写的。
这是因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性。
但是该种写法不规范不建议这样使用。我们还是老老实实加上virtual吧。 如何构成多态 构成多态的两个条件1.子类重写父类的虚函数 2.通过父类的指针或引用去调用虚函数 我们已经有了“虚函数”和“重写”的知识储备现在我来写一个多态的例子
#includeiostream
using namespace std;
class Person
{
public:virtual void BuyTicket() {cout 成人票10r endl;}
};
class Student:public Person
{
public:virtual void BuyTicket() { //条件1.子类重写父类的虚函数cout 学生票5r endl; }
};
void Pay(Person p) { p.BuyTicket(); //条件2.通过父类的引用去调用虚函数
}
int main() {Person p;Student s;//用引用调用Pay(p);Pay(s);cout endl;//用指针调用Person* p1 p, * p2 s; //条件2.通过父类的指针去调用虚函数p1-BuyTicket();p2-BuyTicket();return 0;
} 这就是多态 Q
为什么调虚函数 一定得是父类的指针or引用子类不行吗
因为父类不仅能接收父类的值还能接收子类的值而子类仅能接收子类的值不能接收父类的值。也就是说父类更能包罗。
为什么一定得通过指针or引用调用
这个问题先搁置一下。等讲到多态的底层原理时再说。 重写的例外协变
这个知识点实际上不常用但是笔试题会考
协变是函数重写的一种特殊情况这种情况下父类和子类的虚函数的返回值是不同的
父类的虚函数返回父类对象的指针or引用子类的虚函数返回子类对象的指针or引用。这种情况就称为“协变”。 协变的前提是协变的两个类型必须是父子关系。 例
class Person
{
public:virtual Person BuyTicket() { //注意返回类型Person p;cout 成人票10r endl;return p;}
};
class Student:public Person
{
public:virtual Student BuyTicket() { //注意返回类型Student s;cout 学生票5r endl;return s;}
};
int main() {Person p;Student s;Person* p1 p, * p2 s;p1-BuyTicket();p2-BuyTicket();return 0;
} 既然存在返回值协变的情况那说明构成多态的虚函数 的返回类型未必相同。这是常见的考点。 析构函数的重写
析构函数是需要重写的。
➡️为什么呢来看下面这种特殊的情况
//当析构函数不是虚函数也未被重写时
class A
{
public:~A() { cout ~A() endl;}
};
class B:public A
{
public:~B() {cout ~B() endl;}
};
int main() {A* pa new B; delete pa; //我希望调用的是B的析构函数return 0;
} 当我用一个父类的指针去指向子类的对象时我希望 程序能根据对象的类型去调用对应的析构函数也就是~B()。
但实际上这里并没有实现多态程序会老老实实根据pa的类型去调用~A()。而这样由于B中部分成员未被释放如果这部分成员涉及资源管理的话就会导致内存泄露。
所以析构函数得实现多态。
这种特定情况需要记住面试时经常会问到这个问题 ➡️但问题又来了实现多态的前提是 子类重写父类的虚函数而这俩的析构函数名字都不一样怎么能重写呢
这就不用我们担心了编译器会出手。可以理解为编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor我们只要加上virtual就可以。 正确的写法
class A
{
public:virtual ~A() {cout ~A() endl;}
};
class B:public A
{
public:virtual ~B() {cout ~B() endl;}
};
int main() {A* pa new B;delete pa;return 0;
} 关键字final(C11)
final“最终的”表示这个就是最终版后继无人了。
final的两个功能1. 修饰虚函数表示该虚函数不能再被重写 2. 修饰类让此类不能被继承
//修饰虚函数
class A
{
public:virtual void print() final { ……}
};
class B:public A
{
public:…… //不能继承print函数
}; 之前我们说过要想一个类不能被继承那就把它的构造函数私有化。实际上这种方式并不好。因为从继承关系来说它并没有直接阻止继承这种行为子类依旧是继承了父类。只是创建创建不了子类对象。这是一种间接的阻止。
C11引入的final就可以直接阻止只要父类加了final那就是不可被继承的
class A final
{
public:……
};
class B:public A //会报错不能将final类类型作为基类
{
public:……
}; 关键字override(C11)
override: 检查子类虚函数 是否正确地完成重写如果没有则编译报错。 有时候我们会不小心把函数名写错或者父子的参数列表顺序不一致导致无法构成重写。这种错误编译器往往检查不出来当运行出错误的结果时我们去Debug半天才无语地发现原来bug在这。
而override的出现就是帮助我们去检查错误确保重写的正确免得我们调试找BUG。
class A
{
public:virtual void print() {……}
};
class B:public A
{
public:void pint() { //这里把函数名写错了但编译器没检查出来……}
}; 当加了override以后
class B:public A
{
public:void pint() override { ……}
}; 接口继承和实现继承
普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。
虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口。因为目的是重写达成多态所以只需要继承接口函数体就用不着了。
所以说如果不实现多态不要把函数定义成虚函数。 重载、重写、重定义的对比 二、抽象类
概念
在虚函数的后面写上 0 则这个函数为纯虚函数纯虚函数是不需要实现的。
包含纯虚函数的类叫做抽象类也叫接口类抽象类不能实例化出对象只能被继承。
派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。
class Course //课程是抽象类
{
public:virtual void TeachingMethod() 0; //纯虚函数
};
class Math:public Course
{
public:virtual void TeachingMethod() {cout Math endl;}
};
int main() {//Course c; //报错抽象类无法实例化出对象Math m; //派生类进行了重写可以实例化出对象return 0;
} 纯虚函数的作用是强制子类去完成重写。
抽象类用于表示抽象的类型。实际中许多概念是抽象的没有对应的实体。比如“植物” “形状”这是笼统的概念只有把“植物”具体到“含羞草”把“形状”具体到“三角形”才能定义出一个具体的类型。