- 多态的概念
- 多态的定义及实现方法
- 多态的一些注意细节
多态的概念
多态顾名思义就是多种形态的意思,在生活中可以理解为不同的对象做同一件事情会产生不同的结果,比如买车票,普通人是全价买票,学生是半价买票,军人可以优先买票。在语言层面上多态可以分为静态多态和动态多态。静态多态就是函数重载,它看起来就像是调用同一个函数却执行不同的行为。动态多态是指将基类和派生类对象传给基类引用或指针管理,调用同一个函数,传入对象不同,执行的行为就不同。一般动态多态就简称多态。用以下代码演示
class Person
{
public:
virtual void Buyticket()
{
cout << "全价门票" << endl;
}
};
class Student :public Person
{
public:
virtual void Buyticket()
{
cout << "半价门票" << endl;
}
};
class Soldier :public Person
{
public:
virtual void Buyticket()
{
cout << "优先买票" << endl;
}
};
void test1(Person* p)
{
//p.Buyticket();
p->Buyticket();
}
int main()
{
Person p;
Student s;
Soldier o;
test1(&p);
test1(&s);
test1(&o);
return 0;
}
上面让三个不同类型的对象地址给基类指针管理,调用同一个函数,构成多态,传入的是那个对象就执行那个对象的行为,这就是多态的表现。
多态的定义及实现方法
多态是在继承的基础上实现的,在传入不同对象给基类指针或引用管理,调用同一个函数去执行不同的行为,这需要满足两个条件。
- 调用的函数必须是虚函数
- 在派生类中必须重写虚函数,重写要满足函数返回值、函数名和函数参数都相同即三同。
在类的非静态成员函数前面加virtual修饰的函数即为虚函数。是虚函数又满足三同,就可以在不同的派生类中对应的虚函数中写自己定义的行为。
多态的一些注意细节
在满足多态的条件时调用同一函数就会根据传入的对象,去执行传入对象重写的虚函数相应的行为,如果破环多态的条件,不构成多态,像上面代码那样,就只会调用Person类中的函数,不管传入上面那个对象,都是打印“全价买票”。下面测试验证一下
class Soldier :public Person
{
public:
virtual void Buyticket(int) //这里加个int参数破环三同条件,不构成多态
{
cout << "优先买票" << endl;
}
};
同样传三个不同对象,这时Soldier类型对象与基类就不构成多态了
传入Soldier对象后还是会执行基类的函数打印“全价买票”。
当使返回类型不同时也不会构成多态,但编译器会报错
这里就要讲一下什么是协变。重写虚函数三同条件中有一个特例,就是当函数返回不同只要返回类型是该类的指针或引用返回,基类型的返回基类型的指针或引用,也可以构成多态,这就是协变。在vs下只要返回类型不同又不是协变就直接报错。下面演示协变
class Person
{
public:
virtual Person& Buyticket() //拿引用来说,换成指针也是一样,基类返回基类的引用
{
cout << "全价门票" << endl;
return *this;
}
};
class Student :public Person
{
public:
virtual Student& Buyticket() //返回本类的引用
{
cout << "半价门票" << endl;
return *this;
}
};
class Soldier :public Person
{
public:
virtual Soldier& Buyticket() //返回本类的引用
{
cout << "优先买票" << endl;
return *this;
}
};
析构函数也是可以构成多态的,只要用virtual修饰析构函数就可以构成多态,析构函数本身无参,没有返回值相当于返回值相同,函数名虽然不同但编译器会统一当作destructor处理所以满足三同的条件,只要我们加virtual就可以构成多态。
下面探讨什么情况下要使析构函数构成多态。
//(1)
Person p; //这种情况下定义成多态和不定义没有影响,p会自动调用它的析构函数清理资源
Student s; //s调用完自己的析构函数清理自己那部分后会去调用基类的析构函数清理基类那部分的资源
//(2)
Person *p=new Person; //先调用operator new 再调用构造函数
Student*s=new Student; //这里一样
delete p; //先调用p的析构函数清理资源再调用operator delete
delete s;// s的析构函数+基类析构函数 +operator delete 这种情况析构函数构不构成多态也没有影响。
//(3)
Person*p =new Person;
Person* s=new Student;//当动态开辟的派生类对象交给基类指针管理时
delete p;
delete s; //这里必须使析构函数构成多态,否则delete s时只会调用基类的析构函数清理基类部分的资源,
//而实际对象是Student 还有自身部分的资源需要用自身的析构函数清理,构成多态调用完自身析构函数自动去调用基类析构,避免内存泄漏。
还有一点要注意的是基类虚函数要加virtual 再派生类中重写虚函数可以不加virtual,同样构成多态,为了形式统一最好还是加上。