**继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化 class Person { public : void Print () { cout<<_name <<endl; } protected : string _name ; // 姓名 private : int _age ; // 年龄 }; //三种继承方式,对应上面图表 //class Student : protected Person //class Student : private Person class Student : public Person { protected : int _stunum ; // 学号 };
注意:
1.基类private成员在派生类中无论以什么方式继承都是不可见的。不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上派生类对像无法访问;
2.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式;
3. 在实际运用中一般使用都是public继承,不提倡使protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强;
派生类(子类)对象可以赋值给基类(父类)对象/指针/引用;
即将父类中那部分赋值给基类;
这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
基类对象不能赋值给派生类对象;
基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换;
Student sobj ; // 1.子类对象可以赋值给父类对象/指针/引用 Person pobj = sobj ; Person* pp = &sobj; Person& rp = sobj;
//2.基类对象不能赋值给派生类对象 sobj = pobj;//错误
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针 pp = &sobj Student* ps1 = (Student*)pp; // 这种情况转换时可以的。 ps1->_No = 10; pp = &pobj; Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题 ps2->_No = 10;
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆 class Person { protected : string _name = "小李子"; // 姓名 int _num = 111; // SFZ号 }; class Student : public Person { public: void Print() { cout<<" 姓名:"<<_name<< endl; cout<<" SFZ号:"<<Person::_num<< endl; cout<<" 学号:"<<_num<<endl; } protected: int _num = 999; // 学号 }; void Test() { Student s1; s1.Print(); };
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域 // B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。 class A { public: void fun() { cout << "func()" << endl; } }; class B : public A { public: void fun(int i) { A::fun(); cout << "func(int i)->" <<i<<endl; } }; void Test() { B b; b.fun(10); };
6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个
成员函数是如何生成的呢?
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员;
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员;
单继承:一个子类只有一个直接父类
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况。
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
虚拟继承可以解决菱形继承的二义性和数据冗余的问题,在Student和Teacher的继承;
加关键字:virtual
菱形继承的内存对象成员模型:这里可以看到数据冗余
菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?
这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
虚基表:存放偏移量
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象,高耦合;
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象,低耦合;
优先使用对象组合,而不是类继承
继承和耦合的使用:
// Car和BMW Car和Benz构成is-a的关系 class Car{ protected: string _colour = "白色"; // 颜色 string _num = "陕ABIT00"; // 车牌号 }; class BMW : public Car{ public: void Drive() {cout << "好开-操控" << endl;} }; class Benz : public Car{ public: void Drive() {cout << "好坐-舒适" << endl;} };
// Tire和Car构成has-a的关系 class Tire{ protected: string _brand = "Michelin"; // 品牌 size_t _size = 17; // 尺寸 }; class Car{ protected: string _colour = "白色"; // 颜色 string _num = "陕ABIT00"; // 车牌号 Tire _t; // 轮胎 };