多态公有继承
多态
方法的行为应取决于调用该方法的对象,这种复杂的行为称为多态,具有多种形态,即同一种方法的行为随上下文而异。
实现多态公有继承
- 在派生类中重新定义基类的方法。
- 使用虚方法。
实例一
Person.h
#ifndef PERSON_H_
#define PERSON_H_
#include <string>
// 基类
class Person
{
private:
std::string m_firstname;
std::string m_lastname;
public:
Person(const std::string& firstname, const std::string& lastname);
Person() {}
virtual ~Person() {} // 虚析构函数
std::string HGetName() const
{
return m_firstname + m_lastname;
}
virtual void HShowAll() const; // 虚函数
virtual Person& HChangeName(const std::string &lastname); // 虚函数
};
// 派生类
class Student : public Person // 公有继承
{
private:
int m_scores;
public:
Student(const std::string& firstname, const std::string& lastname, int scores = 0);
Student(const Person &p, int scores = 0);
Student();
int HGetScores() const
{
return m_scores;
}
virtual void HShowAll() const; // 重写虚函数
virtual Student& HChangeName(const std::string &lastname); // 重写虚函数,返回类型协变
};
#endif
#endif
Person.cpp
#include "Person.h"
#include <iostream>
Person::Person(const std::string& firstname, const std::string& lastname)
:m_firstname(firstname), m_lastname(lastname)
{
}
Person& Person::HChangeName(const std::string &lastname)
{
m_lastname = lastname;
return *this;
}
void Person::HShowAll() const
{
std::cout << m_firstname << m_lastname;
}
Student::Student(const std::string& firstname, const std::string& lastname, int scores)
:Person(firstname, lastname) // 列表成员初始化
{
m_scores = scores;
}
Student::Student()
:Person(), m_scores(0)
{
}
Student::Student(const Person &p, int scores)
:Person(p),m_scores(scores)
{
}
void Student::HShowAll() const
{
Person::HShowAll();
std::cout << ": " << m_scores;
}
Student& Student::HChangeName(const std::string &lastname)
{
Person::HChangeName(lastname);
return *this;
}
main.cpp
#include "Person.h"
#include <iostream>
using std::cout;
using std::endl;
int main()
{
Person p1("li","si");
p1.HShowAll();
cout << endl;
Student stu1(p1, 100); // 调用Student(const Person &p, int scores = 0);
stu1.HShowAll();
cout << endl;
Student stu2("zhang","san", 100);
Person & p2 = p1;
Person & p3 = stu2;
p2.HShowAll(); // 调用Person::HShowAll();
cout << endl;
p3.HShowAll(); // 调用Student::HShowAll();
cout << endl;
// 指向Person的指针数组
Person * all_person[3];
all_person[0] = new Person("zhang","yi");
all_person[1] = new Student("zhang","er", 90);
all_person[2] = new Student("zhang","san");
for(int i = 0; i < 3; i++)
{
all_person[i]->HShowAll();
cout << endl;
delete all_person[i]; // 将调用指针所指向的对象类型的析构函数
all_person[i] = nullptr;
}
Person p4("li","si");
p1.HShowAll();
cout << endl;
Person p5 = p1.HChangeName("yi");
p2.HShowAll();
cout << endl;
Student stu3("zhang","san");
stu3.HShowAll();
cout << endl;
Student stu4 = stu3.HChangeName("er");
stu4.HShowAll();
cout << endl;
return 0;
}
程序说明
- Person类在声明HShowAll()时使用了关键字
virtual
,这个方法称为虚方法。
virtual void HShowAll() const; // 虚函数
- 如果不使用关键字
virtual
,程序将根据引用类型或指针类型选择方法; - 如果使用了
virtual
,程序将根据引用或指针指向的对象的类型来选择方法。
// 不将HShowAll()声明为虚函数
Person p1("li","si");
Student stu2("zhang","san", 100);
Person & p2 = p1; // 引用类型为Person
Person & p3 = stu2; // 引用类型为Person
p2.HShowAll(); // 调用Person::HShowAll();
p3.HShowAll(); // 调用Person::HShowAll();
因此,如果要在派生类中重新定义基类的方法,则将它设置为虚方法;否则,设置为非虚方法。
- Person类将析构函数声明为虚析构函数。这样做是为了确保释放派生类对象时,按正确的顺序调用析构函数。
virtual ~Person() {} // 虚析构函数
- 如果析构函数不是虚的,则将只调用对应于指针类型的析构函数,例如实例一中将会只调用Person的析构函数;
- 如果析构函数是虚的,将调用指针所指向的对象类型的析构函数。
- 派生类中调用基类的虚函数。使用作用域解析运算符来调用基类的方法
void Student::HShowAll() const
{
Person::HShowAll();
std::cout << ": " << m_scores;
}
虚函数工作原理
编译器会给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含了一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重写定义虚函数,该虚函数表将保存函数原始版本的地址。
哪些函数不能为虚函数,哪些函数应该为虚函数
- 构造函数不能是虚函数。创建派生类对象时,将先调用派生类的构造函数,然后再调用基类的构造函数,这种顺序不同于继承机制。
- 友元不能是虚函数。因为友元不是类成员,只有成员函数才能是虚函数。
- 析构函数应该是虚函数,防止派生类中的析构函数不被调用,造成内存泄漏。
返回类型协变
如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,这种特性称为返回类型协变,它允许返回类型随类类型的变化而变化。
class Person
{
...
virtual Person& HChangeName(const std::string &lastname); // 虚函数
...
};
class Student : public Person
{
...
virtual Student& HChangeName(const std::string &lastname); // 重写虚函数,返回类型协变
...
};
访问控制:protected
- 关键字protected与private相似,在类外不能直接访问protected部分中的成员,只能通过类中的方法访问。
- 在继承方面,则派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。
- 对于外部来说,保护成员的行为与私有成员相似,但对于派生类来说,保护成员的行为与公有成员相似。
class Person
{
private: // 私有成员,外界不能访问,派生类不能直接访问
std::string m_firstname;
protected: // 保护成员,外界不能访问,派生类可以直接访问
std::string m_lastname;
public:
int m_age; // 公有成员,外界可以访问,派生类可以直接访问
...
};
抽象基类
当类声明中包含纯虚函数时,则不能创建该类的对象,这样的类被声明为了抽象的。
C++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0
实例二
Person.h
#ifndef PERSON_H_
#define PERSON_H_
#include <string>
// 基类
class Person
{
private:
std::string m_fullname;
// 保护成员
protected:
int m_age;
const std::string & HGetFullname() const
{
return m_fullname;
}
public:
Person(const std::string fullname, int age)
: m_fullname(fullname), m_age(age) {}
Person() { m_age = 0; }
virtual ~Person() {} // 虚析构函数
virtual void HFeture() const = 0; // 纯虚函数
};
// 派生类
class Student : public Person // 公有继承
{
private:
int m_scores;
public:
Student(const std::string& fullname, int age, int scores = 0);
Student(const Person &p, int scores = 0);
Student();
virtual void HFeture() const; // 必须重写纯虚函数
};
// 派生类
class Teacher : public Person // 公有继承
{
private:
int m_classid;
public:
Teacher(const std::string& fullname, int age, int classid = 0);
Teacher(const Person &p, int classid = 0);
Teacher();
virtual void HFeture() const; // 必须重写纯虚函数
};
#endif
Person.cpp
#include "Person.h"
#include <iostream>
Student::Student(const std::string& fullname, int age, int scores)
:Person(fullname, age), m_scores(scores) // 列表成员初始化
{
}
Student::Student()
:Person(), m_scores(0)
{
}
Student::Student(const Person &p, int scores)
:Person(p),m_scores(scores)
{
}
void Student::HFeture() const
{
std::cout << "My name is " << HGetFullname(); // 可以直接访问基类的保护成员 HGetFullname()
std::cout << ". I'm " << m_age << " years old."; // 可以直接访问基类的保护成员 m_age
std::cout << ". I'm a student" << std::endl;
std::cout << "I got " << m_scores << " points in this exam\n";
}
Teacher::Teacher(const std::string& fullname, int age, int classid)
: Person(fullname, age), m_classid(classid)
{
}
Teacher::Teacher(const Person &p, int classid)
: Person(p), m_classid(classid)
{
}
Teacher::Teacher()
:Person(), m_classid(0)
{
}
void Teacher::HFeture() const // 必须重写纯虚函数
{
std::cout << "My name is " << HGetFullname(); // 可以直接访问基类的保护成员HGetFullname()
std::cout << ". I'm " << m_age << " years old."; // 可以直接访问基类的保护成员m_age
std::cout << ". I'm a teacher" << std::endl;
std::cout << "I am the head teacher of Class " << m_classid << std::endl;
}
main.cpp
#include "Person.h"
#include <iostream>
using std::cout;
using std::endl;
int main()
{
Student stu1("zhangsan", 18, 90);
stu1.HFeture();
std::cout << std::endl;
Teacher t1("lisi", 28, 2);
t1.HFeture();
return 0;
}
程序说明
- 保护成员:对于保护成员,外界不能直接访问。所以上述程序中
m_age
和HGetFullname()
外界不能直接访问。 - 保护继承:派生类可以直接访问。所以对于
m_age
和HGetFullname()
,派生类Student
和Teacher
可以直接访问。 - 纯虚函数:在基类中不用定义纯虚函数,而派生类必须重新定义纯虚函数。
- 抽象类:基类
Person
中有纯虚函数使Person
成为了抽象类,抽象不能实例化。
virtual void HFeture() const = 0; // 纯虚函数