《C++ Primer中文版》(第四版)信息汇总(五)

编写自己的面向对象类型或泛型类型需要对C++的充分理解,幸运的是,我们可以使用面向对象和泛型类型而无须了解它们的构建细节。本章主要介绍面向对象编程与泛型编程。

15、面向对象编程

面向对象编程基于三个基本概念:数据抽象、继承和动态绑定。在C++中,用类进行数据抽象,用类派生从一个类继承另一个类:派生类继承基类的成员,动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数。继承和动态绑定在两个方面简化了我们的程序:能够容易地定义与其他类似但又不相同的新类,能够更容易地编写忽略这些相似类型之间区别的程序。

1、在C++中,基类必须指出希望派生类重定义哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

2、通过动态绑定我们能够编写程序使用继承层次中任意类型的对象,无须关心对象的具体类型,使用这些类的程序无须区分函数是在基类还是在派生类中定义的。在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定,引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数时引用(或指针)所指对象的实际类型所定义的。

3、保留字virtural的目的是启用动态绑定,成员默认为非虚函数,对非虚函数的调用在编译时确定,为了指明函数为虚函数,在其返回类型前面加上保留字virtual。除了构造函数之外,任意非static成员函数都可以是虚函数。保留字virtual只在类内部的成员函数声明中出现,不能在类定义体外部出现的函数定义上

4、protected成员可以被派生类对象访问但不能被该类型的普通用户访问。

举例:假定Bulk_item定义了一个成员函数,接受一个Bulk_item对象引用和一个Item_base对象的引用,该函数可以访问自己对象的protected成员以及Bulk_item形参的protected成员,但是,它不能访问Item_base形参的protected成员。

5、定义派生类Bulk_item,每个Bulk_item对象包含四个数据成员:从Item_base继承的isbn和price,自己定义的min_qty和discount.

6、派生类一般会重定义所继承的虚函数,如果派生类没有重定义某个虚函数,则使用基类中定义的版本。派生类必须对想要重定义的每个继承成员进行声明。派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有个例外:返回对基类型的引用(或指针)的虚函数,派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针) 

7、如果需要声明(但不实现)一个派生类,则声明包含类名但不包含派生列表。

8、C++中函数电泳默认不适用动态绑定,要触发动态绑定,必须满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。

(1) 从派生类到基类的转换:因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象。

(2) 可以在运行时确定virtual函数的调用:

(3) 覆盖虚函数机制:有时候希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这时可以使用作用域操作符,如果派生类忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。

(4) 虚函数与默认实参:与其他任何函数一样,虚函数也可以有默认实参。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。

9、如果进行private或protected继承,则基类成员的访问级别在派生类中比在基类中更受限:

在这个继承层次中,size在Base中为public,但在Derived中为private。为了使size在Derived中成为public,可以在Derived中的public部分增加一个using声明。如下这样改变Derived的定义,可以使size成员能够被用户访问,并使n能够被Derived派生的类访问。

10、友元关系不能继承:基类的友元对派生类的成员没有特殊访问权限,如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。如果派生类想要将自己成员的访问权授予其基类的友元,派生类必须显式地这样做。

11、继承与静态成员:如果基类定义了static成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个static成员只有一个实例。static成员遵循常规访问控制:如果成员在基类中为private,则派生类不能访问它。假定可以访问成员,则既可以通过基类访问static成员,也可以通过派生类访问static成员。

12、理解基类类型和派生类型之间的转换,对于理解面向对象编程在C++中如何工作非常关键:基类类型对象既可以作为独立对象存在,也可以作为派生类的一部分存在,因此,一个基类对象可能是也可能不是一个派生类对象的部分,结果,没有从基类引用或基类指针到派生类引用或者派生类指针的自动转换。相对于引用或指针而言,对象转换的情况更为复杂。虽然一般可以使用派生类型的对象对基类类型的对象进行初始化或赋值,但没有从派生类型对象到基类类型对象的直接转换

(1) 派生类到基类的转换:如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或者初始化。

(2) 基类到派生类的转换:从基类到派生类的自动转换是不存在的,需要派生类对象时不能使用基类对象。没有从基类类型到派生类型的自动转换,原因在于基类对象只能是基类对象,它不能包含派生类型的成员。

甚至当基类指针或引用实际绑定到派生类对象时,从基类到派生类的转换也存在限制:

