文章目录
- 第八章 继承 (Inheritance)
- 第九章 多态 (Polymorphism)
- 第十章 模板 (Template)
- 十一章 IO 流 (IO Stream)
- 十二章 异常 (Exception)
第八章 继承 (Inheritance)
8.1 引入
8.1.1 为什么需要继承 why inherit?
在 C++中代码的可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。
如果没有掌握继承性,就没有掌握类与对象的精华。
8.1.2 引例
在”老师“与”学生“之前,代码重用性的思考。
//引例
//老师:姓名,性别,年龄,教课,吃饭
//学生:姓名,性别,年龄,学习,吃饭
//人类:姓名,性别,年龄,吃饭
//父类(继承关系) or 基类(派生关系):
class Human {
public:
void Eating(string food)
{
cout << "i am eating " << food << endl;
}
private:
string _name;
char _sex;
int _age;
};
//子类 or 派生类:
class Teacher:public Human {
public:
Teacher(string name ="NULL", char sex = 'N', int age = 0) :
_name(name), _sex(sex), _age(age)
{
}
void Teach(string course)
{
cout << "i am a teacher, and i am teaching " << course << endl;
}
private:
string _name;
char _sex;
int _age;
};
//子类 or 派生类:
class Student: public Human {
public:
Student(string name = "NULL", char sex = 'N', int age = 0):
_name(name),_sex(sex),_age(age)
{
}
void Learing(string course)
{
cout << "i am a student, and i am studying " << course << endl;
}
private:
string _name;
char _sex;
int _age;
};
int main(int argc, char** argv)
{
Teacher duan("DuanZhiQiang", 'M', 35);
duan.Teach("Chinese");
duan.Eating("BugerKing");
Student zheng("ZhengShaoJIe", 'M', 23);
zheng.Learing("C++");
zheng.Eating("KFC");
system("pause");
return 0;
}
Human是从学生和讲师中抽象出来的共同属性。让学生和老师均继承自Human的话, 就可以实现代码的重用性了。
8.1.3 结论
继承是一种设计的结果,通常是发生于一套类库中的,设计代码重用的方式,这种关系是一种设计而为之,不是想继承,就可随便继承的。
8.2 继承
8.2.1 定义
类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是 类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。 派生与继承,是同一种意义两种称谓。
8.2.1 定性 is-a Not has-a
is-a 是一种属于关系,如:狗属于一种动物,车属于一种交通工具(DogisanAnimal.Car is a Vehicle.)在面向对象中表现为一种继承关系。可以设计一个 Animal 类,Dog 类作为 Animal 类(基类)的派生类;设计一个Vehicle类,Car类作为Vehicle类(基类)的派生类。
has-a是一种包含、组合关系。如:车包含方向盘、轮胎、发动机(Car hassteering-wheel, wheels, engine),但不能说方向盘/轮胎/发动机是一种车;狗包含腿、尾巴,但不能说腿、 尾巴是一种狗。正确的应该说车聚合(包含)了方向盘、轮胎、发动机。 因此,如果 A is a B,则 B 是 A 的基类,A 是 B 的派生类。为继承关系。如果 A 包含 B, 则B 是 A 的组成部分。为聚合关系,可以由组成部分聚合成为一个类。 宏观意义上来讲, is-a 和 has-a 均可以实现代码的可重用性。
8.2.3 语法
class 派生类名:[继承方式] 基类名
{
派生类成员声明;
}
默认的继承方式是 private 私有继承。
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承。 下面从单继承讲起。
8.2.4 继承方式
8.2.4.1 分类
公有继承(public):基类的公有成员(public)和保护成员(protected)在派生类中保持原有访问属性,其私有成员仍为基类的私有成员。
私有继承(privated):基类的公有成员(public)和保护成员(protected)在派生类中成了私有成员,其私有成员仍为基类的私有成员。
保护继承(protected):基类的公有成员(public)和保护成员(protected)在派生类中成了保护成员,其私有成员仍为基类的私有成员。
本章节只讨论公有继承。其他两种之后学习。
8.2.4.2 集成方式影响了什么
继承方式规定了子类如何访问从基类继承的成员。
继承方式有public,private,protected。继承方式不影响派生类的原访问权限,影响了从基类继承而来的成员的访问权限,包括派生类内的访问权限和派生类对象的访问权限。
可以发现,卡在了子类中父类的private成员上。这种现象称之为不可见。protected成员在外部访问的时候等价于privated;但是在继承中是可见的。
8.2.4.3 结论
如何验证,父类中 protected 成员,在 public 继承后,仍然是 protected 的呢?答案是, 再次用public派生出孙子类,在其类内考查其可见性。
8.2.5 派生类的组成
8.2.5.1 组成图示
派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。 从基类继承过过来的表现其共性,而新增的成员体现了其个性。
这种派生类与基类的关系,我们称之为全盘接受。不管你基类里有什么,派生类通同继承下来,包括私有成员(除开构造器与析构器)。。基类有可能会造成派生类的成员冗余,比如,基类有20个功能,但是我只需要基类的一个功能,所以说基类是需设计的。
派生类有了自己的个性,使派生类有了意义。意思是,有自己的个性,这个派生类才有意义。
8.2.5.2 sizeof(父/子)
#include <iostream>
#include <typeinfo>
using namespace std;
//szieof(父/子)
class AA {
public:
AA()
{
cout << "AA构造" << endl;
cout << "&a " << &a << endl;
cout << "AA-this " << this << endl;
cout << typeid(this).name() << endl;
}
int a;
};
class BB: public AA {
public:
BB()
{
cout << "BB构造" << endl;
cout << "&b " << &b << endl;
cout << "BB-this " << this << endl;
cout << typeid(this).name() << endl;
}
int b;
};
class CC : public BB {
public:
CC()
{
cout << "CC构造" << endl;
cout << "&c " << &c << endl;
cout << "CC-this " << this << endl;
cout << typeid(this).name() << endl;
}
int c;
};
int main(int argc, char** argv)
{
CC cc;
cout << "&cc " << &cc << endl;
cout << typeid(cc).name() << endl;
system("pause");
return 0;
}
输出如下:
8.3 派生类的构造
派生类中,由基类继承而来的成员的初始化工作,还是由基类的构造函数完成,然后派生类中新增的成员在派生类的构造函数中初始化。
8.3.1 语法格式
派生类名 :: 派生类名( 总参列表 )
:基类名(参数表), 内嵌子对象( 参数表 )
{
派生类新增成员的初始化语句;
}
8.3.2 构造原理
8.3.2.1 图示
8.3.2.2 注释
由于子类中,包含了两部分内容,一部分来自父类,一部分来自子类。父类的部分,要由调用父类的构造器来完成,子类的部分,在子类的构造器中来完成始化。子类中, 有内嵌的子对象也需要构造。
顺序如下:
- 标配:1 默认无参构造器 2 重载(包含无参) 3 默参(包含无参)
- 先调用了基类的构造器, 或隐 “标配” 或显 “标配"也可以显,但无"标配”,必须得显。
- 然后内嵌子对象的构造, 或隐 “标配” 或显 “标配"也可以显,但无"标配”,必须得显。
- 必须得显,指得显示的在初始化参数列表调用, 初始化参数列表中的顺序,不代表真实的调用顺序。
就像家里来了客人,我们先给老爸倒水,再给客人倒水,最后再给自己倒水。
//派生类的构造
class AAA {
public:
AAA(int j):_a(j)
{
cout << "AAA构造" << endl;
//_a = 0;
}
int _a;
};
class CCC {
public:
CCC(int c) :_c(c)
{
cout << "CCC构造" << endl;
}
int _c;
};
class BBB:public AAA {
public:
BBB(int a, int b, int c):AAA(a), cc(c), _b(b)
{
cout << "BBB构造" << endl;
//_b = b;
}
int _b;
CCC cc;
};
int main(int argc, char** argv)
{
BBB b(3,5,10);
cout << b._a << b._b << b.cc._c << endl;
return 0;
}
输出如下:
8.3.3 实战
class Birth {
public:
Birth(int year, int month, int day)
:_year(year),_month(month),_day(day)
{
}
void DisBirth()
{
cout << "生日:" << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
class Stu {
public:
Stu(string name,char sex,float score)
:_name(name),_sex(sex),_score(score)
{
}
void DisInfo()
{
cout << "姓名:" << _name << endl;
cout << "性别:" << _sex << endl;
cout << "分数:" << _score << endl;
}
private:
string _name;
char _sex;
float _score;
};
class GraStu:public Stu {
public:
GraStu(string name, char sex, float score, float salary,int year,
int month, int day)
: Stu(name, sex, score), birth(year, month, day)
{
_salary = salary;
}
void Dis()
{
DisInfo();
cout << "工资:" << _salary << endl;
birth.DisBirth();
}
Birth birth;
private:
float _salary;
};
class Doctor :public GraStu {
public:
Doctor(string title, string name, char sex, float score, float salary, int year,
int month, int day)
:GraStu(name, sex, score, salary, year,
month, day)
{
_title = title;
}
void DisDoctorInfo()
{
cout << "学位:" << _title << endl