您的位置:首页 > 编程语言 > C语言/C++

C++面向对象的程序设计——继承

2016-06-15 08:52 429 查看
首先让我们了解一个面向对象程序设计的4个主要特点:抽象,封装,继承和多态。其中继承性和派生性是面向对象程序设计的两个重要特性。

1.什么是继承:

在C++中继承就是在一个已存在类的基础上建立一个新类,然后新类可以从已有类那里获得它已有的特性。

继承可以提高代码的复用率。

class Base
{
public:
int a;
int b;
};
class Derived :public Base
{
public:
int c;
};
在这里首先定义了一个基类Base它有两个公有成员a,b。然后通过Base类派生出一个新类Derived,称它为Base的派生类,那么Derived就继承了Base类的两个公有成员a,b与此同时又添加了属于自己的成员c。

2.派生类的声明:

class 派生类名:[继承方式] 基类名

{

派生类新增加的成员

};

其中继承方式又包括:public(公有的),private(私有的)和protected(受保护的)三种。

3.继承关系与访问限定符:





3.1.公有继承派生类的访问属性:



3.2.私有继承派生类的访问属性:



3.3.保护继承派生类的访问属性:



总结:
1.基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
2.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3.protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
4.不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
5.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6.在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承.
4.派生类的默认构造函数:
4.1派生类中构造函数的调用顺序:



通过一个例子来验证:

class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
};

class C
{
public:
C()
{
cout << "C()" << endl;
}
};

class Derived :private Base
{
public:
Derived()
{
cout << "Derived()" << endl;
}
private:
C c;
};
int main()
{
Derived d;
system("pause");
return 0;
}
运行结果为:



显而易见的程序在调用派生类的构造之前首先调用的是基类的构造函数,然后进入自己的构造函数构造自己的的成员对象c,与此同时调用C类的构造函数。当派生类中的所有成员对象创建完成后,最后执行自己的构造函数体。
【注意】
1.基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
2.基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
3.基类定义了带有形参表构造函数,派生类就一定定义构造函数。
4.2.派生类中析构函数的调用顺序:
派生类中析构函数的调用规则跟普通类中的调用规则完全相同,先创建的对象后析构,后创建的对象先析构。

class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base" << endl;
}
};
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class C
{
public:
C()
{
cout << "C()" << endl;
}
~C()
{
cout << "~C()" << endl;
}
};

class Derived :public Base,public A
{
public:
Derived()
{
cout << "Derived()" << endl;
}
~Derived()
{
cout << "~Derived()" << endl;
}
private:
C c;
};
void Test()
{
Derived d;
}
int main()
{
Test();
system("pause");
return 0;
}
运行结果为:



【同名隐藏】:
在继承体系中基类和派生类属于两个不同的作用域。当基类和派生类中出现同名的函数时,如果通过一个派生类对象来调用此函数,则系统自动调用派生类中的这个函数,而隐藏基类中与之同名的函数(虽然派生类继承了基类的这个函数)。把这种现象叫做同名隐藏。
但是在派生类中可以通过 派生类对象.基类::成员函数 这种形式来让派生类对象访问基类中成员函数。
注意:
1.在实际中在继承体系里面最好不要定义同名的成员。
2.区分函数重载,同名隐藏以及多态内容中的重写(覆盖)。
5.基类与派生类的转换(赋值兼容规则)
1. 派生类对象可以赋值给基类对象(切割/切片)

2. 基类对象不能赋值给派生类对象

3. 基类的指针/引用可以指向派生类对象

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





Base b;             //定义基类对象b
Derived d;          //定义基类对象d
b = d;              //用派生类对象d对基类对象b赋值
但是,赋值后不能通过对象b去访问派生类对象d的成员,因为b的成员与d的成员是不同的。



Base b;
Derived d;
Base *pb = &b;       //使基类指针指向基类对象b
pb = &d;             //使基类指针指向派生类对象d
Derived *pd = &d;    //使派生类指针指向派生类对象d
pd = (Derived*)&b;   //使派生类指针指向基类对象b   需要强制类型转换, pd = &b出现类型错误
【友元与继承】

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。(相当于在类外访问私有和保护成员)

【继承与静态成员】

基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
6.单继承、多继承和菱形继承
6.1.单继承:
一个派生类只有一个基类。



6.2.多继承:
一个派生类有两个或多个基类:



6.3.菱形继承:



可以通过一段简单地代码来思考菱形继承的对象模型:
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base" << endl;
}
protected:
int _b;
};

class C1:public Base
{
public:
C1()
{
cout << "C1()" << endl;
}
~C1()
{
cout << "~C1()" << endl;
}
protected:
int _c1;
};

class C2 :public Base
{
public:
C2()
{
cout << "C2()" << endl;
}
~C2()
{
cout << "~C2()" << endl;
}
protected:
int _c2;
};
class Derived :public C1,public C2
{
public:
Derived()
{
cout << "Derived()" << endl;
}
~Derived()
{
cout << "~Derived()" << endl;
}
protected:
int _d;
};
int main()
{
Derived d;
cout << sizeof(Derived) << endl;
system("pause");
return 0;
}
通过这段代码可以得出派生类Derived的类型长度以及构造函数的调用顺序。



不难看出最终的派生类是由三部分构成的即C1类,C2类和Derived类。



但是这种方法使得Derived对象中出现了两份Base类的成员,这显然是没有必要的。菱形继承存在数据冗余和二义性的问题。解决的办法就是采用虚继承的方法。
虚继承--解决菱形继承的二义性和数据冗余的问题
1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。

2. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得

已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的

损耗。
具体做法就在C1类和C2类继承Base类是通过加关键字virtual使得继承关系变成虚继承。
class C1:virtual public Base
class C2 :virtual public Base
再次查看下这种情况下Derived类的类型长度和对象的构造情况:

此时的派生类对象模型为:

此时基类成员只被保存了一份,而且C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略其他派生类(C1和C2)对虚基类构造函数的调用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: