继承是面向对象的主要特征之一,它使得一个类可以从现有类中派生,而不必重新定义一个新类。
继承的实质就是用已有数据类型创建新的数据类型,并保留已有数据类型的特点,以旧类为基础创建新类,新类包含了旧类的成员变量和成员函数,并且可以在新类中添加新的成员变量和成员函数。
旧类被称为基类或者父类,新类被称为派生类或者子类。
(1)类继承的一般格式
class 派生类名: [继承方式] 基类名
{
成员...
}
派生类又称子类,基类又称父类,所以以下的格式和前者是一致的:
class 子类类名: [继承方式] 父类名
{
成员...
}
继承方式有三种,分别是公有型(public)、保护型(protected)和私有型(private)。
(2)继承的使用
假设有一个 Pesonal(人) 对象,Pesonal有姓名和年龄两个属性:
class Pesonal
{
private:
string mName; // 姓名
int mAge; // 年龄
public:
void setName(string name)
{
mName = name;
}
string getName()
{
return mName;
}
void setAge(int age)
{
mAge = age;
}
int getAge()
{
return mAge;
}
};
还有一个Student(学生)对象,Student有姓名、年龄、成绩三个属性:
class Student
{
private:
string mName; // 姓名
int mAge; // 年龄
int16_t mScore; // 成绩
public:
void setName(string name)
{
mName = name;
}
string getName()
{
return mName;
}
void setAge(int age)
{
mAge = age;
}
int getAge()
{
return mAge;
}
void setScore(int score)
{
mScore = score;
}
int getScore()
{
return mScore;
}
};
学生也是人,因此学生也有人的特性,Student也具有Pesonal的姓名和年龄两种属性,所以,这两个类可以使用继承关系。
我们需要对Student类做下修改,代码如下:
class Student:public Pesonal
{
private:
int16_t mScore; // 成绩
public:
void setScore(int score)
{
mScore = score;
}
int getScore()
{
return mScore;
}
};
Student类中,只要添加Student特有的属性即可,Student可以使用Pesonal中的属性。
使用代码如下:
Student student;
student.setName("zhangsan");
student.setAge(16);
student.setScore(90);
cout << "姓名:" << student.getName() << endl;
cout << "年龄:" << student.getAge() << endl;
cout << "成绩:" << student.getScore() << endl;
(2)访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
访问 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类(子类) | yes | yes | no |
外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
- 继承方式
(3)继承方式
继承方式有 public、private、protected 3种,3种继承方式说明如下:
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
(4)构造函数和析构函数的访问顺序
为了方便研究派生类和基类构造函数和析构函数的调用顺序,需要写一个演示代码:
#include <iostream>
#include <string>
using namespace std;
class Pesonal
{
private:
string mName; // 姓名
int mAge; // 年龄
public:
Pesonal()
{
cout << "构造函数 --- Pesonal" << endl;
}
~Pesonal()
{
cout << "析构函数 --- Pesonal" << endl;
}
void setName(string name)
{
mName = name;
}
string getName()
{
return mName;
}
void setAge(int age)
{
mAge = age;
}
int getAge()
{
return mAge;
}
};
class Student:public Pesonal
{
private:
int16_t mScore; // 成绩
public:
Student()
{
cout << "构造函数 --- Student" << endl;
}
~Student()
{
cout << "析构函数 --- Student" << endl;
}
void setScore(int score)
{
mScore = score;
}
int getScore()
{
return mScore;
}
};
int main()
{
Student student;
student.setName("zhangsan");
student.setAge(16);
student.setScore(90);
cout << "姓名:" << student.getName() << endl;
cout << "年龄:" << student.getAge() << endl;
cout << "成绩:" << student.getScore() << endl;
return 0;
}
在运用时,我们只创建了派生类对象,调用后的输出结果是:
构造函数 --- Pesonal
构造函数 --- Student
姓名:zhangsan
年龄:16
成绩:90
析构函数 --- Student
析构函数 --- Pesonal
所以,构造函数的调用顺序是:
基类(父类) --> 派生类(子类)
析构函数的调用顺序和构造函数相反:
派生类(子类) --> 基类(父类)
(5)子类显式调用父类的构造函数
当子类实例化时,子类和父类的构造函数都会被执行;
子类执行哪个构造函数取决于子类实例化传入的参数;
那么,到底执行了父类的哪个构造函数呢?
一般情况下,默认调用父类没有形式参数的构造方法,如果想要调用父类有形式参数的构造方法,我们需要显式调用父类的构造函数,代码如下:
Pesonal类的带参构造方法:
Pesonal(string name, int age)
{
mName = name;
mAge = age;
}
Student类的无参构造方法:
Student() :Pesonal("zhangsan", 16)
{
}
此时,先调用Pesonal类的带参构造方法,再调用Student类的无参构造方法。
(6)上转型对象
上转型其实就是子类转成父类,我们先看下代码:
Student student;
student.setName("zhangsan");
student.setAge(16);
student.setScore(90);
Pesonal pesonal = student; // 上转型,子类转成父类
cout << "姓名:" << pesonal.getName() << endl;
cout << "年龄:" << pesonal.getAge() << endl;
Student有一个属性:成绩,由于Student继承Pesonal,相当于Student有三个属性:姓名、年龄、成绩;
Pesonal只有两个属性:姓名、年龄;
当Student赋值给Pesonal时,即:
Pesonal pesonal = student; // 上转型,子类转成父类
pesonal只能访问Pesonal类中的成员,不能访问Student中的成员。
如果需要父类转成子类,这样的转换属于降级转换,降级转换必须使用强制转换,父类转子类的强制转换和上转型相反。
(7)多重继承
C++可以多重继承,多重继承的一般形式为:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
多个基类用逗号隔开。
子类可以调用多个父类的公有型和保护型的成员。
[本章完...]