13、本身不是派生类的基类,其构造函数和复制控制基本上不受继承影响,继承对基类构造函数的唯一影响是,在确定提供哪些构造函数时,必须考虑一类新客户,像任意其他成员一样,构造函数可以为protected或private,某些类需要只希望派生类使用的特殊构造函数,这样的构造函数应定义为protected.

14、派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。

(1) 合成的派生类默认构造函数,对于Bulk_item类,合成的默认构造函数会这样执行:首先调用Item_base的默认构造函数,然后用常规变量初始化规则初始化Bulk_item的成员。

(2) 定义默认构造函数。

(3) 向基类构造函数传递实参,派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。相反,派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。

(4) 一个类只能初始化自己的直接基类,直接基类就是在派生类别中指定的类。

15、复制控制和继承

(1) 定义派生类复制构造函数:如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:

(2) 派生类赋值操作符:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。赋值操作符必须防止自身赋值。

(3) 派生类析构函数:析构函数的工作与复制构造函数和赋值操作符不同,派生类析构函数不负责撤销基类对象的成员,编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员,对象的撤销顺序与构造顺序相反,首先运行派生类析构函数,然后按继承层次依次向上调用各基类析构函数。

16、虚析构函数:自动调用基类部分的析构函数对于基类的设计有重要影响,如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际上是派生类型的,则没有定义该行为。为了保证运行适当的析构函数,基类中的析构函数必须为虚函数。

如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同。

注意:构造函数和赋值操作符不是虚函数。构造函数不能定义为虚函数。虽然可以在基类中将成员函数operator=定义为虚函数,但这样做并不影响派生类中使用的赋值操作符。将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么好处。

17、与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。当然可以使用作用域操作符访问被屏蔽的基类成员。

18、在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽

19、虚函数与作用域,虚函数在基类和派生类中必须拥有同一原型,如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。如下:

20、C++中一个通用的技术是定义包装类或句柄类。句柄类存储和管理基类指针。指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象,用户通过句柄类访问继承层次的操作。因为句柄类使用指针执行操作,虚成员的行为将在运行时根据句柄实际绑定的对象的类型而变化,因此,句柄的用户可以获得动态行为但无须操心指针的管理。       

16、模板与泛型编程

所谓泛型编程就是以独立于任何特定类型的方式编写代码。模板是泛型编程的基础,使用模板时无需了解模板的定义,本章将介绍怎样定义自己的模板类和模板函数。

1、定义函数模板

(1) 以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或多个模板形参的列表,形参之间以逗号分隔。

(2) 函数模板可以用非模板函数一样的方式声明为inline.说明符放在模板形参表之后、返回类型之前,不能放在关键字template之前

2、定义类模板

(1) 以Queue类为例,先定义它的接口:以关键字template开头,后接模板形参表。

(2) 使用类模板,与调用函数模板形成对比,使用类模板时,必须为模板形参显式指定实参。

(3) 模板形参:像函数形参一样,程序员为模板形参选择的名字没有本质含义。

(4) 模板形参遵循常规名字屏蔽规则,如下所示,tmp不是double型,相反,tmp的类型是绑定到模板形参的任意类型。

(5) 用作模板形参的名字不能在模板内部重用,这一限制意味着模板形参的名字只能在同一模板形参表中使用一次。

(6) 模板声明,声明必须指出函数或类是一个模板。

每个模板类型形参前面必须带上关键字class或typename,每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:

3、typename和class的区别:在函数模板形参中,关键字typename和class具有相同含义,可以交互使用,两个关键字都可以在同一模板形参表中使用:

4、非类型模板形参:模板形参不必都是类型,在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。

5、函数模板的特化:我们并不总是能够写出对所有可能被实例化的实例都最合适的模板,如下面的代码:

如果用两个const char*实参调用这个模板的定义,函数将比较指针值,它将告诉我们这两个指针在内存中的相对位置,但没有说明与指针所指数值的内容相关的任何事情,为了将compare函数应用于字符串,必须提供一个知道怎样比较C风格字符串的特殊定义。这些版本是特化的。

模板特化是这样一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。如下:

特化的声明必须与对应的模板相匹配,现在当调用compare函数的时候,传给它两个字符指针,编译器将调用特化版本。编译器将为任意其他实参类型(包括普通char*)调用泛型版本。

小结

函数模板是建立算法库的基础,类模板是建立标准库容器和迭代器类型的基础。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值