C++继承体系

来源:互联网 发布:淘宝详情页模板哪里有 编辑:程序博客网 时间:2024/06/07 07:56

继承
一.继承的概念
在C++语言中,继承作为几大重要特征之一,具有相当重要的意义。继承体系运行程序员在保持原有类的基础上,进行扩展,增加新的功能。这样产生的新的类,我们称之为派生类或者子类,而原有的类称之为父类或者基类。它是面向对象程序设计使用代码复用的重要手段,体现了面向对象程序设计的层次结构。
二.定义形式
 classDeriverClassName:acess-labelBaseClassName
1.DeriverClassName:派生类名字
2.:acess-label :继承形式,这里有三种继承形式:(1)public;(2)privated;(3)protected,当使用class时没有给出继承形式时,编译器默认为private,使用struct时,默认为public。
3.BaseClassName:基类名字
三.三种继承方式的区别
1.public(公有)继承
  当时用public继承时,派生类继承基类中的公有和保护成员,并且这些成员保持原有属性。可以通过派生类的子类进行访问。
2.protected(受保护)继承
此时派生类同样继承了基类中的公有以及受保护的成员,但是基类中受保护的成员无法通过派生类的对象进行访问,只能通过派生类的成员函数或者友元函数访问,私有成员属性不变。
3.private(私有)继承
这种继承方式使得基类中的公有成员和受保护成员属性成为派生类的私有成员,无法通过派生类的子类进行访问。
4.注意
友元关系不能继承,无论哪种方式,基类中的私有成员都被派生类继承存在,但无法访问。实际运用中一般都是public继承,少数情景下会使用protected和privated继承。
public继承代码验证
#define _CRT_SECURE_NO_WARNINGS#include<iostream>using namespace std;class Base{void Show(){cout << "_a=" << _a;cout << "_b=" << _b;cout << "_c=" << _c;}public:int _a=10;protected:int _b = 20;;private:int _c=30;};class Derived :public Base{void show(){_a = 1;//在派生类中可以访问基类的公有成员_b = 2;//在派生类中可以访问基类的保护成员_c = 10;//无法访问基类私有成员,在父类是私有成员,无法通过子类进行访问。cout << "_a=" << _a;cout << "_b=" << _b;}public:int _d;protected:int _e;private:int _f;};int main(){Derived a;a._a = 1;//通过派生类对象可访问基类公有成员a._d = 2;a._b = 10;//通过派生类对象无法访问基类受保护成员getchar();return 0;}
四.派生类的默认成员函数:
1.首先我们通过一个实例来观察在构造派生类是函数的调用过程:
#define _CRT_SECURE_NO_WARNINGS#include<iostream>using namespace std;class Base{public:Base(){cout << "基类构造函数" << endl;}Base(int a):_b(a){cout << "基类的构造函数" << endl;}Base(const Base&b){_b = b._b;cout << "基类的拷贝构造函数" << endl;}Base& operator=(const Base&b){_b = b._b;cout << "基类赋值运算符重载" << endl;}~Base(){cout << "基类析构函数" << endl;}private:int _b;};class Derived :public Base{public:       Derived(){cout << "派生类构造函数" << endl;}Derived(int a):_d(a){cout << "派生类的构造函数" << endl;}Derived(const Derived&b){_d = b._d;cout << "派生类的拷贝构造函数" << endl;}Derived& operator=(const Derived&b){_d= b._d;cout << "派生类赋值运算符重载" << endl;return *this;}~Derived(){cout << "派生类析构函数" << endl;}private:int _d;};int main(){Derived a;Derived b(a);Derived c;a = c;    getchar();return 0;}
运行结果:


从此实例我们可以看出,在构造派生类时,首先调用的是基类的构造函数,然后调用派生类的构造函数。
由此我可以总结出来在构造派生类时,函数的调用次序。


在这里,有三点要说明注意的点
(1)基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
(2)基类没有定义构造函数,则派生类也可以不用定义,全使用缺省构造函数。
(3)基类定义了带有形参表构造函数,派生类就一定定义构造函数。
2.析构函数
下面销毁创建的类对象程序运行结果:


   
总结就是以下的顺序:


五.继承体系中的作用域
1.在继承体系中基类和派生类是两个不同的作用域。
2.子类和父类中有同名成员,子类成员将屏蔽对父类成员的直接访问。(在子类成员函数中,可以使用基类::基类成员 访问)
3.为了避免这种问题,注意在实际运用中不要定义同名的成员。
在这里就不对这种情况进行代码验证。
六.继承与转换—赋值兼容规则—public继承
class Base{public:int a;private:int b;};class Derived:public Base{public:int c;private:int d;};int main(){Derived e;Base f;f = e;//派生类可以赋值给基类e = f;//出错,无法赋值Derived* pd = &f;//派生类指针无法指向基类Base*ps = &e;//基类指针可以指向派生类getchar();return 0;}
为什么会出现这种情况呢,我们通过类的对象模型来解释这个问题.

这是两个类在构造时创建的对象模型,当使用派生类对基类赋值时,派生类将其中的基类部分对应的赋值给需要赋值的对象,我们称之为切片,而反过来,基类给派生类赋值时,派生类的成员变量无法访问,所以出错。同样基类的指针可以只想派生类,而派生类的指针不能指向子类。(通过强制类型转换可以实现)。
七.继承与静态成员
基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个派生类,都只有一个static成员实例。
class Base{public:Base(){count++;}public:static int count;protected:int e;};int Base::count = 0;class Derived1:public Base{protected:int b;};class Derived2 :public Base{protected:int c;};int main(){Base a1;Base a2;Base a3;Base a4;cout << "count=" << Base::count<<endl;Base::count = 0;cout << "count=" << Base::count;getchar();return 0;}


八.单继承,多继承,菱形继承
1.一个基类派生出一个类的继承关系是单继承;

2.一个基类派生出多个派生类的继承关系是多继承;


3一个基类派生出两个派生类,同时这两个派生类又被一个类同时继承,这种关系是菱形继承。


这里我们重点讨论菱形继承所带来的问题。
首先来看菱形继承的对象模型:


菱形继承存在的最主要的问题就存在二义性和数据冗余,我们解决的方法是通过虚拟继承。
1.解决方式在通过关键字virtual
class a{public:int a;};class b :virtual public a{public:int c;};class d :virtual public a{public:int e;};class f :public b, public d{public:int g;};int main(){f a1;getchar();}
那么普通继承和虚拟继承有什么关系呢,我们来看看它的对象模型;

在虚拟继承中,会多出来四个字节用于存放地址,地址指向的是一个偏移量表格。
这个表格里,首先放的是相对于自己的偏移量,第二个存放的是相对于基类的偏移量。并且在这里会压栈一个1用于检测是否为虚拟继承。
总结:1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。 
2. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。



原创粉丝点击