目录
一、概念
通俗来说,就是多种形态,当完成某个行为时,当不同的对象去完成时会产生不同的状态
比如买票,普通人买票就是全票,学生买票就是半票,而军人就有可能会免票。
比如刷抖音,小红刷到的大多都是穿搭帅哥明星,小黑刷到的大多都是王者吃鸡,小刚刷到的大多都是健身视频,对于同一个行为,不同得人最终刷到的视频内容大相径庭,这就是多态。
二、多态的定义及实现
下面这是之前写过的动物类之间的继承,Animal为基类,Cat和Dog为由Animal继承下来的俩个派生类。
class Animal
{
public:
Animal(const string& name, const string& sex, int age)
: _name(name)
, _sex(sex)
, _age(age)
{}
void sleep()
{
cout << _name << "在睡觉~~~" << endl;
}
void eat()
{
cout << _name << "吃东西……" << endl;
}
protected:
string _name;
string _sex;
int _age;
};
class Dog : public Animal
{
public:
Dog(const string& name, const string& sex, int age, const string& color)
: Animal(name, sex, age)
, _color(color)
{}
private:
string _color;
};
class Cat : public Animal
{
public:
Cat(const string& name, const string& sex, int age, const string& Tempter)
: Animal(name, sex, age)
, _Tempter(Tempter)
{}
private:
string _Tempter;
};
void Test1()
{
Dog dog("小黑", "公", 2, "金黄色");
dog.eat();
dog.sleep();
Cat cat("咪咪", "母", 1, "白色");
cat.eat();
cat.sleep();
}
int main()
{
Test1();
return 0;
}
代码的执行中,cat和dog最终的sleep与eat都是打印的同一个结果,因为他们都是继承自基类,都使用的是基类Animal中的同一个成员函数,猫和狗二者的习性是大相径庭的,所以不应该这样笼统的让它们进行同样的行为。
所以给Cat类与Dog类中添加了各自的成员函数,由于派生类中与基类都出现了相同名字的成员,则触发被动——同名隐藏,因此即会使用派生类中的成员函数。
Dog:
void eat()
{
cout << _name << "吃的声音很大" << endl;
}
void sleep()
{
cout << _name << "呼噜噜……" << endl;
}
Cat:
void eat()
{
cout << _name << "悄咪咪的吃东西" << endl;
}
void sleep()
{
cout << _name << "zzz……" << endl;
}
功能是实现了,可是在与用户接触的Test函数中却书写了大量重复的代码
使用多态
第一步 使用基类引用或指针给子类赋值
void Dynamic(Animal& s)
{
s.eat();
s.sleep();
}
int main()
{
Dog dog("小黑", "公", 2, "金黄色");
Dynamic(dog);
Cat cat("咪咪", "母", 1, "白色");
Dynamic(cat);
return 0;
}
第二步 将基类Animal中成员函数声明为虚函数
virtual void sleep()
{
cout << _name << "在睡觉~~~" << endl;
}
virtual void eat()
{
cout << _name << "吃东西……" << endl;
}
完成!!!
需求:实现一个绘图软件
如果是圆,画圆
如果是矩形,画矩形
如果是三角形,画三角形
class Shape { public: virtual void Drow() { cout << "图形未知,无法作图" << endl; } virtual double GetPerimeter() { cout << "图形未知" << endl; return 0; } }; class Circle : public Shape { public: Circle(double r) : _r(r) {} virtual void Drow() { cout << "○" << endl; } virtual double GetPerimeter() { return 2 * 3.14 * _r; } private: double _r; }; class Rectangle : public Shape { public: Rectangle(double length, double width) : _length(length) , _width(width) { } virtual void Drow() { cout << "□" << endl; } virtual double GetPerimeter() { return 2 * (_length+_width); } private: double _length; double _width; }; class Trangle : public Shape { public: Trangle(double a, double b, double c) : _a(a) , _b(b) , _c(c) {} virtual void Drow() { cout << "△" << endl; } virtual double GetPerimeter() { return _a+_b+_c; } private: double _a; double _b; double _c; }; void Test(Shape& s) { s.Drow(); cout << s.GetPerimeter() << endl; } int main() { Circle c(2.2); Rectangle r(1, 2); Trangle t(3, 4, 5); Test(c); Test(r); Test(t); return 0; }
三、动态多态的实现条件
1)必须在继承前提下,子类必须重写基类的虚函数 (被virtual修饰的函数即为虚函数)
2)关于虚函数的调用,通过引用或者指针
3)动态:即只有当程序运行的时候根据传入的对象编译器才会选择类中对应的虚函数进行调用
如果类中哪个方法想要实现多态的效果,则该方法必须为虚函数,并且在子类中必须要被重写
四、重写相关
1)基类中要被重写的成员函数必须为虚函数
2)子类虚函数与基类虚函数的原型要一致(返回值类型、函数名、参数列表必须完全一致)
特例:
① 析构函数一般定义为虚函数(基类与子类函数名不同 但构成重写)
② 协变:基类函数返回基类的指针或引用,子类返回子类的指针或引用
(返回值类型不同,但也构成重写)
3)子类中的virtual关键字有没有均可(推荐加上)
4)基类与子类的虚函数访问权限可以不同(一般保持一致)
class B
{
public:
virtual ~B() //特例 ① virtual修饰基类的析构函数
{
cout << "B::~B()" << endl;
}
virtual B* GetObjPtr() //特例 ② 基类返回基类的指针,子类返回子类的指针
{
return this;
}
};
class D : public B
{
public:
~D()
{
cout << "D::~D()" << endl;
}
virtual D* GetObjPtr()
{
return this;
}
};
void Test(B* t)
{
delete t;
t = nullptr;
}
void TestAddr(B* t)
{
if (t)
{
t->GetObjPtr();
}
}
int main()
{
B *pb = new B;
Test(pb);
D* pd = new D;
Test(pd);
///
B b;
TestAddr(&b);
D d;
TestAddr(&d);
return 0;
}
五、构成重写与同名隐藏的函数有什么区别?
1)相同点:
1、都在同一个继承体系下,一个在基类中,一个在子类中
2、二者的函数名相同
2)不同点:
1、重写中基类的函数必须是虚函数,而同名隐藏没有要求
2、重写要求俩个函数的原型必须一致(析构函数与协变除外)
而同名隐藏只要求俩个函数函数名相同
下列代码只有func1构成重写
class B
{
public:
virtual void func1()
{
cout << "B::func1" << endl;
}
void func2()
{
cout << "B::func2" << endl;
}
void func3()
{
cout << "B::func3" << endl;
}
void func4(int a)
{
cout << "B::func3" << endl;
}
};
class D : public B
{
public:
virtual void func1()
{
cout << "D::func1" << endl;
}
void func2()
{
cout << "D::func2" << endl;
}
virtual void func3() // 子类中为虚函数 基类中不为虚函数 (不构成重写)
{
cout << "D::func3" << endl;
}
virtual void func4(int a) // 子类中为虚函数 基类中不为虚函数 (不构成重写)
{
cout << "D::func4" << endl;
}
};
六、C++11 中的override 和 final
从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数
名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有
得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮
助用户检测是否重写
final修饰类,表示该类不能被继承。
1)final修饰虚函数,表示该虚函数不能再被重写(最后一个了嘛)
2)override只能修饰子类中的虚函数,指定该虚函数必须要实现重写基类中函数(如果无法实现则报错)