C++复习day7:类继承与代码重用(依据C++premier plus)

基于《C++premier plus》第13、14章学习整理:

 


第十三章 类继承


一、相关概述

1.继承完成如下工作:在已有类基础上添加功能/为类添加数据/修改类方法的行为

2.派生类声明方法形如:class RatedPlayer :public TableTennisPlayer{};也即一般类声明:派生方式 基类名

3.基类私有部分成为了派生类的一部分,但只能通过基类的公有和保护方法进行访问

4.需要在继承特性中添加(1)派生类自己的构造函数(2)根据需要添加额外的数据和函数

5.派生类构造函数必须使用基类构造函数(创建派生类对象时,程序首先创建基类对象)形如RatedPlayer::RatedPlayer (参数列表):显式调用基类构造函数{自身构造函数体;}派生成员也可以写在成员初始化列表(即添加在显式调用基类构造函数的后面)。即便不显式调基类构造函数也一定会自动调用基类的默认构造函数。

6.派生类和基类的基本关系:

(1)派生类对象可以使用非私有的基类成员

(2)基类指针和引用可以在不进行显式转换的情况下指向或引用派生类对象<但其只能调用基类具备的成员>

(3)用派生类对基类赋值/复制等操作,会调用基类的赋值/复制方法,结果是仅复制及基类成员。

7.c++具备公有/私有/保护三种继承方式,且其继承相当于is-a关系。

 

 

二、多态公有继承:同一个方法的行为随上下文(基类与派生类)而不同

1.在派生类中重新定义基类的方法:

直接在派生类中定义与基类方法同名同参的方法,在调用该方法时编译器会根据所声明的指针/引用/对象类型来选择调用

2.使用虚方法:

在基类方法前加上virtual关键字(派生类里可以不加)然后同名同参,在调用该方法时编译器会根据指针/引用/对象的内容物实际的类型来选择调用方法。

3.两个惯例:

(1)派生类中重新定义基类方法通常会定义为virtual。

(2)为基类声明一个虚析构函数确保释放派生类对象时正确顺序调用析构方法。

4.在派生方法中,常通过作用域解析运算符::显式调用基类的成员。

5.通过虚函数机制和基类指针数组可以实现数组组织不同实质上不同类的系列对象。

6.如果不定义虚析构函数,则根据声明指针/对象进行处理方法的选取,这会带来问题。

 

三、静态联编与动态联编

1.静态联编(又称早期联编)编译过程中完成联编<c++解决重载问题>;动态联编(又称晚期联编)在程序运行时选择正确的虚函数代码

2.c++默认的仍旧是静态联编。因为动态联编需要设置追踪基类指针等方式实现,其效率低于静态联编。

3.定义了虚函数的类会得到一个指向其独立虚函数表的隐藏成员,所以虚函数灵活但是存在开销。

4.注意事项:

(1)构造函数不符合一般继承机制,也不允许声明成虚函数。

(2)除非类不用做基类,否则应当将其析构方法设定成虚函数(即便他不需要析构函数)。

(3)虚函数一定是成员函数,所以不能把友元函数设定为虚函数,由此产生的设计问题可以通过友元函数调用虚成员方法来实现。

(4)派生类未定义的虚函数将会自动沿用派生链最新版本

(5)重新定义基类虚方法应保证声明完全相同(特殊情况:根据返回类型协变允许将返回值实基类/基类指针/基类引用的改成派生类)。若出现不同,则派生类将完全屏蔽基类同名方法而非重载。

 

四、保护访问控制:protected

1.被设置为protected的成员在派生类中等同于公有,而在外界等同于私有。

 

五、抽象基类

1.当两个对象具备部分共性但却又存在相当的冲突时采用的思想:从中抽象出其共性部分组成一个抽象基类。(然后分别继承该基类)

2.抽象类通过在类中声明纯虚函数创建(至少有一个就是抽象类了),使之不能被实例化并指定派生类需要遵守的“接口约定”。纯虚函数形如virtual void method_name(value)=0;纯虚函数允许定义或不定义方法体。

3.问题:定义了方法体的纯虚函数是否还必须要派生实现。

 

六、继承和动态内存分配

1.情况一:派生类未使用new。此时不需要对派生类的默认方法做任何处理。

2.情况二:派生类使用了new。

    (1)析构函数:派生类自动调用基类析构函数,所以自身析构函数中只需要对新增new内存进行释放即可。

    (2)赋值构造函数:需要显式在成员初始化列表调用基类的复制构造函数对基类部分也进行复制,并在方法体重对新增new内容进行深拷贝。

    (3)赋值运算符:形如

hasDMA & hasDMA::operator=(const hasDMA & rs){

    if(this ==&ra)

        return *this;

    baseDMA::operator=(hs);

    delete [] style;

    style =new char[std::strlen(hs.style)+1];

    std::strcpy(style,hs.style);

    return *this;

}

 

七、其他

1.友元函数当然不会被继承,但针对派生类可以在类外定义调用基类友元函数的函数来实现友元函数的重用,形如

std::ostream & operator<<(std::ostream & os,const hasDMA & hs){

    os<<(const baseDMA &)hs;//调用为基类创建的友元函数

    os<<"style:"<<hs.style<<endl;

    return os;

}

 

 


