1.多态性
- 多态性定义:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息,就是调用函数,不同的行为就是指不同的实现,即执行不相同的函数。
- 例如:老师给你和你妈妈(不同对象)同时发送了你的期末考试成绩(消息),对于这一信息,你在思考为何会错(可能你在思考回家会不会挨打),你妈妈在思考等你回家怎么揍你(不同行为)
- 多态的另一种理解:编译时一种状态,运行时一种状态
- 程序的多态性:
- 编译时多态:又叫静态多态,程序在编译阶段就能决定调用哪个函数
- 运行时多态:又叫动态多态, 程序在编译阶段不确定调用哪个函数,而是在程序运行过程中才动态地确定操作所针对的对象
2.虚函数
思考下列程序的缺点
#include <iostream>
using namespace std;
class A{
protected:
int x;
int y;
int z;
public:
A(int x,int y,int z){
this->x = x;
this->y = y;
this->z = z;
}
void display(){
cout<<"x = "<<x<<",y = "<<y<<",z = "<<z<<endl;
}
};
class B : public A{
protected:
int m;
int n;
public:
B(int x,int y,int z,int m,int n):A(x,y,z){
this->m = m;
this->n = n;
}
void display(){
cout<<"x = "<<x<<",y = "<<y<<",z = "<<z<<",m = "<<m<<",n = "<<n<<endl;
}
};
int main(){
A a(1,2,3);
B b(4,5,6,7,8);
A *p;
p = &a;
p->display(); // 输出:1 2 3
p = &b;
p->display(); //输出: 4 5 6
return 0;
}
父类和子类同时都有display函数,用于输出本类的属性值,但是用父类型指针分别指向父类对象和子类对象并通过指针间接调用display函数,可以发现:无论指向的是父类对象还是子类对象,调用display函数都是输出x,y,z的值,调用子对象的display输出的也是x,y,z
Q:如何消除上述问题呢?
A:将父类中的display函数设置为虚函数!!!
virtual void display(){
cout<<"x = "<<x<<",y = "<<y<<",z = "<<z<<endl;
}
这样编译运行之后,程序输出变成了这样:
此时用父类型指针指向子类对象并调用display函数,不仅仅输出了从父类继承来的属性值,还输出了子类特有的属性值
上述虚函数的应用,使父类指针指向不同子类(不同对象),调用同一函数p->display();(同一信息),产生了不同的输出(不同行为),这就是多态性
- 虚函数解决了继承类中同名方法重载的问题,重载的方法体不同,如何在程序运行时,让程序知道应该调用哪一个类中的重载方法
#include <iostream>
using namespace std;
class Animal{
public:
virtual void jiao(){}
};
class Cat : public Animal{
public:
void jiao(){
cout<<"miao miao miao~~~~~~~"<<endl;
}
};
class Dog : public Animal{
public:
void jiao(){
cout<<"wang wang wang~~~~~~~"<<endl;
}
};
int main(){
Animal *animal; //定义父类指针
Cat cat; //定义Cat对象
Dog dog; //定义Dog对象
animal = &cat;
animal->jiao();
animal = &dog;
animal->jiao();
return 0;
}
输出:
调用同一函数产生不同输出,这个例子对于多态理解会更为深刻
看完上面的代码,你可能会有以下疑问:
Q:为什么不直接使用子类对象调用display方法呢,如:cat.jiao();和dog.jiao(),偏偏使用父类指针来调用呢?
A:其实这是因为面向对象编程要求对程序进行解耦合,提高程序的可扩展性,多态机制就是非常典型的面向抽象编程,不要面向具体编程,上述代码中Animal就是抽象,子类Cat和Dog就是具体,面向父类Animal进行编程,所以使用父类型指针,子类对象你可以随便增加删除,只需要修改父类指针指向就可以,而cat.jiao()这种调用方法就是面向具体编程。
总结:面向抽象编程,不要面向具体编程
虚析构函数
#include <iostream>
using namespace std;
class A{
public:
virtual ~A(){
cout<<"执行A类对象析构函数!"<<endl;
}
};
class B : public A{
public:
~B(){
cout<<"执行A类子类B类对象析构函数!"<<endl;
}
};
class C : public A{
public:
~C(){
cout<<"执行A类子类C类对象析构函数!"<<endl;
}
};
int main(){
A *pb,*pc;
pb = new B;
pc = new C;
delete pb;
delete pc;
return 0;
}
输出:
- 如果不将父类析构函数写成虚函数,因为子类对象是new出来的,则用delete撤销对象时,系统只会执行基类的析构函数,而不指向子类的析构函数
3.抽象类
了解抽象类之前我们首先需要了解一个概念:纯虚函数
- 纯虚函数:此函数只有函数名、返回值、形参列表,并没有函数体,它的函数体留给子类根据需要去实现
- 纯虚函数定义格式:virtual 返回值类型 函数名(参数列表) const = 0;
- 例如:求图形面积函数:virtual float area() const = 0;这里就是将area声明为一个纯虚函数
- 注意:
- ①纯虚函数没有函数体{ }
- ②const = 0只是为了告诉编译系统此函数是纯虚函数
- ③纯虚函数末尾有分号(;)
- ④该纯虚函数如果没有在子类中实现,则该函数在子类中仍然是纯虚函数
代码功能:在Animal类中定义纯虚函数jiao,子类实现
class Animal{
public:
virtual void jiao() const = 0;
};
class Cat : public Animal{
public:
void jiao() const{
cout<<"miao miao miao~~~~~~~"<<endl;
}
};
class Dog : public Animal{
public:
void jiao() const{
cout<<"wang wang wang~~~~~~~"<<endl;
}
};
int main(){
Animal *animal;
animal = new Cat;
animal->jiao();
animal = new Dog;
animal->jiao();
delete animal;
return 0;
}
- 抽象类:一种不用来定义对象,只用来继承的类
- 抽象类作用:作为公共基类去建立其他的派生类
- 凡是包含纯虚函数的类都被称为抽象类
- 因为纯虚函数在被实现之前是无法被调用的,所以抽象类无法实例化(即无法有抽象了来创建抽象类对象)
- 抽象类是为某一类族提供公共接口
- 动态关联:定义父类型指针,通过指向不同子类对象来调用方法,在运行阶段才知道应该调用哪个子类中的方法,称为动态关联
- 静态关联:定义各个子类对象,通过对象名来调用方法,在编译阶段编译器就知道应该调用哪里的方法,称为静态关联。
#include <iostream>
using namespace std;
class Animal{
public:
virtual void eat() const{} //虚函数
virtual void sleep() const{} //虚函数
virtual void jiao() const = 0; //纯虚函数
};
class Dog : public Animal {
public: //实现抽象类中的方法
void eat() const{
cout<<"I'm eating now!"<<endl;
}
void sleep() const{
cout<<"I'm sleeping now!"<<endl;
}
virtual void jiao() const{
cout<<"wang wang wang !"<<endl;
}
};
int main(){
Animal *animal;
animal = new Dog;
Dog dog;
animal->eat(); //动态关联
dog.sleep(); //静态关联
animal->jiao(); //动态关联
delete animal;
return 0;
}
通过这个程序就可以对抽象类、纯虚方法、虚函数、动态关联、静态关联有了更好的理解!