继承与派生 (inherit & derive)表达的是一个意思。
在C++中可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。如果没有掌握继承性,就没有掌握类与对象的精华。
#if 0
学生 姓名, 年龄, 性别, 学号, 吃饭, 学习
老师 姓名, 年龄, 性别, 工号, 吃饭, 教学
抽取: 姓名, 年龄, 性别, 吃饭(可重用代码)-> 父类 基类
学生+ 学号, 学习->子类 派生类
老师+ 工号, 教学
#endif
class Human
{
public:
void eat(string food)
{
cout<<"i am eating"<<food<<endl;
}
};
class Student:public Human
// public 继承方式
{
public:
// public 访问权限
void study(string course)
{
cout<<"i am a student, i am learning"<<course<<endl;
}
};
class Teacher:public Human
{
public:
void teach(string course)
{
cout<<"i am teacher, i am teaching"<<course<<endl;
}
}
// 父类体现的是共性特点
// 子类是在父类的基础上增加新的功能
// C++通过继承关系,实现了代码的可重用性
int main()
{
Student s;
s.study("C++");
s.eat("Hamburge");
Teacher t;
t.teach("Java");
t.eat("dumplings");
return 0;
}
语法
派生类的声明:
class 派生类名: [继承方式]基类名
{
派生类成员声明;
}
继承方式
继承方式规定了如何访问基类继承的成员。继承方式有public,private,protected,继承方式不影响派生类的访问权限。影响了从基类继承来的成员的访问权限,包括派生类内的访问权限和派生类对象。
公有继承:基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类的私有成员。
私有继承:基类的公有成员和保护成员在派生类中成了私有的成员,其私有成员仍为基类的私有成员。
保护继承:基类的公有成员和保护成员在派生类成了保护成员,其私有成员仍为基类的私有成员。
protected对于外界访问属性来说,等同于私有,但可以派生类中可见。
- 全盘接收,除了构造器和析构器,基类有可能会造成派生类的成员冗余,所以说基类是需要设计的。
- 派生类有了自己的个性,使派生类有了意义。
派生类的构造
派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,然后派生类中新增的成员在派生类的构造函数中初始化。
派生类名::派生类名(参数总表)
:基类名(参数表), 内嵌子对象(参数表)
{
派生类新增成员的初始化语句;//也可出现在参数列表中
}
父类中如果有标配、重载或默认,则把默认包含进来;子类中可以不用显示地调用父类的构造器。
class Doctor:public Graduate
{
public:
Doctor(string sn, int ia, float fs, double ds, string st);
void dump()
{
print();
cout<<"title:"<<title<<endl;
}
private:
string title;
};
Doctor::Doctor(string sn, int ia, float fs, double ds, string st)
:Graduate(sn, ia, fs, ds), title(st)
// 一代了一代,不隔代负责
{
}
子类只需要对父类负责,不需要对父类的父类负责。
父类的父类,由父类负责。
初始化顺序: 父类初始化-> 父类的父类初始化-> 父类的父类的父类初始化->类对象的初始化-> 本类的初始化
派生类的赋值重载
子类未实现时,会调用父类的拷贝构造器
浅拷贝<->等位拷贝
子类一旦实现了拷贝构造,则必须显示地调用父类的拷贝构造器
// Graduate是Student的子类,则Graduate的拷贝构造器
Graduate::Graduate(const Graduate& another)
:Student(another), salary(another.salary)
{
//赋值兼容
// 子类对象(引用或指针)可以赋值给父类对象(引用或指针)
// 并且这是一个隐式转化过程
}
子类中未实现赋值重载时,会调用父类的赋值重载。
子类一旦实现赋值重载,不会主动调用父类的赋值重载。
Graduate& Graduate::operator=(const Graduate & another)
if (this == &another)
return *this;
Student::operator=(another);
this->salary = another.salary;
return *this;
父子类重名问题如何解决
class Father
{
public:
void dis()
{
cout<<"fatherfather"<<endl;
}
};
class Son: public Father
{
public:
void dis()
// shadow: 子类中会把父类重名的成员shadow掉
// 绝不要使子类中的成员同父类中的成员同名
// shadow成立的条件只和函数名有关
{
Father::dis();
cout<<"sonson"<<endl;
}
};
派生类的析构函数
派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。
析构顺序:子类->成员->父类
没有参数:不涉及重载,不涉及默认参数
overload 重载 发生在同一个作用域 函数名同,参数列表不同(个数、类型、顺序),希望发生
shadow 发生在父子类当中,只要函数名相同,即可构成shadow,不希望发生
继承方式
类内部的 private, protected, public 影响访问权限
继承中 private, protected, public 父类中的成员在子类中的访问权限,不影响子类现有的成员访问方式
1. 在子类中
2. 在子类对象中
99%只使用public的继承方式
father | public | protected | private |
---|---|---|---|
pub | pub | pro | pri |
pro | pro | pro | pri |
pri | inaccess | inaccess | inaccess |
father | public | public | public |
---|---|---|---|
pub | pub | pub | pub |
pro | pro | pro | pro |
pri | inaccess | inaccess | inaccess |
public 提供接口用的
protected 隐藏数据用的,传承数据用的
private 隐藏数据用的
father | protected | protected | protected |
---|---|---|---|
pub | pro | pro | pro |
pro | pro | pro | pro |
pri | inaccess | inaccess | inaccess |
father | private | private | private |
---|---|---|---|
pub | pri | inaccess | inaccess |
pro | pri | inaccess | inaccess |
pri | inaccess | inaccess | inaccess |
继承方式总结
public 传承接口,也传承了数据
protected 传承数据
private 既没有传承接口,也没有传承数据
私有继承和保护继承的存在意义
此两种方式有效的防止了基类公有接口的扩散,是一种实现继承,而不是一种单纯的is-a的关系了。是在内部完成一些权限控制。
多继承
派生类名::派生类名(参数总表)
: 基类名1(参数表1), 基类名2(参数表2), ..., 基类名n(参数表n), 内嵌子对象1(参数列表1), 内嵌子对象2(参数列表2), ..., 内嵌子对象n(参数列表n)
{
派生类新增成员的初始化语句;
}
虚继承的意义:
在多继承中,保存共同基类的多份同名成员,虽然有时是必要的,可以在不同的数据成员中分别存放不同的数据,但在大多数情况下,是我们不希望出现的。因为保留多份数据成员的拷贝,不仅占有较多的存储空间,还增加了访问的困难。为此C++提供了虚基类和虚继承机制,实现了在多继承中只保留一份共同成员。虚基类,需要设计和抽象,虚继承,是一种继承的扩展。
虚基类(virtual base class)是抽象和设计的结果。
class 派生类名:virtual 继承方式 基类
class A
{
A(int i)
{}
};
class B: virtual public A
{
B(int n):A(n){}
};
class C: virtual public A
{
C(int n):A(n){}
};
class D:public B, public C
{
D(int n)
: A(n), B(n), C(n)
{}
};