第十四章 c++的代码重用


has-a关系的实现:由包含对象成员的类和私有继承两种形式(他们都有特殊的构造方式)

一、包含对象成员的类(通常使用该方式)

1.提供算术支持的用作数组的模板类valarray。形如valarray<double> q_values={2.1,3.7,5.1,4.2};拥有索引/max/min/sum/size等方法

2.使用成员名在成员初始化列表中进行初始化,形如Student(const char* str,const double * pd,int n):name(str),scores(pd,n){},且一定先进行继承的初始化。

3.初始化顺序以声明顺序为准而非在初始化列表中的顺序。

4.使用成员对象名调用其成员方法

 

二、私有继承(在需要重写虚函数或访问原有类保护成员时使用)

1.在声明继承中使用private的继承方式<这也是默认的继承方式>,在这种情况下基类的除私有成员外全部成为派生类的私有成员,相当于派生类“包含”了这些内容。

2.构造函数中需要用类名构造私有继承来的成员而不能用成员名,形如Student(const char* str,const double * pd,int n):string(str),ArrayDb(pd,n){}

3.使用基类类名和作用域标识符::来调用基类的方法;通过强制转换到基类对象来访问基类对象

4.使用using提供特别的访问权限:即便包含或私有继承对象,通过在派生类public部分加入形如using valarray<double>::min;这样的声明,可以使之被允许在类外调用。

 

三、保护继承:从基类获得的公有和保护成员全都成为派生类的保护成员

 

四、多重继承:一般情况在基类声明部分用逗号隔开即可

1.多重继承自同源基类时需要配合虚基类机制,否则会有很多问题

情形描述:基类Worker派生了Waiter和Singger,如今用Singingwaiter同时继承自Waiter和Singger

(1)实现方式是在中间类(也即情形中Waiter和Singger)建立时声明基类部分加入关键字virtual表示他们的基类重复时可以共享。

(2)使用virtual是为了减少关键字引入的一种关键字重载

(3)使用了虚基类的派生类型在构造函数中除了自身直接基类外还需要前面的共有基类显示调用构造函数(因为关闭了中间函数的自动传递机制)

(4)调用同源多重继承派生类成员时可以通过作用域解析符(如 sw.Waiter::show();)和类定义函数接口解决调用二义性

2.混合使用虚基类和普通基类也是允许的。其中虚基类共享源基类而普通基类互相独立源基类

3.支配(优先)有差异的方法不会因为多重继承出现二义性

如B(q)->C(q、omg)->D(...)

          ->E(omg)

且D与B派生F,该情形下q在c中优先于B中会默认调用,而omg优先相同出现二义性。

 

五、类模板:提供定义的参数化类型能力

1.定义规则

(1)如函数一样在声明时使用template <class type>;(type是可以自定义的类型占位符/类型    参数)

(2)将type作为类型标识编写类体;

(3)注意在类外进行成员方法定义时形如 void Stack<type>::push(const Type & item);即需要在类限定中加入类型占位符标识模板类。且每个函数定义上面要有一个template声明。

2.使用规则

指定具体类型替换泛型名:形如Stack<string> mystack;

3.模板参数

(1)在template <模板参数列表>中,可逗号隔开复数的参数。

(2)参数除class/typename表示的类型参数外,还可以有整型、枚举、引用、指针类型的表达式参数(但其实不如构造函数初始化成员参数好用)

4.其他功能

(1)模板类可以用作基类也可以用作组件类,模板可以成为结构、类或模板类的成员

(2)模板可以递归使用,形如ArrayTP<ArrayTPM<int,5>,10> twodee;

(3)template <class type=int>;允许像这样为模板参数设置默认值;

(4)模板类的类型参数也可以是模板类形如template <template<typename T> class thing>

5.模板的具体化:

(1)隐式实例化:进行对象定义(因为编译器在需要对象前,不会生成类的隐式实例化)时生成类定义并根据定义创建一个对象。

(2)显式实例化:如template class ArrayTP<string,100>;显式声明,实际进行了类的声明和方法定义。

(3)显式具体化:(为部分类型设置优先于通用类型的特殊同名模板)形如template <> class SortedArray<const char*>{类体;};

(4)部分具体化:形如template <不具体化的参数> class SortedArray<进行具体化的参数>;

同时符合多个定义将自动调用具体化程度最高的。

6.模板类与友元

(1)非模板友元函数:普通的函数做友元,成为模板所有实例化的友元。(可将该函数参数形如Stack<T> &)当模板类多次实例化,则相当于创建了几个重载的常规函数。(函数本身不是模板)

(2)约束模板友元函数:函数本身是模板(且其参数与类一致),形如friend void counts<TT>();根据类实例化的不同创建不同的函数(属于类自己的友元函数)

(3)非约束模板友元函数:函数本身是模板,但其参数与类无关,声明形如template <typename C,typename D>friend void show(C & c,D & d);

7.可变参数模板见第十八章

 

六、模板别名

(1)using pc=const  char *;using用于非模板名时作用等同typedef

(2)对模板形如

template <class T>

using arrtype=std::array<T,12>;则有arrtype<int> arr;等同std::array<int,12> arr;

或者using arrtype=std::array<T>;则有arrtype arr;等同std::array<int> arr;

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值