C++类的继承总结

来源:互联网 发布:欧美邮箱一手数据 编辑:程序博客网 时间:2024/06/03 13:46

、继承概念:是面向对象程序设计使代码可以复用的重要手段,保持原有类特性基础进行扩展,增加功能,产生新的类,称为派生类。

定义格式:


三种继承关系:public,protected,private

三种类成员访问限定符:public,protected,private(需要与三种继承关系区别,不能混淆

举一个简单类:B公有继承(public)与A,B为派生类,A为基类

class A{public:void FunTest1(){cout<<"FunTest1"<<endl;}int _a1;protected:int _a2;private:int _a3;};class B:public A{public:void FunTest2(){cout<<"FunTest2"<<endl;}private:int _b;};void FunTest(){B b;b.FunTest1();b._a1=1;}

继承方式与基类成员访问限定符关系变化:


总结:1.派生类继承了(无论哪一种继承方式)基类内部所有数据成员和成员函数,在派生类内部中可以访问基类的公有(public)成员和保护(protected)成员,基类的私有(private)成员存在但不可见,派生类不可以直接访问;

2.若基类成员不想在类外直接被访问,但需要在派生类中被访问,就定义为protected(即保护成员限定符是因继承才出现的);若在类外定义派生类对象,其也只能访问派生类中的public成员;

3.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象;protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分, has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,一般使用的都是公有继承。

4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

二、派生类的构造函数和析构函数(区别调用构造函数和执行函数体两种顺序)

类继承关系中派生类调用构造函数顺序:

派生类构造函数————>>基类构造函数(在派生类初始化列表调用)

函数体执行顺序:

基类构造函数————->>派生类构造函数(因为在派生类初始化列表又调用了基类构造函数,所以先执行基类函数体)

调用析构函数顺序:

派生类析构函数————>>基类析构函数

函数体执行顺序:

派生类析构函数————>>基类析构函数(因为:1派生类对象中自己独有成员创建晚,生命周期短,2派生类析构自己独有成员,基类析构自己独有成员,二者没有关系)

举例如:

class A{public:A(){cout<<"A()"<<endl;}~A(){cout<<"~A()"<<endl;}int _a1;protected:int _a2;private:int _a3;};class B:public A{public:B(){cout<<"B()"<<endl;}~B(){cout<<"~B()"<<endl;}private:int _b;};void FunTest(){B b;}
在VS2010中其运行结果显示为派生类对象b调用构造函数和析构函数的函数体执行顺序:

注意:

1.基类有缺省构造函数,派生类没有显示定义构造函数,编译器会给派生类合成默认构造函数;

2.基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表;如:

class A{public:A(int a){cout<<"A()"<<endl;}};class B:public A{public:B(int a):A(a){cout<<"B()"<<endl;}};void FunTest(){B b(2);}
3.基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数;

4.基类定义了带有形参表构造函数,派生类就一定定义构造函数。

 三.继承与转换

同名隐藏:若基类与派生类中有相同名字函数或数据成员,在类外派生类对象调用基类同名成员时,加基类作用域,如:

class A{public:void FunTest(){cout<<"A::FunTest()"<<endl;}int _a;};class B:public A{public:void FunTest(){cout<<"B::FunTest()"<<endl;}int _a;};void FunTest(){B b;b.FunTest();  //访问的是派生类中的FunTest();b.A::FunTest();//访问基类A中FunTest();b._a;             //访问派生类成员b.A::_a;         //访问基类成员}

赋值兼容规则--public继承

 1. 子类对象可以赋值给父类对象;

 2. 父类对象不能赋值给子类对象;

 3. 父类的指针/引用可以指向子类对象;

 4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

在VS2010中举例如:

class A{public:int _a;};class B:public A{public:int _b;};void FunTest(){A a;     //父类对象B b;     //子类对象a=b;//b=a;    编译错误b=*(B*)&a; //可强制类型转换(使用时需要谨慎)A* pa=&b;A& ra=b;//B* pb=&a;  编译错误//B& rb=a;   编译错误B* pb=(B*)&a;  //强转B& rb=*((B*)&a);}

四.继承方式

单继承:一个子类只有一个直接父类时称这个继承关系为单继承(简单的基本继承)


多继承:一个子类有两个或以上直接父类称为多继承


菱形继承:如

在VS2010中举简单例子:

class A{public:int _a;};class B:public A{public:int _b;};class C:public A{public:int _c;};class D:public B,public C{public:int _d;};void FunTest(){D d;}
运行监视d对象组成:

它的内存组成方式为:(内存分布按照继承顺序)

由上可知,d中_a占了两份内存空间,因为类C与类D都继承了类A的成员,所以菱形继承存在二义性与数据冗余问题,浪费空间,为解决这一问题,出现

虚继承:在继承方式前加关键字:virtual(使_a只占一份空间,能够共用)

如:

class A{public:int _a;};class B:virtual public A{public:int _b;};class C:virtual public A{public:int _c;};class D:public B,public C{public:int _d;};void FunTest(){D d;d._a=1;d._b=2;d._c=3;d._d=4;}

运行完成查看d内存:


即内存分布方式为:(即内存第一份和第三份空间是个指针,存储地址,监视查看此地址内容,存储的为分别相对于B和C自身偏移地址与相对于_a偏移地址(可把它们看成一个表))


一般派生类是虚继承时,编译器会为其合成默认构造函数,作用:将存储偏移地址的表首地址赋给其对象内存存储顺序第一个空间。

虽然虚继承解决了菱形继承的二义性和数据冗余的问题,但虚继承效率太低,每次都要访问表的偏移地址后才能访问其成员。

注意:

1.友元与继承友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员;

2.如果类中包含静态成员,无论继承多少派生类,静态成员只保存一份;

3.构造函数和析构函数不能被继承。




0 1