一、为什么要用继承
一个简化的Student类
class Student {
private:
string name;
string studentID;
public:
string getName(){ return name; }
void setName(string newName) { name = newName; }
string getStudentID(){ return studentID; }
void setStudentID(string newID) { studentID = newID; }
};
例:
在建模的过程中需要产生新类:研究生类,这个类是学生类的一个特殊类型:
- 一个研究生在进入研究生学习之前需要获得什么学位?
- 这个学生是从什么学院获得的学士学位?
方案一:修改学生类
class Student {
private:
string name;
string studentID;
// 新增
string undergraduateDegree;
string undergraduateInstitution;
bool isGraduateStudent;
public:
string getName(){ return name; }
void setName(string newName) { name = newName; }
string getStudentID(){ return studentID; }
void setStudentID(string newID) { studentID = newID; }
void displayAllAttributes();
};
void Student::displayAllAttributes(){
cout << "姓名 = " << name << "学号 = " << studentID << endl;
if(isGraduateStudent) {
cout << "毕业院校 = " << undergraduateDegree;
cout << "毕业院校 = " << undergraduateInstitution;
}
}
问题:
- 学生种类很多的时候,代码会变得冗长
- 每新增一个学生类,就要修改添加很多代码
- 难以编写和维护
方案二:克隆学生类产生新类
- 将上述Student类克隆出一个GraduateStudent类
class GraduateStudent {
private:
string name;
string studentID;
string undergraduateDegree;
string undergraduateInstitution;
...
问题:
- 这是一个非常不好的设计,因为在Student类和GraduateStudent类中包含了太多相同的代码。若将来想修改其中的一个类的属性,则必须两个类同时进行维护
正确的方案:利用继承
- 在研究生类中不必复制学生类中任何属性,因为研究生类已经自动的继承学生类中的属性
class Student {
private:
string name;
string studentID;
public:
string getName(){ return name; }
void setName(string newName) { name = newName; }
string getStudentID(){ return studentID; }
void setStudentID(string newID) { studentID = newID; }
};
// 继承
class GraduateStudent:public Student {
private:
string undergraduateDegree;
string undergraduateInstitution;
...
}
二、类的继承与派生
-
继承和派生是同一过程从不同的角度来看:
- 继承:保持已有类的特性而构造新类 的过程
- 派生:在已有类的基础上新增自己的特性而产生新类 的过程
-
基类(父类):被继承的已有类
- 直接基类:直接参与派生出某类的基类
- 间接基类:基类的基类甚至跟高层的基类
-
派生类(子类):派生出的新类
-
语法:
class 派生类 :继承访问控制 基类 { public: 公有成员列表 protected: 受保护成员列表 private: 私有成员列表 };
示例:
// 基类Point类的定义
class Point {
private:
float x, y;
public:
void initPoint (float = 0, float y = 0){
this->x = x;
this->y = y;
}
void move(float offX, float offY){
x += offX;
y += offY;
}
float getX() { return x; }
float getY() { return y; }
};
// 派生类定义
class Rectangle: public Point {
private:
float width, height;
public:
void initRectangle(float x, float y, float w, float h) {
initPoint(x, y); // 调用基类公有成员函数
this->width = w;
this->height = h;
}
float getHeight() { return height; }
float getWidth() { return width; }
};
三、访问控制规则
3.1 public继承
-
定义一个父类:
- 公共、保护、私有属性各有一个
class Base1 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
- 定义一个子类,继承上述父类
class Son1 : public Base1 {
public:
void func() {
m_A = 10; // 父类的公共权限成员,子类中依旧是公共权限
m_B = 10; // 父类的保护权限成员,子类中依旧是保护权限
}
};
-
结论与分析
-
父类中的公共权限成员,在子类中依旧是公共权限
-
类外可以访问
-
-
父类中的保护属性,在子类中依旧是保护属性
-
父类中的私有属性,在子类中不可访问
-
3.2 protected继承
class Son2 : protected Base2 {
public:
void func() {
m_A = 100; // 父类的公共权限成员,子类中变成保护权限
m_B = 100; // 父类的保护权限成员,子类中依旧是保护权限
//m_C = 100; // 父类的私有成员,子类访问不到
}
};
-
m_A在父类中是公共权限成员,类外可以访问,但是子类通过保护继承的方式进行继承,其在子类中变成了保护权限,在类外就无法对其进行访问
-
m_B在父类中是保护权限成员,子类通过保护继承的方式进行继承,其在子类中依旧是保护权限,在类外就无法对其进行访问
3.3 private继承
class Son3 : private Base3 {
public:
void func() {
m_A = 100; // 父类的公共权限成员,子类中变成私有成员
m_B = 100; // 父类的保护权限成员,子类中变成私有成员
//m_C = 100; // 父类的私有成员,子类访问不到
}
};
-
通过私有继承方式继承,父类中的三种属性成员均会在子类中变成私有属性
四、构造函数和析构函数的继承
- 基类:
- 基类中的数据成员所构成的部分——基类子对象
- 基类子对象由基类中声明的构造函数初始化
- 派生类:
- 派生类的对象的数据结构由基类中声明的数据成员和在派生类中声明的数据成员构成
- 派生类的构造函数只负责初始化在派生类中声明的数据成员
- 派生类的构造函数必须通过调用基类的某个构造函数来初始化基类子对象
- 不管派生类在类层次上有多少个祖先类,其构造函数只能用其直接基类的构造函数
- 构造函数不被继承,但可以被调用
- 子类继承父类后,当创建子类对象时,也会调用父类的构造函数
- 先调用父类的构造函数,再调用子类的构造函数
- 先调用子类的析构函数,再调用父类的析构函数
#include <iostream>
using namespace std;
// 父类Base
class Base {
public:
Base() { cout << "constructing Base object.\n"; }
~Base() { cout << "constructing Base object.\n"; }
};
// 子类Derived
class Derived:public Base{
public:
Derived(){ cout << "constructing Derived object.\n"; }
~Derived(){ cout << "Destructing Derived object.\n"; }
};
int main(){
// 创建一个Derived类对象,与此同时自动调用Derived的无参构造函数
// 先调用父类的构造函数,再调用子类的构造函数
Derived obj;
// 程序结束,自动调用Derived的析构函数,然后再调用父类的析构函数
// 先调用父类的析构函数,再调用子类的析构函数
return 0;
}
五、派生类构造函数
-
派生类构造函数应包括初始化本身数据成员和基类子对象的形式参数
-
派生类构造函数实现时使用初始化列表将基类子对象的参数传递给基类构造函数
-
实现带初始化列表的派生类构造函数的形式如下:
派生类名(参数表):基类名(基类构造函数参数表){ 派生类构造函数体 }
示例:带参数构造函数
#include <iostream>
using namespace std;
// 父类Base
class Base {
private:
int base;
public:
Base(); // 无参构造函数Base
Base(int base); // 有参构造函数Base
~Base(); // 无参析构函数Base
void print(); // 打印函数
};
Base::Base() {
base = 0; // 无参构造函数将私有属性base初始化为0
cout << "Base's default constructor called." << endl;
}
Base::Base(int base) {
this->base = base; // 有参构造函数将传入的base值赋给私有属性base
cout << "Base's constructor called." << endl;
}
Base::~Base() {
cout << "Base's destructor called." << endl;
}
void Base::print() {
cout << "base = " << base << endl;
}
// 子类Derived,继承父类Base
class Derived: public Base {
private:
int derived;
public:
Derived();
Derived(int base, int derived);
~Derived();
void print();
};
Derived::~Derived() {
cout << "Derived's destructor called." << endl;
}
Derived::Derived() {
derived = 0;
cout << "Derived's default constructor called." << endl;
}
/*
“:Base(base)” 表明在准备执行Derived构造函数的时候要先调用Derived的父类Base的有参构造函数Base(int base),将传入的base值给父类的有参构造函数,然后“derived(derived)”再将传入的参数derived传给子类Derived的私有属性derived
*/
Derived::Derived(int base, int derived): Base(base), derived(derived) {
cout << "Derived's constructor called." << endl;
}
void Derived::print() {
Base::print();
cout << "derived = " << derived << endl;
}
int main() {
// 定义子类的对象obj,同时自动调用Derived的有参构造函数,并将参数(5,6)传入
Derived obj(5, 6);
obj.print();
// 先调用子类析构函数,再调用父类析构函数
return 0;
}
六、派生类析构函数
- 析构函数不被继承,派生类要自行声明
- 声明方法与一般类的析构函数相同
- 不需要显式地调用基类地析构函数,系统会自动隐式调用
- 析构函数的调用顺序与构造函数相反
#include <iostream>
using namespace std;
class Point {
private:
float x, y;
public:
Point() { x = 1; y = 1;} // 缺省构造函数
Point(float x, float y) {this->x = x; this->y = y;} // 有参构造函数
~Point() {} // 析构函数
void move(float offX, float offY) { x += offX; y += offY; }
float getX() { return x; }
float getY() { return y; }
void display() { cout << "x=" << x << "y=" << y; }
};
class Rectangle: public Point {
private:
float width, height;
public:
Rectangle();
rectangle(float x, float y, float w, float h); // 从父类继承了x和y属性,因此要初始化四个参数
~Rectangle(){}
float getHeight() { return height; }
float getWidth() { return width; }
void display();
};
// 如果省略Point(1,1),则自动调用无参构造函数
Rectangle::Rectangle():Point(1,1){
wigth = 1;
height = 1;
}
// Rectangle类的有参构造函数
Rectangle::Rectangle(float x, float y, float w, float h):Point(x, y){
width = w;
height = h;
}
void Rectangle::display() {
Point::display();
cout << "wigth = " << width << "height = " << height << endl;
}
int main(){
Rectangle r1;
r1.display();
Rectangle r2(3, 3, 5, 6);
r2.display();
return 0;
}
七、继承同名成员处理方式
当子类和父类出现同名成员,如何通过子类对象,访问到子类或父类中同名成员?
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
-
访问同名成员变量
父类和子类中有一个同名的m_A成员,父类中m_A为100,子类中m_A为200
class Base { public: Base() { m_A = 100; } int m_A; }; class Son : public Base { public: Son() { m_A = 200; } int m_A; };
访问子类Son中的m_A:
void test() { Son s; cout << "m_A = " << s.m_A << endl; }
通过子类Son对象访问父类Base中的m_A:
void test() { Son s; cout << "m_A = " << s.Base::m_A << endl; }
-
访问同名成员函数
若子类中出现和父类同名的成员函数,子类的同名函数会隐藏掉父类中所有同名成员函数
- 若想访问到父类中被隐藏的同名成员函数,需要加作用域
-
访问同名静态成员
-
静态成员变量的属性
- 所有对象共享同一份数据
- 编译阶段分配内存
- 类内声明,类外初始化
-
静态成员函数的属性
- 所有对象共享同一个函数
- 只能访问静态的成员变量
-
静态成员和非静态成员出现同名,处理方式一致
-
静态成员变量
静态成员类内声明,类外初始化
class Base { public: static int m_A; }; int Base::m_A = 100; // 类内声明,类外初始化 class Son :public Base { public: static int m_A; }; int Son::m_A = 200; // 类内声明,类外初始化
-
静态成员函数
class Base { public: static int m_A; // 静态成员函数 static void func() { cout << "Base - static void func()" << endl; } }; int Base::m_A = 100; // 类内声明,类外初始化 class Son :public Base { public: static int m_A; // 静态成员函数 static void func() { cout << "Son - static void func()" << endl; } }; int Son::m_A = 200;
-
-