01:继承是什么
定义
继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 保 持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象 程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 继 承是类设计层次的复用
简单来说,继承是实现代码复用的一种手段。
继承的语法
继承权限可以不写,使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过****最好显示的写出继承方式。
打个比方
我们要实现一个学生类和一个老师类
cpp
class student {
int name; //姓名
char Date_of_birth[32]; //出生日期
char Origin[32]; //籍贯
//.......
long long School_Degree; //学号
long long Dormitory_number; //宿舍号
//........
};
class teacher {
int name; //姓名
char Date_of_birth[32]; //出生日期
char Origin[32]; //籍贯
//.......
char Graduation_school[32]; //毕业学校
char posts[32]; //职位
//........
};
可以看出老师类与学生类有很多相同的内容,如果要在两个类中都写个add函数的话,两个函数会有很多相同之处.这实在不够优雅**,**所以c++的祖师爷搞出来继承这套东西.
cpp
//个人类
class Person {
int name; //姓名
char Date_of_birth[32]; //出生日期
char Origin[32]; //籍贯
//.......
};
//学生类,学生类依赖于个人类
class student : public Person
{
long long School_Degree; //学号
long long Dormitory_number; //宿舍号
//........
};
//老师类,老师类依赖于个人类
class teacher : public Person
{
char Graduation_school[32]; //毕业学校
char posts[32]; //职位
//........
};
子类中可以按照相应的权限来使用父类的内容
02.继承关系与访问限定符
3种继承关系:public, protected ,private与3种访问限定符:public, protected ,private.总共能组合出九种访问权限.
继承关系与访问限定符的关键字字符一样,但书写位置不同,含义不同.
巧记:
- 基类的private在派生类都是不可见的.
- public>protected>private,基类的非private元素取继承关系与访问限定符较小的为权限.
举个例子
cpp
class parent {
public:
int pub;
protected:
int pro;
private:
int pri;
};
class child :public parent{ //以共有的方式继承
void fun()
{
pub = 1; // public和public ->公有继承
pro = 2; // public和protected ->保护继承
//pri = 3; // 父类为private ->在子类中不可见,访问会报错
}
};
最常见的继承方式是公有继承
父类的私有元素是否会被子类继承?
以上段代码为例,我们来使用一下sizeof()
一个int为4字节,打印的12证明child继承到了三个父类元素,也就代表子类会继承父类元素
给父类写一个构造函数,通过内存窗口也能看到
结论:子类会继承父类的全部元素,但是父类的私有元素不可访问
03:子类与父类的赋值转换
子类可以直接给父类赋值,这个过程叫做切片,(请注意父类不可给子类复制,当然有手动重载的除外)
父类及父类的指针,父类的引用都可直接由对应的子类赋值.子类的指针可以通过父类指针强制类型转换来赋值,但是有越界的可能
例:
cpp
class Person {
string _name;
int _sex;
int _age;
};
class Student : public Person {
long long _no;
};
int main()
{
Person p;
Student s;
Person* pp = &s;
Person& tmp = s;
Person qp = s;
Student* ss = (Student*)&p;//有可能越界
return 0;
}
04:继承的作用域
在继承的体系中,父类和子类有相互独立的作用域.
当子类和父类有同名函数时,会构成隐藏,请注意,隐藏只与函数名有关,与参数和返回值无关!!!!
默认调用的时子类函数,可以通过加上作用域来指定作用域
子类与父类最好不要定义重名函数
05.子类的默认成员函数
有的人死了,但他还活着!有的人活着,但他已经死了!
----鲁迅
不知道大家自类和对象之后再次看到默认成员函数是什么感觉,反正我有点心肌梗塞,你先别怕,怕了也学不会其实这里的要比类和对象里的简单一些.
- 构造函数:调用子类的构造函数时必须要调用父类的构造函数,如果父类没有默认构造函数,那么在初始化子类时必须显式的调用父类的构造函数
- 子类的拷贝构造必须要调用父类的拷贝构造.而在子类的构造函数中给父类元素赋值
- 子类operator=函数必须调用父类的operator=完成赋值,而非直接访问父类元素
- 子类的析构在调用完之后会自动的去调用父类的析构,子类的析构中不需要显示调用父类的析构
- 子类的构造会先调用父类的构造函数
- 子类的析构会最后调用父类的析构函数
06.继承与友元
父类的友元不是子类的友元函数,也就是说父类友元不能直接访问子类保护和私有
cpp
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s) {
cout << p._name << endl;
cout << s._stuNum << endl; //不是子类友元,这里不能访问
}
07:父类与静态成员
在父类中定义的静态成员,当父类被继承到子类时,所有的子类都会只使用一个静态成员.
cpp
class A {
public:
static int a;
};
class B : public A {
public:
void fun() {
cout << "在B中查看全局变量a,a的值为:"<< a << endl;
}
};
class C : public A {
public:
void fun() {
cout << "在C中查看全局变量a,a的值为:" << a << endl;
}
};
int A::a = 0;
int main() {
A aa;
B bb;
C cc;
cout << "A中aa的值改为0" << endl;
bb.fun();
cc.fun();
aa.a = 10;
cout << "A中aa的值改为10" << endl;
bb.fun();
cc.fun();
system("pause");
return 0;
}
08.多继承
多继承与单继承
单继承很好理解,譬如学生是一个人,,那么学生类就可以做人类类的子类,二者关系为单继承.
多继承可以理解为在学生和人类的基础之上,学生张三是一名某某游戏会员,那么在描述张三时,可以让张三继承两个类,这样一个子类对应多个父类,他们的关系为多继承.
Java是没有的多继承的,由此可见c++这门语言是一门旷世神作,c++程序员都聪明绝顶,Java语言过于局限,Java程序员好逸恶劳(手动滑稽),多继承毫无疑问是有现实意义的,譬如有些博士生既是老师又是学生,谷爱凌既是中国人又是美国人,马克思既是犹太人又是中国人(不是),如果使用多继承的话可以很好的实现一些面向对象功能,但是毫无疑问在后续使用菱形继承之类复杂继承时,出现了很多语法困难,c++早期版本也是一直在改进继承.
总之,多继承是有他独特的意义的,是值得学习的
多继承语法
语法很简单:在单继承的基础之上加个逗号,再加上第二个父类即可
菱形继承
可以看出,在Student类和Teacher类中都有Person类,进而在Assistant类中会有两份Person类,这就造成了代码冗余,因为两个Person中的数据很显然是一模一样的
ps:棱形继承要访问Person元素的话,需要在前面加上父类作用域,否则编译器分不清是在操作哪个Person
cpp
class Person
{
public :
string _name ; // 姓名
};
class Student : public Person
{
protected :
int _num ; //学号
};
class Teacher : public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a ;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy"; }
虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student 和
Teacher 的继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。
cpp
class Person
{
public :
string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test ()
{
Assistant a ;
a._name = "peter"; }
虚拟继承的底层原理(vs2022编译器32位)
这是一些简单的类构成的菱形继承.
cpp
class A {
public:
int _a;
};
// class B : public A
class B : virtual public A {
public:
int _b;
};
// class C : public A
class C : virtual public A {
public:
int _c;
};
class D : public B, public C {
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0; }
首先是非虚函数,看以看到A存了两份,有数据冗余的情况
虚函数,看以看到A只存了一份
可以看出,虚函数通过存储指针->偏移量来找到二者共有的A的地址