类继承是OOP的三个基本概念之一(数据抽象、继承、动态绑定),它让我们可以通过修改和扩展一个已有的类(基类)来获得新的类(派生类),提高了代码的可重用性。
1.基类与派生类
为了说明类继承,我们首先需要一个基类。假设现在有一个Student类,记录学生在班上的编号以及姓名:
#ifndef STUDENT_H_
#define STUDENT_H_
#include <iostream>
using std::string;
using std::cout;
using std::endl;
class Student {
private:
int num;
string name;
public:
Student(const int number = 0, const string student_name = ""):
num(number), name(student_name) { }
void print_info(){
cout << "num: " << num << ", name: " << name;
}
};
#endif
这是一个很简单的类,私有数据部分包含了学生的编号、姓名,公有部分定义了一个简单的构造函数和一个用来打印学生信息的print_info函数。
然而,班上的学生有的是班干部比如班长、副班长,有的不是班干部,我们希望有这样一个类,它能够包含是班干部的同学在班上担任的职位,此时我们需要这样定义一个Student类的派生类StudentWithPosition表示有职位的学生:
class StudentWithPosition: public Student {
private:
string position;
public:
StudentWithPosition(const int number = 0, const string stu_name = "", const string pos = ""): Student(number, stu_name), position(pos) { }
void print_info_with_position(){
print_info();
cout << ", position: " << position << endl;
}
};
第一行冒号表示Student是一个公有基类,StudentWithPosition是有这个公有基类派生而来。
在继承过程中,派生类会:
1.存储基类的数据成员num, name
2.继承基类的接口Student(), print_info()
除此以外,派生类还要添加自己的数据成员position,这样一来我们还必须添加自己的构造函数和成员函数。
添加构造函数:
在创建构造函数时,有一点必须注意的是派生类对象创建时,程序首先创建基础对象,也就是说,基类对象必须在程序进入派生类构造函数之前被创建,为了做到这一点,我们需要使用初始化列表:
StudentWithPosition(const int number = 0, const string stu_name = "", const string pos = ""): Student(number, stu_name), position(pos) { }
如果我们没有在初始化列表中调用基类的构造函数,那程序将会使用默认的基类构造函数,也就是说,下面这样写:
StudentWithPosition(const int number = 0, const string stu_name = "", const string pos = ""): position(pos) { }
与这样写:
StudentWithPosition(const int number = 0, const string stu_name = "", const string pos = ""): Student(), position(pos) { }
是等价的。
添加成员函数:
在派生类中,我们也希望有一个成员函数能够打印出学生的基本信息以及职位信息,于是,我们添加了一个print_info_with_position函数:
void print_info_with_position(){
print_info();
cout << ", position: " << position << endl;
}
这里需要注意的就是,派生类不能直接访问基类的私有成员,必须通过基类方法进行访问,所以我们不能这样写:
void print_info_with_position(){
cout << "num: " << num << ", name: " << name; //wrong
cout << ", position: " << position << endl;
}
而必须通过基类方法print_info来访问num和name成员。
下面,我们来简单地使用一下刚刚定义好的派生类,我们把基类和派生类的定义都放在了student.h头文件中,下面是main.cc:
#include <iostream>
#include "student.h"
using std::cout;
using std::endl;
int main()
{
Student s1(1, "Mike");
s1.print_info();
cout << endl;
StudentWithPosition s2(2, "Tom", "monitor");
s2.print_info_with_position();
StudentWithPosition s3(3, "John", "vice monitor");
s3.print_info_with_position();
return 0;
}
输出:
num: 1, name: Mike
num: 2, name: Tom, position: monitor
num: 3, name: John, position: vice monitor
关于基类与派生类的关系,还有一些需要注意的地方:基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用也可以在不进行显式类型转换转换的情况下引用派生类对象,也就是说,下面这样的用法是可行的:
StudentWithPosition s1(1, "Jerry", "monitor");
Student *ptr = &s1;
Student &ref = s1;