基类指针和虚函数实现多态
#include <iostream>
using namespace std;
class People {
public:
People(char *name_parameters, int age_parameters);
People(const People &people);
~People();
void display();
char *get_name();
int get_age();
private:
char *name;
int age;
};
People::People(char *name_parameters, int age_parameters) : name(name_parameters), age(age_parameters) {
cout << "people class constructed is created" << endl;
}
People::People(const People &people) { cout << "people copy constructed is used" << endl; }
People::~People() { cout << "people destructed is used" << endl; }
int People::get_age() { return this->age; }
char *People::get_name() { return this->name; }
void People::display() { cout << "name:" << name << ",age:" << age << endl; }
class Teacher : public People {
public:
Teacher(char *name, int age, int salary);
~Teacher();
void display();
int get_salary();
private:
int salary;
};
Teacher::Teacher(char *name, int age, int salary) : People(name, age), salary(salary) {
cout << "Teacher class constructed is created" << endl;
}
Teacher::~Teacher() { cout << "Teacher destructed is used" << endl; }
int Teacher::get_salary() { return this->salary; }
void Teacher::display() {
cout << "name2:" << get_name() << ",age:" << get_age()\
<< "salary:" << salary << endl;
}
int main() {
People *ptr = new People("tom", 12);
ptr->display();
ptr = new Teacher("jack", 18, 2000);
ptr->display();
return 0;
}
输出:
people class constructed is created
name:tom,age:12
people class constructed is created
Teacher class constructed is created
name:jack,age:18
通过上述程序输出,可以看出即使基类指针ptr指向了派生类,但是依然无法访问派生类的成员函数,只能访问派生类继承的成员变量。
那么此时,可以使用虚函数使基类指针可以访问派生类成员函数。虚函数使用方式很简单,和虚基类类似,在要访问的函数前添加virtual关键字即可。
#include <iostream>
using namespace std;
class People {
public:
People(char *name_parameters, int age_parameters);
People(const People &people);
~People();
virtual void display();
char *get_name();
int get_age();
private:
char *name;
int age;
};
People::People(char *name_parameters, int age_parameters) : name(name_parameters), age(age_parameters) {
cout << "people class constructed is created" << endl;
}
People::People(const People &people) { cout << "people copy constructed is used" << endl; }
People::~People() { cout << "people destructed is used" << endl; }
int People::get_age() { return this->age; }
char *People::get_name() { return this->name; }
void People::display() { cout << "name:" << name << ",age:" << age << endl; }
class Teacher : public People {
public:
Teacher(char *name, int age, int salary);
~Teacher();
void display();
int get_salary();
private:
int salary;
};
Teacher::Teacher(char *name, int age, int salary) : People(name, age), salary(salary) {
cout << "Teacher class constructed is created" << endl;
}
Teacher::~Teacher() { cout << "Teacher destructed is used" << endl; }
int Teacher::get_salary() { return this->salary; }
void Teacher::display() {
cout << "name2:" << get_name() << ",age:" << get_age()\
<< "salary:" << salary << endl;
}
int main() {
People *ptr = new People("tom", 12);
ptr->display();
ptr = new Teacher("jack", 18, 2000);
ptr->display();
return 0;
}
输出:
people class constructed is created
name:tom,age:12
people class constructed is created
Teacher class constructed is created
name2:jack,age:18salary:2000
使用虚函数之后,基类指针指向基类对象时就访问基类的成员,指向派生类对象时,则就访问派生类继承的成员(成员变量和成员函数),这种多种表现形式称之为多态。简单来说同一语句可以有不同的操作,不同的表现形式就是多态。
多态的目的
多态的目的是为了使基类指针对所有直接派生类和简接派生类的成员进行访问,如果没有多态,那么基类指针只能访问成员变量。虚函数的作用就是为了构建多态。
通过和之前学习过的类指针调用成员函数相比,虚函数是个特例,调用它不是由指针的类型决定的,而是指针的指向决定的。(普通类指针,指针类型是哪个类那么就会调用哪个类的成员函数)
通过引用实现多态
int main() {
People p1("tom", 20);
People &qtr = p1;
Teacher t2("jerry", 21, 2000);
People &ptr = t2;
qtr.display();
ptr.display();
return 0;
}
输出:
people class constructed is created
people class constructed is created
Teacher class constructed is created
name:tom,age:20
name2:jerry,age:21salary:2000
Teacher destructed is used
people destructed is used
people destructed is used
由于引用类似于常量,只能在定义的同时初始化,并且以后也要从一而终,不能再引用其他数据,所以本例中必须要定义两个引用变量,一个用来引用基类对象,一个用来引用派生类对象。从运行结果可以看出,当基类的引用指代基类对象时,调用的是基类的成员,而指代派生类对象时,调用的是派生类的成员。
不过引用不像指针灵活,指针可以随时改变指向,而引用只能指代固定的对象,在多态性方面缺乏表现力,所以一般多态使用的都是指针。
虚函数的注意事项
- 只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。
- 为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数。
- 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。
- 只有派生类的虚函数覆盖基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问派生类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。
- 构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。
- 析构函数可以声明为虚函数,而且有时候必须要声明为虚函数
构成多态的条件:
- 必须存在继承关系;
- 继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)。
- 存在基类的指针,通过该指针调用虚函数。
什么时候声明虚函数
首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。