多态的概念
多态:顾名思义,多种形态,当有不同的对象去调用成员函数时会有不同的效果。
比如平时的买票出行:不同的人去买票优惠都是不一样的,普通人买票全价,学生买票有优惠,而军人买票是优先买票。
多态的实现
多态是如何构成的呢?有两个条件。
1、被调用的函数必须是虚函数,并且子类必须对父类的虚函数进行重写。
2、必须通过父类的指针或引用来调用虚函数。
那虚函数又是什么呢?
虚函数:被virtual关键字修饰的成员函数既虚函数。
重写又有什么要求?
需是 虚函数 + (函数名+参数+返回值 都得相同)构成重写。
class Person
{
public:// 成员函数前面加上virtual既虚函数
virtual void BuyTicket()
{}
};
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 testBuyTicket(Person* p)
{
p->BuyTicket();
}
int main()
{
//有3个不同身份的对象:普通人、学生、军人
Person p;
Student st;
soldier sl;
//不同的对象去调用函数会有不同的效果
testBuyTicket(&p);
testBuyTicket(&st);
testBuyTicket(&sl);
return 0;
}
虚函数的重写
虚函数的重写(也叫覆盖):子类中有一个跟父类完全相同的虚函数(返回值类型、函数名字、参数完全相同),子类的虚函数重写了父类的虚函数。所以可以把其称为重写或覆盖。
class Person
{
public:
virtual void BuyTicket() // 函数名+参数+返回值(三同)
{
cout << "普通人买票——全价" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTicket() // 函数名+参数+返回值(三同)
{
cout << "学生买票——优惠" << endl;
}
};
但构成重写的有2个例外。
1、重写的时候返回值可以不同,称为协变。但返回值必须是父子关系的指针或引用。
class A
{};
class B :public A
{};
class Person
{
public:
virtual A* f()
{
cout << "virtual A* f()" << endl;
return new A;
}
};
class Student :public Person
{
public:
virtual B* f()
{
cout << "virtual A* f()" << endl;
return new B;
}
};
2、析构函数的重写(父类、子类析构函数名不同)
如果父类的析构函数为虚函数,且子类的析构函数一旦定义了,无论它是否加了virtual关键字,都会与父类的析构函数构成重写。虽然父类与子类的析构函数名不同,但也是构成重写的。上面不是说到构成重写的条件是:虚函数 + (返回值类型、函数名字、参数完全相同)吗。那怎么这里的函数名不同就能构成重写了。可以理解为编译器对析构函数的名称进行了特殊的处理。编译后析构函数的名称会被统一成destructor。因为析构函数名称被统一为destructor了,如果父类的析构函数不是虚函数且子类也定义了析构函数就会构成隐藏,是会出问题的。因此父类的析构函数必须得是虚函数。
class Person {
public:
//父类的析构函数是虚函数
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
//子类的析构是不是虚函数都会构成重写
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
要是父类的析构函数不加virtual的话,子类的析构函数会与父类构成隐藏,对于普通对象没有影响。但对于对象中有存在指向new动态分配内存的成员变量指针就会出现问题,只会调用父类的析构函数,而不会调用子类的析构函数,从而可能导致子类的内存泄漏。
class Person {
public:
//父类的析构不是虚函数
~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
//只构成隐藏,会有问题
~Student()
{
cout << "~Student()" << endl;
}
};
int main()
{
Person* p1 = new Person;
delete p1; // 会去调用p1->destructor() + operator delete(p1)
Person* p2= new Student;
delete p2; // 会去调用p1->destructor() + operator delete(p1)
return 0;
}
所以设计一个类的时候,这个类作为基类的话,就必须将析构函数定义为虚函数。当父类指针或引用指向一个子类时,生命周期结束会先调用子类析构函数再调用父类析构函数。从而解决内存泄漏的问题。