《C++ Primer 第四版》笔记(4)

来源:互联网 发布:幸运28源码出售 编辑:程序博客网 时间:2024/05/16 08:59
第四部分 面向对象编程与泛型编程

 [第15章  面向对象编程]   
    virtual的目的是启用动态绑定,除了构造函数和static成员函数之外,其他成员函数均可以是虚函数。virtual只能出现在类的声明处,不能出现在类外的定义处。
    基类必须是已经定义的类,如果对一个类只进行了声明而没有定义,则这个类不能作为基函数。因为派生类必须知道基函数的成员都有哪些。这个规则暗示着不能从类自身派生出一个类。
    如果要声明一个派生类而不定义它,则声明处只能声明该派生类名,不能包含派生列表。如:
    class Bulk_item:public Item_base; //这个声明是错误的
    //应该如下那样进行声明
class Bulk_item;    
class Item_base;
    动态绑定的实现必须包含两个条件:(1)基类中使用virtual指定为函数;(2)必须使用基类类型的引用或指针进行函数调用。
    虚函数也可以有默认实参,虚函数默认实参的值在编译时确定。如果通过基类的指针或引用调用虚函数,则默认实参的值使用基类中指定的值;如果通过派生类的指针或引用调用虚函数,则默认实参的值使用派生类中指定的值,这种情况往往不是我们想要的。因此,虚函数的默认实参在基类和派生类中最好指定相同的值。
    接口继承:public派生类继承基类的接口,它具有与基类相同的接口。public派生类的对象可以用在任何需要基类对象的地方。最常见的继承形式是public。
    实现继承:使用private或protected的方式继承。
    使用class关键字定义的类,其成员默认是private的,如果派生类列表不指定派生形式,则派生类也默认具有private访问权限。用struct关键字定义的类,成员函数及派生类都是默认具有public访问权限。
    友元关系不能继承。
    如果基类定义了static成员,则整个继承层次中只有一个这样的成员,如果该static成员是非private的,则派生类也可以访问这个成员。访问该static成员,可以使用类作用域运算符或点或箭头形式。
    可以将派生类对象的引用(或指针)转换为基类对象的引用(或指针)。但没有从基类对象的引用(或指针)到派生类对象的引用(或指针)的自动转换。虽然一般可以使用派生类型的对象对基类类型的对象进行初始化或复制,但是没有从派生类对象到基类对象的直接转换。
    当用派生类对象给基类对象复制或赋值时,其实是在调用基类的复制构造函数或重载的赋值操作符(可能是自定义的,也可能是编译器合成的默认版本),这两类函数的形参都是基类对象的引用,因此可以接受派生类对象。在函数内部把派生类对象的基类部分拷贝到被复制或赋值的基类对象中,把派生类对象本身的成员“切掉”舍弃。
    构造函数和复制控制成员不会继承,每个类需要定义自己的构造函数和复制控制成员。
    编译器为派生类合成的复制构造函数、赋值操作符以及析构函数会对基类部分以及派生类自己的部分进行复制、赋值或者析构,对基类部分进行操作时,它们会调用基类的对应函数进行操作。只包含类类型或内置数据成员、不包含指针的类一般可以使用编译器合成的操作,不需要专门定义赋值构造函数、赋值操作符和析构函数。
    如果定义了派生类自己的复制构造函数或者赋值操作符,则一般需要传递参数显式调用基类的复制构造函数或者赋值操作符,否则会调用基类默认的对应函数,这样的操作可能不是我们想要的。析构函数则不一样,编译器会自动调用基类析构函数,派生类不需要显式调用基类的析构函数,派生类析构函数只需要负责清理派生类自己特有的成员就可以了。
    通常将基类的析构函数定义为虚函数,这样利用到了多态性的时候,销毁利用基类的引用或指针指向的派生类对象时,能够正确调用派生类的析构函数。
    不能将构造函数定义为虚函数,因为构造函数是在对象完全构造之前运行的,在构造函数运行时,对象的动态类型还不完整,无法实现多态性。
    在构造函数或析构函数中,最好不要利用多态性调用虚函数,否则很可能得不到我们想要的结果。派生类的构造函数首先会调用基类的构造函数,然后才是构造派生类本身的成员。在调用基类的构造函数过程中,派生类是不完整的,如果在基类的构造函数中调用了虚函数,则无法实现多态性,基类实际上调用的是基类本身对应的那个虚函数。析构函数的过程与此类似。总之,在基类构造函数或派生类构造函数中,会把派生类对象当做基类对象来对待。
    派生类作用域嵌套在基类作用域中。查找名字时,首先在派生类部分中查找,没有查找到再去基类部分查找。
    名字的查找在编译时确定,普通对象、引用以及指针的静态类型决定了对象能够完成的行为。即使静态类型和动态类型不同,比如使用基类的引用或指针指向派生类,静态类型仍然决定着可以使用什么成员。此时,指向派生类的基类指针(或引用)不能调用派生类中定义的函数。
    派生类中定义的与基类中函数同名的函数(即使形参不同)将覆盖基类中的同名函数,因为派生类和基类不在同一个作用域内。如果派生类重定义了基类的重载成员,则通过派生类只能直接访问派生类中重定义的那些成员,不能直接访问基类的其他重载版本。如果派生类想要重载基类中的函数,要么重定义所有重载版本,要么使用using声明基类中的重载成员(一个using声明只能指定一个名字,不能指定函数形参列表,用using声明后将把基类的所有重载版本引入到派生类作用域中),然后派生类再重定义自己需要的特定版本。
    继承层次中确定函数调用的过程如下:
