简介
- c++最重要的是代码重用,通过继承机制可以利用已有的数据类型定义新的数据类型,新的数据类型不仅拥有旧类成员,还拥有新类成员。
- 派生类的成员包含两个部分:1.从基类继承的,2.自己增加的成员。
- 子类继承父类,子类拥有父类中的全部成员变量和成员方法(除构造函数和析构函数),但在子类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。
- 重载运算符,深拷贝不能继承,因为派生类继承的方法的特征标与基类完全相同,但赋值运算符的特征标随类而异,因为它包含了一个类型为其所属类的形参。
- 如果对象属于派生类,编译器使用基类中的赋值运算符处理派生类中的基类部分,如果显式的提供了赋值运算符,则使用该运算符。
继承的格式
派生类定义格式:
Class 派生类名称:继承方式 基类名
{
派生类新增的数据成员和成员函数
}
其中,继承方式有:public,protected,private
继承方式
- 对于继承类,构造函数将使用成员初始化列表语法,它使用类名称来标识构造函数。
- 派生类不能直接访问基类的私有成员,必须通过基类方法进行访问。
1.public:公有继承
- 建立is-a的关系
1.父类中的public成员,在子类中也是public。
2.父类中的private成员,在子类中是不可见的,但存在。
3.父类中的protected成员,在子类中是protected,可见的,类似于本类中的私有成员。
2.private:私有继承
- 建立has-a的关系
1.父类中的public成员,在子类中也是private。
2.父类中的private成员,在子类中是不可见的,但存在。
3.父类中的protected成员,在子类中是private。 - 只能在派生类的方法中使用基类方法。
- 私有继承能够使用类名称和作用域来调用基类方法。
- 访问基类对象:
const string & Student::Name() const
{
return (const string &) *this;
}
- 上述方法返回一个引用,该引用指向用于调用该方法的Student对象中的继承而来的string对象。
3.protected:保护继承
- 建立has-a的关系
1.父类中的public成员,在子类中也是protected,类似于本类中的私有成员。
2.父类中的private成员,在子类中是不可见的,但存在。
3.父类中的protected成员,在子类中是protected。
使用using重新定义访问权限
- 就像名称空间那样来指出派生类可以使用基类的特定成员,即使采用的是私有派生。
class Student : private std::string, private std::valarray<double>
{
...
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
...
};
4.单继承
调用构造和析构的顺序
子类中只有基类情况
- 构造顺序:基类—>>子类
- 析构顺序:子类—>>基类
子类中有基类、对象成员
- 构造顺序:基类—>>对象成员—>>子类
- 析构顺序:子类—>>对象成员—>>基类
详解子类中的构造
- 创建子类的构造函数时,如果不显式的调用父类的构造函数,则子类会默认调用基类中的默认无参构造。
- 否则,子类必须用初始化列表显式调用基类中的有参构造。
- 子类构造函数可以使用初始化列表将值传递给基类构造函数。成员初始化列表只能用于构造函数。
- 格式:
Son(int a,int b,...):Father(b,...)
{
this->a = a;
}
- 子类中构造函数总结:
1.首先创建基类对象。
2.子类构造函数应通过成员初始化列表将基类的信息传递给基类构造函数。
3.子类构造函数应初始化子类新增的数据成员。 - 创建子类对象时,程序先调用基类构造函数,然后再调用子类的构造函数。子类的构造函数总是调用一个基类的构造函数。
派生类和基类之间的特殊关系
1.派生类对象可以使用基类方法。
2.基类指针可以在不进行显式类型转换的情况下指向派生类对象。
3.基类引用可以在不进行显式类型转换的情况下引用派生类对象。
子类:RatedPlayer,父类:TableTennisPlayer
RatedPlayer rplayer1(1140, "Mallory", "Duck", true);
TableTennisPlayer & rt = rplayer; /反过来不行
TableTennisPlayer * pt = &rplayer;
rt.Name(); // invoke Name() with reference
pt->Name(); // invoke Name() with pointer
- 基类指针或引用只能用于调用基类的方法,不能调用派生类的方法。
父子类的同名成员数据的处理
同名成员变量
-
在子类中使用父类的同名成员变量,必须加上作用域,如果是父类的私有成员,则在子类中不可见,这种方法也不行,必须在父类中是public成员。
-
解决上述问题,在父类中定义公有方法。
同名成员函数
- 子类中有与父类的同名成员函数,将屏蔽父类中所有的同名成员函数。
- 解决上述问题,调用父类中的同名成员函数时,加上作用域。
多继承
- 格式:
- 如果不指出继承方式,编译器默认为私有继承。
Class 派生类名称:继承方式1 基类名1 ,继承方式2 基类名2,...
{
派生类新增的数据成员和成员函数
}
菱形继承
- 具有公共祖先,会产生二义性,采用作用域方式访问
虚继承
- 解决上述菱形继承的问题,过程中会产生虚继承指针vbptr和虚继承表vbtable(保存了当前的虚指针相对于虚基类的首地址的偏移量),其目的是保证不管多少个继承,虚基类的数据只有一份。
- 从虚基类的一个或多个实例派生而来的类将只继承了一个基类对象。
- 虚继承只能解决具备公共祖先的多继承所带来的二义性问题。
- 格式:
class 子类:virtual public 父类
{
。。。
};
- 现在,SheepTuo只包含Animal对象的一个副本,从本质上说,继承的Sheep和Tuo共享一个Animal对象。
- 在SheepTuo中,应该显式调用基类构造函数,否则使用基类的默认构造函数。
- 多继承中,调用方法时会存在二义性,可以用作用域解决。最好的方法是:在SheepTuo中重新定义该方法,并指明使用哪个父类的方法。
- 当类通过多条虚途径和多条非虚途径继承某个特定的基类时,该类将包含一个表示所有的虚途径的基类子对象,和分别表示各条非虚途径的多个基类子对象。
继承和动态内存分配
派生类不使用new
- 不需要为派生类定义显式析构函数,拷贝构造函数,赋值运算符。
派生类使用new
- 必须为派生类定义显式析构函数,拷贝构造函数,赋值运算符。
包含、层次化、组合
- 包含对象成员的类来重用类代码,是has-a的关系。与私有/保护继承相比,包含更容易实现或使用,所以优先采用这种方式。
- 但继承允许派生类访问基类的保护成员,还允许派生类重新定义从基类中继承来的虚函数,因为包含不是继承,所以不能使用这些功能。
- 如果需要使用某个类的几个对象,包含更合适。