C++——多态——应试重点

专栏链接:《C++学习》、《Linux学习》
—
文章目录
- 一、初始多态
- 1.1多态的介绍
- 1.2虚函数
- 2.1多态的实现
- 3.1深剖多态实实现过程
- 3.2难题剖析
- ❀二、析构函数的重写
- 三、关于多态的补充
- 3.1关键字override和final
- ❀3.2重载、重写、隐藏对比
- 3.3协变
一、初始多态
1.1多态的介绍
多态分为静多态和动多态。
静态多态也叫做编译时多态,像之前的函数重载、函数模板就是静多态。之所以叫静多态是因为实参传给形参的这个过程在编译的时候就已经完成。静多态通过传递不同的参数就可以调用不同的函数。
ex:我传入一只猫就会构造猫叫的函数,但不会输出喵喵喵,传入一只狗就会构造狗叫的函数,但不会输出汪汪汪。
动多态主要是为了完成某个函数行为,传入不同的对象就会完成不同的行为。
ex:函数为叫声,我传入一个猫就是喵喵喵,我传入一个狗就是汪汪汪。



1.2虚函数
类成员函数前⾯加virtual修饰,那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修
饰。
class Person
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
2.1多态的实现
多态实现需要两个条件
1.虚函数的形参必须是基类指针或者引用 调用
2.被调用的必须是虚函数,虚函数已经完成重写/覆盖
重写覆盖的条件——三同
1.函数类型必须相同
2.形参类型和个数必须相同
3.返回类型必须相同
代码实现
//1.9多态实现2
#include<iostream>
using namespace std;//基类
class Animal
{
public://1.重写/覆盖构建virtual void talk(){cout << "~~~" << endl;}
};//派生类一、
class dog:public Animal
{
public:virtual void talk(){cout << "汪汪汪" << endl;}
};
//派生类二、
class cat:public Animal
{
public:virtual void talk(){cout << "喵喵喵" << endl;}
};//调用基类的&或*
void Func(Animal&p)
{p.talk();
}int main()
{//由p指向的对象决定cat c1;Func(c1);dog d1;Func(d1);return 0;
}
买票
//1.9
#include<iostream>
using namespace std;class Person
{
public:
//必须构成函数重写/覆盖virtual void buyticket(){cout << "买票全价" << endl;}
};class Student :public Person
{
public:void buyticket(){cout << "买票半价" << endl;}
};//参数必须是基类& 或者*
void Func(Person& p1)
{p1.buyticket();
}void Test()
{Student s1;Func(s1);Person s2;Func(s2);
}int main()
{Test();return 0;
}
3.1深剖多态实实现过程
1.继承构成



2.实现条件二、被调用的必须是虚函数,并完成重写/覆盖



3.实现条件一、虚函数必须是基类指针或者引用调用
实现条件一是固定写法,类型必须是基类

4.重难理解调用谁和传入的对象有关,我定义一个cat类型传入c1 那么调用cat类
我定义一个dog类型传入d1,那么调用dog类

3.2难题剖析
以下程序输出结果是什么()
A:A->0 B:B->1 C:A->1 D:B->0 E:编译出错F:以上都不正确
#include<iostream>
using namespace std;class A
{
public:virtual void func(int val = 1)
{cout << "A->" << val << endl;
}virtual void test() { func(); }
};
class B:public A
{void func(int val = 0){cout << "B->" << val << endl;
}
};int main()
{B* p = new B;p->test();return 0;
}
图解

正确答案为B
小结
1.多态调用时,父类函数名+子类实现
2.普通调用看类型,多态调用看传入的类型是什么(指向的对象)
❀二、析构函数的重写
当基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual,都与基类函数构成重写。
虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor
所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。
#include<iostream>
using namespace std;class A
{
public:virtual ~A(){cout << "~A()" << endl;}
};class B : public A {public:~B(){cout << "~B()->delete:" << _p << endl;delete _p;}protected:int* _p = new int[10];
};// 只有派⽣类Student的析构函数重写了Person的析构函数,下⾯的delete对象调⽤析构函数,才能
//构成多态,才能保证p1和p2指向的对象正确的调⽤析构函数。int main()
{A* p1 = new A;A* p2 = new B;delete p1;delete p2;return 0;
}
若去掉virtual 变为~A()


小结
当基类指针指向派生类对象时,若基类析构不是虚函数,delete 会仅调用基类析构,导致派生类中动态分配的资源(如堆内存)无法释放,造成内存泄漏。
因为 析构函数名本质上是相同的都是 destructor
三、关于多态的补充
3.1关键字override和final
override是一个检查函数名的关键字,final是一个不构成多态的关键字。final加在基类函数中。
本质上override和final都是限制类的关键字
例如在下述代码中加上override可以检查出函数是否出现拼写错误
class Car {public:virtual void Dirve(){}
};class Benz :public Car {public:virtual void Drive() override { cout << "Benz-舒适" << endl; }
};int main()
{return 0;
}
再例如下述代码中加上final就不再构成多态
class Car{public:virtual void Drive() final {}
};class Benz :public Car
{public:virtual void Drive() { cout << "Benz-舒适" << endl; }
};int main()
{return 0;
❀3.2重载、重写、隐藏对比

3.3协变
派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。协变的实际意义并不⼤,所以我们了解⼀下即可。
#include<iostream>
using namespace std;class A {};class B : public A {};class Person {public:virtual A* BuyTicket(){cout << "买票-全价" << endl;return nullptr;}
};class Student : public Person {public:virtual B* BuyTicket(){cout << "买票-打折" << endl;return nullptr;}
};void Func(Person* ptr)
{ptr->BuyTicket();
}int main()
{Person ps;Student st;Func(&ps);Func(&st);return 0;
}

