允许程序员在保持原有的特性基础上进行扩展,增加功能,这样产生新的类,称作是派生类。继承呈现了面向对象程序设计的层析结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。
1、定义基类:
基类有两种成员函数:一种是希望其派生类进行覆盖覆盖(virtual虚函数,动态绑定),另一种是派生类直接继承而不要改变的函数。
2、定义派生类:
派生类继承了基类的:1)所有成员变量,包括static静态成员变量。2)作用域,但是友元关系没有继承。
2.1 派生类向基类的类型转换:一个派生类包括两部分:从基类继承的对象和自己定义的子对象。
由于派生类对象中含有基类继承的部分(继承的关键),我们可以把派生类对象当成基类对象来使用。
Base item;// 基类对象item
Derived bulk;// 派生类对象bulk
Base *p=&item;// p指向Base对象
p=&bulk;// p指向派生类对象bulk的Base部分
2.2 派生类构造/析构函数:首先初始化基类部分,再按照声明顺序初始化派生类的成员。析构时先析构派生类,再析构基类。
3、类型转换与继承(只对指针或引用有效);
只存在派生类向基类的类型转化,因为每个派生类对象都包含一个基类对象
Derived bulk;// 派生类对象
Base* item=&bulk;// item指向派生类对象bulk的基类部分。
不存在基类向派生类的类型转化
Base base;// 基类对象
Derived* bulk=&base// 错误,基类可能不是派生类的一部分。
4、抽象基类
纯虚函数:基类中声明但是没有实现,需要在派生类中实现
Class Base{
double net_price() const=0;// 纯虚函数,基类中没定义
};
含有纯虚函数的类是抽象基类。
性质:不能创建抽象基类的对象,必须是在派生类中给出net_price的定义后,可以创建派生类的对象。
5、访问控制与继承
5.1 protected 关键字访问类的对象,派生类成员函数只能访问派生类对象中的基类部分,而不能直接访问基类对象。
Class Base{
protected:
int a;
}
class Derived:public Base{
friend void func(Derived&);// 正确,派生类函数只能访问派生类对象中的基类部分
friend void func(Base&);// 错误,派生类函数不能直接访问基类对象
}
虚继承
目的:解决多继承(菱形继承)时很容易产生命名冲突。
类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A-->B-->D 这一路,另一份来自 A-->C-->D 这一条路。
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突,而且很少有这样的需求。
为了解决这个问题,C++提供了虚基类,使得在派生类中只保留间接基类的一份成员。
#include <iostream>
using namespace std;
class A {
protected:
int a;
public:
A(int a) : a(a) {}
};
class B : virtual public A {// 加virtual 虚继承
protected:
int b;
public:
B(int a, int b) : A(a), b(b) {}
};
class C : virtual public A {// 加virtual
protected:
int c;
public:
C(int a, int c) : A(a), c(c) {}
};
class D : virtual public B, virtual public C {// 两个虚继承virtual
private:
int d;
public:
D(int a, int b, int c, int d) : A(a), B(a, b), C(a, c), d(d) {}// 注意还要初始化虚基类A
void display();
};
void D::display()
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
int main()
{
(new D(1, 2, 3, 4))->display();
return 0;
}
PS:
1、注意D的构造函数,除了对直接基类B、C初始化,还需要对虚基类A进行初始化。(由于虚基类在派生类中只有一份成员变量,所以对这份成员变量的初始化必须由派生类直接给出。)
2、C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。