
继承继承机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类。继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用继承是类设计层次的复用。继承的定义语法说明 派生类会将基类的成员变量和成员函数都继承下来但是访问限定符会根据继承方式而发生变化。继承方式有三种public继承protected继承private继承访问限定符public访问protected访问private访问继承基类成员的访问方式的变化类成员/继承方式public继承protected继承private继承基类的public成员派生类的public成员派生类的protected成员派生类的private成员基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员基类的private成员派生类中不可见派生类中不可见派生类中不可见总结基类的private成员在派生类中都是不可见的这里的不可见是指基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。基类成员在父类中的访问方式min成员在基类的访问限定符继承方式publicprotectedprivate。一般会把基类中不想让类外访问的成员设置为protecd成员不让类外访问但是让派生类可以访问。基类和派生类对象之间的赋值转换派生类对象会通过 “切片” 或 “切割” 的方式赋值给基类的对象、指针或引用。但是基类对象不能赋值给派生类对象。实例演示123456789101112131415161718192021222324252627282930313233343536373839404142434445464748classPerson{public:Person(constchar* name ):_name(name){}voidPrint(){cout name: _name age: _age endl;}protected:string _name ;int_age 1;};classStudent :publicPerson{public:Student():Person(xiaoming){}voidPrint(){cout name: _name age: _age _stuid: _stuid _major: _major endl;}private:int_stuid 0;// 学号int_major 0;// 专业};intmain(){Student s;// 子类对象可以赋值给父类的对象、指针和引用 反过来不行// Student对象通过 “切片” 或 “切割” 的方式进行赋值Person p1 s;Person* p2 s;Person p3 s;p1.Print();p2-Print();p3.Print();// 基类的指针可以通过强制类型转换赋值给派生类的指针Student* ps (Student*)p2;ps-Print();return0;}总结派生类对象可以“切片”或“切割”的方式赋值给基类的对象基类的指针或基类的引用就是把基类的那部分切割下来。基类对象不能给派生类对象赋值。基类的指针可以通过强制类型转换赋值给派生类的指针。但必须是基类的指针指向派生类的对象才是安全的因为如果基类是多态类型会引发多态。继承中的作用域在继承体系中基类和派生类对象都有独立的作用域子类中的成员成员变量和成员函数会对父类的同名成员进行隐藏也叫重定义。实例演示123456789101112131415161718192021222324252627282930313233classPerson{public:Person(constchar* name ):_name(name){}voidPrint(){cout name: _name age: _age endl;}protected:string _name ;int_age 1;};classTeacher :publicPerson{public:voidPrint(){cout name: _name age: _age jobid: _jobid endl;}private:int_jobid 0;// 工号};intmain(){Teacher t;t.Print();t.Person::Print();// 子类会隐藏重定义父类的同名成员同名函数或同名成员变量 可以通过指定域作用限定符访问return0;}代码运行结果如下得出结论 子类中的成员成员变量和成员函数会对父类的同名成员进行隐藏如果相要访问父类的同名成员必须指定类域访问。看下面一个小问题 请问A中的fun函数和B中的fun函数是构成重载还是隐藏12345678910111213141516171819202122classA{public:voidfun(){cout func() endl;}};classB :publicA{public:voidfun(inti){A::fun();cout func(int i)- i endl;}};voidTest(){B b;b.fun(10);};答案 两个函数在不同的作用域不可能构成重载。因为构成重载的条件是两个函数必须在同一作用域而隐藏是要求在基类和派生类不同作用域的所以这里同名成员是构造隐藏。派生类的默认成员函数C中的每个对象中会有6个默认成员函数。默认的意思就是我们不写编译器会生成一个。那么在继承中子类的默认成员函数是怎么生成的呢先看下面一个例子123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475classPerson{public:Person(constchar* name ,intage 1):_name(name),_age(age){cout Person() endl;}Person(constPerson p):_name(p._name), _age(p._age){cout Person(const Person p) endl;}Person operator(constPerson p){_name p._name;_age p._age;cout Person operator(const Person p) endl;return*this;}voidPrint(){cout name: _name age: _age endl;}~Person(){cout ~Person() endl;}protected:string _name;int_age;};classStudent :publicPerson{public:Student(constchar* name,intage,intstuid 0):Person(name, age)// 此处调用父类的构造函数堆继承下来的成员进行初始化不谢的话编译器调用父类的默认构造函数, _stuid(stuid){cout Student() endl;}Student(constStudent s):Person(s)// 子类对象可以传给父类的对象、指针或引用,_stuid(s._stuid){cout Student(const Student s) endl;}Student operator(constStudent s){cout Student operator(const Student s) endl;if(this! s){Person::operator(s);// 先完成基类的复制_stuid s._stuid;}return*this;}voidPrint(){cout name: _name age: _age _stuid: _stuid endl;}~Student(){// 基类和派生类的析构函数的函数名都被编译器处理成了destruction构成隐藏是一样指定域访问//Person::~Person();// 不需要显示调用 编译器会自动先调用派生类的析构函数然后调用基类的析构函数cout ~Student() endl;}private:int_stuid;// 学号};测试1构造函数和析构函数1234voidtest1(){Student s(小明,18,10);}代码运行结果如下总结1 子类的构造函数必须调用基类的构造函数初始化基类的那一部分成员如果基类没有默认构造函数则必须在派生类构造函数的初始化列表阶段显示调用。子类的析构函数会在被调用完成后自动调用基类的析构函数清理基类的成员。不需要显示调用。这里子类和父类的析构函数的函数名会被编译器处理成destructor这样两个函数构成隐藏。测试2拷贝构造函数12345voidtest2(){Student s1(小明, 18, 10);Student s2(s1);}代码运行结果如下总结2 子类的拷贝构造必须代用父类的拷贝构造完成父类成员的拷贝。测试3operator结论3 子类的operator必须调用基类的operator完成基类的赋值。思考如何设计一个不能被继承的类 把该类的构造函数设为私有。如果基类的构造函数是私有那么派生类不能调用基类的构造函数完成基类成员的初始化则无法进行构造。所以这样设计的类不可以被继承。后面还会将加上final关键字的类也不可以被继承总结子类的构造函数必须调用基类的构造函数初始化基类的那一部分成员如果基类没有默认构造函数则必须在派生类构造函数的初始化列表阶段显示调用。子类的拷贝构造必须代用父类的拷贝构造完成父类成员的拷贝。子类的operator必须调用基类的operator完成基类的赋值。子类的析构函数会在被调用完成后自动调用基类的析构函数清理基类的成员。不需要显示调用。子类对象会先调用父类的构造在调用子类的构造。子类对象会先析构子类的析构再调用父类的析构。继承中的两个小细节继承和友元友元关系不能被继承。也就是说基类的友元不能够访问子类的私有和保护成员。继承和静态成员基类定义的static静态成员存在于整个类中不属于某个类无论右多少个派生类都这有一个static成员。实例演示123456789101112131415161718192021222324252627282930classPerson{public:Person(){_count;}// static成员存在于整个类 无论实例化出多少对象都只有一个static成员实例staticint_count;};intPerson::_count 0;classStudent :publicPerson{public:int_stuid;};intmain(){Student s1;Student s2;Student s3;// Student()._count 10;cout 人数 Student()._count - 1 endl;return0;}代码运行结果如下单继承和多继承菱形继承单继承 一个子类只有一个直接父类时称这个继承关系为单继承。多继承 一个子类有两个或以上的直接父类时称这个继承关系为多继承。菱形继承 多继承的一种特殊情况。多继承带来的问题 子类会得到两份BenZ的数据会造成数据冗余和二义性。虚拟继承概念为了解决菱形继承带来的数据冗余和二义性的问题C提出来虚拟继承这个概念。虚拟继承可以解决前面的问题在继承方式前加椰果virtual的关键字即可。123456789101112131415161718192021classPerson{public:string _name;};// 不要在其他地方去使用。classStudent :virtualpublicPerson{public:int_num;//学号};classTeacher :virtualpublicPerson{public:int_id;// 职工编号};classAssistant :publicStudent,publicTeacher{protected:string _majorCourse;// 主修课程};虚拟继承的原理先看下面一串代码12345678910111213141516171819202122232425262728293031323334classA{public:int_a;};classB :virtualpublicA{public:int_b;};classC :virtualpublicA{public:int_c;};classD :publicB,publicC{public:int_d;};intmain(){D d;d.B::_a 1;d.C::_a 2;d._b 4;d._c 5;d._d 6;return0;}我们通过内存窗口查看它的对象模型原理 从上图可以看出A对象同时属于B和CB和C中分别存放了一个指针这个指针叫虚基表指针分别指向的两张表叫虚基表虚基表中存的是偏移量B和C通过偏移量就可以找到公共空间存放A对象的位置。组合与继承总结一下几点组合和继承都属于类层次的复用。public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象-。组合是一种has-a的关系。假设B组合了A每个B对象中都有一个A对象。优先使用对象组合而不是类继承 。继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用。术语“白箱”是相对可视性而言在继承方式中基类的内部细节对子类可见 。继承一定程度破坏了基类的封装基类的改变对派生类有很大的影响。派生类和基类间的依赖关系很强耦合度高。对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用因为对象的内部细是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系耦合度低。优先使用对象组合有助于你保持每个类被封装。实际尽量多去用组合。组合的耦合度低代码维护性好。不过继承也有用武之地的有些关系就适合继承那就用继承另外要实现多态也必须要继承。类之间的关系可以用继承可以用组合就用组合。C的缺陷之一多继承就是一个。多继承会带来菱形继承菱形继承又会带来数据冗余和二义性为了解决这个问题又引入了虚拟继承。进而导致C的底层结构对象模型非常复杂这样会带来一定的损失。所以说尽量不要设计出菱形继承。