(1)首先确定函数调用的对象、引用或指针的静态类型;
(2)按照本类层次-->直接基类-->间接基类的顺序逐层进行名字查找,寻找调用函数的名字;
(3)一旦找到名字,就进行常规类型检查;
(4)如果调用合法,编译器就生成代码。如果是多态调用,则根据动态类型确定运行的是哪个函数版本,否则生成代码直接调用函数。

 [第16章  模板与泛型编程] 
    模板是泛型编程的基础。模板是创建类或函数对象的蓝图或公式。
    函数模板是建立算法库的基础;类模板是建立标准库容器和迭代器类型的基础。
    模板形参表不能为空。
    函数模板也可以声明为inline,inline关键词应该放在模板形参表之后、函数返回类型之前。
    编写泛型代码有两个重要原则:
(1)模板形参是const引用;
(2)函数体中的比较只用小于(<)比较。
    使用类模板时,必须显式指定模板实参。使用函数模板时,编译器通常会为我们推断模板实参。从函数实参确定模板实参的类型和值的过程叫做模板实参推断(template argument deduction)。当模板实参推断不能唯一推断一个实例时,必须在函数调用时显示指定模板实参,如long val = sum<long, int, long>(i, lng);
    可以使用函数模板对函数指针进行初始化或赋值,编译器会根据指针的类型实例化具有适当模板实参的模板版本。
    对象的模板实参将确定成员函数的模板形参,用模板形参定义的成员函数形参的实参允许进行常规转换。而普通的函数模板则不能进行常规转换。
    类模板的成员函数只有为程序所用时才进行实例化。如果某成员函数从未使用,则不会实例化该成员函数。如果类模板有一个指针成员指向另一个类模板,则只用使用这个指针时才会对另一个类模板进行实例化。
    模板编译模型包括两种:
(1)包含编译模型(inclusion compilation model),通过在声明函数模板或类模板的头文件中添加一条#include语句把模板定义文件包含进来;
(2)分别编译模型(separate compilation model),通过在定义处使用export关键字声明。
    模板可以包含非类型模板形参。非类型模板实参必须是编译时常量表达式,如:Screen<24, 80> hp2621;
    在类模板中可以出现三种友元声明:
(1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数;
(2)类模板或函数模板的友元声明,授予对友元所有实例的访问权;
(3)只授予对类模板或函数模板的特定实例的访问权的友元声明。此种形式的友元声明,要求在本类模板前面声明要添加的类模板或函数模板的声明。
    任意类(模板类或非模板类)的成员可以本身也为类模板或函数模板,这种成员称为成员模板(member template),成员模板不能为虚。在类模板作用域外定义成员模板的时候,应该包含类模板和本身函数模板两个template模板形参表。
    函数模板可以重载:可以定义相同名字但形参数目或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。声明一组既包含模板又包含非模板函数的重载函数集合很容易造成函数匹配过程的错误或二义性。


原创粉丝点击