继承
现在需要建立 Student 类型和 Teacher 类型。我们发现,两者有相同的地方,比如都有名字,都可以说话。两者也有不同的地方,比如说话的方式不同。我们可以将相同的地方抽象成 Human 类型,让 Student 和 Teacher 去继承 Human,再让两者分别在自己的类型中实现不同的地方。这里,Human 是基类,Student 和 Teacher 是子类:
class Human {
public:
virtual ~Human() {};
virtual void talk() = 0;
};
class Student final : public Human {
public:
Student(const std::string& name) : name_(name) {}
void talk() override {
std::cout << "Student: " << name_ << "\n";
}
private:
std::string name_;
};
class Teacher final : public Human {
public:
Teacher(const std::string& name) : name_(name) {}
void talk() override {
std::cout << "Teacher: " << name_ << "\n";
}
private:
std::string name_;
};
现在需要创建一个 Human。为此,我们编写一个工厂函数,根据输入的类型和名字,返回相应的 Human 对象:
// 用来指明 Human 的类型
enum class Type {
student, teacher
};
// 返回一个类型是 type,名字是 name 的 Human 对象
Human* make_human(Type type, const std::string& name) {
if (type == Type::student) {
return new Student(name);
}
else { // type == Type::teacher
return new Teacher(name);
}
}
我们来使用一下:
int main() {
// *p1 的类型是 Human,实际类型是 Student
Human* p1 = make_human(Type::student, "mimi");
p1->talk(); // 输出 Student: mimi
// *p2 的类型是 Human,实际类型是 Teacher
Human* p2 = make_human(Type::teacher, "xiao");
p2->talk(); // 输出 Teacher: xiao
}
这里我们用到了多态,即同一个类型可能有不同的形式。上面的 *p1 和 *p2 都是 Human 类型,但一个表现为 Student,一个表现为 Teacher。
多态是通过虚函数实现的。每个类型都有自己的虚表,里面存放了该类型中每个函数的位置。每个对象都有自己的虚指针,指向实际类型的虚表。在上面的代码中,*p1 的虚指针指向 Student 类型的虚表,调用 talk() 函数时会去 Student 类型的虚表中查找。*p2 也是一样。
注意,只有定义了虚函数的类型(包括从基类继承来的虚函数)才会有虚表。
构造和析构的顺序
先回顾一下 Student 类型:
class Student final : public Human {
public:
Student(const std::string& name) : name_(name) {}
void talk() override {
std::cout << "Student: " << name_ << "\n";
}
private:
std::string name_;
};
构造的顺序:
- 构造基类(Human)子对象。为什么叫子对象呢?这是因为基类对象是包含在子类对象中的。注意,这时虚指针指向基类的虚表,基类子对象构造完成后,才指向子类的虚表;
- 构造子类的成员(name_);
- 执行子类构造函数的函数体。
析构的顺序和构造的顺序相反:
- 执行子类析构函数的函数体;
- 析构子类的成员(name_);
- 析构基类(Human)子对象。
六大函数
每个类都有六大函数,它们与对象的创建和销毁息息相关:
class Human {
public:
// 默认构造函数
Human() {}
// 析构函数
~Human() {}
// 拷贝构造函数
Human(const Human& rhs) {}
// 拷贝赋值函数
Human& operator=(const Human& rhs) {}
// 移动构造函数
Human(Human&& rhs) {}
// 移动赋值函数
Human& operator=(Human&& rhs) {}
};
上面是我们自己动手实现。如果我们不写的话,编译器会帮我们实现:
class Human {
// 编译器帮我们实现
};
我们也可以显示要求编译器帮我们实现,最后生成的代码和前面两种一样:
class Human {
public:
Human() = default;
~Human() = default;
Human(const Human& rhs) = default;
Human& operator=(const Human& rhs) = default;
Human(Human&& rhs) = default;
Human& operator=(Human&& rhs) = default;
};