CPP Primer 4th

 

 

 

 

CPP Primer 4th - chapter 13 复制控制

摘要

复制构造函数、赋值操作符和析构函数总称为复制控制。有一种特别常见的情况需要类定义自己的复制控制成员:类具有指针成员。

如果没有定义复制构造函数,编译器就会为我们合成一个。与合成的默认构造函数不同,即使我们定义了其他构造函数,也会合成复制构造函数。合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。为了防止复制,类不惜显式声明其复制构造函数为private。如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。然而类的友元和成员仍可以进行复制,如果想要连友元和成员中的复制也禁止,就可以声明一个private复制构造函数但不对其定义。

容器中的元素总是按逆序撤销:首先撤销size()-1的元素,然后size()-2的元素直到最后撤销下标为0的元素。

如果类需要析构函数,则它也需要赋值操作符和赋值构造函数 – 三法则。

 

 

Notes

复制构造函数、赋值操作符和析构函数总称为复制控制。有一种特别常见的情况需要类定义自己的复制控制成员:类具有指针成员。

 

复制构造函数具有单个形参,该形参是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数;当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用复制构造函数。

 

根据另一个同类型的对象显式或隐式初始化一个对象,复制一个对象,将它作为实参传递给一个函数,从函数返回时复制一个对象,初始化顺序容器中的元素,根据元素初始化式列表初始化数组元素。

 

除非想使用容器元素的默认初始值,更有效的办法是,分配一个空容器并将已知元素的值加入容器。

 

如果没有定义复制构造函数,编译器就会为我们合成一个。与合成的默认构造函数不同,即使我们定义了其他构造函数,也会合成复制构造函数。合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。为了防止复制,类不惜显式声明其复制构造函数为private。如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。然而类的友元和成员仍可以进行复制,如果想要连友元和成员中的复制也禁止,就可以声明一个private复制构造函数但不对其定义。

 

一般而言,最好显式或隐式定义复制构造函数和默认构造函数。只有不存在其他构造函数时才合成默认构造函数,如果定义了复制构造函数,必须定义默认构造函数。复制构造函数和赋值操作符应该视为一个单元,同时存在。

 

大多数操作符可以定义为成员函数或非成员函数。当操作符为成员函数时,它的第一个操作数隐式绑定到this指针,赋值操作符必须定义为自己的类的成员。合成的复制操作符与合成复制构造函数的操作类似,它会进行逐个成员赋值。

 

当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。

 

容器中的元素总是按逆序撤销:首先撤销size()-1的元素,然后size()-2的元素直到最后撤销下标为0的元素。

 

如果类需要析构函数,则它也需要赋值操作符和赋值构造函数 – 三法则。

合成析构函数按对象创建时的逆序撤销每个非static成员。因此,它按成员在类中声明次序的逆序撤销成员。对于类类型的每个成员,合成析构函数调用该成员的析构函数来撤销对象。

撤销内置类型成员或复合类型的成员没有什么影响。尤其是,合成析构函数并不删除指针成员所指向的对象。

 

析构函数不带任何形参,因此不能进行充值。

 

即使对象赋值给自己,赋值操作符的正确工作也非常重要。

 

赋值操作符通常要做赋值构造函数和析构函数也要完成的工作,通用工作应放在private使用函数中。

2012.02.03

 

 

 

CPP Primer 4th - chapter 14 重载操作符与转换

摘要:赋值=,下表[],调用(),和成员访问箭头->等操作符必须定义为成员,和赋值操作符一样,复合赋值操作符通常定义为类的成员,自增、自减和解引用,通常定义为类成员,对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数,重载逗号、取地址、逻辑与和逻辑或等操作符不是好做法;函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由参数的数目和类型加以区分。定义了调用操作符的类,其对象称为函数对象;转换操作符是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换,转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型。Operator type();

  

重载操作符必须具有至少一个类类型或枚举类型的操作数,这样可以避免重新定义用于内置类型对象的操作符的含义。

重载操作符的优先级、结合性或操作数数目不能改变,重载的操作符不再具备短路求值特性。

重载逗号、取地址、逻辑与和逻辑或等操作符不是好做法。这些操作符具有有用的内置含义,如果定义了自己的版本,就不能再使用这些内置含义。

赋值=,下表[],调用(),和成员访问箭头->等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

和赋值操作符一样,复合赋值操作符通常定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义为非成员复合赋值操作符,不会出现编译错误。

改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常定义为类成员。

对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数

为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对ostream形参的引用。

输入输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。

为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。

如果定义了==,也应该定义!=,定义类相等操作符的类一般也具有关系操作符。

下标操作符必须定义为类成员函数。当类定义下标操作符时,一般需要定义两个版本:一个为const成员并返回引用,另一个为const成员并返回const引用。

箭头操作符必须定义为类成员函数,解引用操作符不要求定义为成员,但将它作为成员一般也是正确的。和下标操作符一样,我们需要解引用操作符的const和非const版本。重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。

函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由参数的数目和类型加以区分。定义了调用操作符的类,其对象称为函数对象

绑定器(brinder)通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象;求反器将谓词函数对象的真值取反。

转换操作符是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换,转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型。

Operator type();

这里的type表示内置类型名,类类型名或由类型别名所定义的名字。对任何可作为函数返回类型的类型(void除外)都可以定义转换函数,不运行定义转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用时可以的。

只能应用一个类类型转换,类类型转换之后不能再跟另一个类类型转换。

一般而言,给出一个类的两种内置类型的转换不是很好的选择。

避免二义性最好的方法是,保证只有一种途径从一种类型转换为另一种类型,可以限制转换操作符的数目,尤其是到内置类型的转换只能有一个。不要定义没有明显映射关系的类型之间的转换。

如果重载集中的两个函数可以用同一转换函数匹配,则使用转换之前或之后的标准转换序列的等级来选择最佳匹配;如果重载集中两个函数可以使用不同转换操作,则认为这两个转换是同样好的匹配,不管可能需要或不需要标准转换的等级如何。

重载操作符就是重载函数,使用重载函数确定相同的步骤来匹配。一般而言,重载函数调用的候选集只包括成员函数非成员函数,不会两者同时包括;而确定操作符使用时,操作符的成员版本非成员版本可能都是候选者。

 

Notes

重载操作符必须具有至少一个类类型或枚举类型的操作数,这样可以避免重新定义用于内置类型对象的操作符的含义。

重载操作符的优先级、结合性或操作数数目不能改变,重载的操作符不再具备短路求值特性。

大多数重载操作符可以定义为普通非成员函数或类的成员函数,作为类成员的重载函数,其形参看起来比操作数数目少一个。作为成员函数的操作符有一个隐含的this形参,限定为第一个操作数。一般将算数和关系操作符定义为非成员函数,而将复制操作符定义为成员。

 

操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元。重载输入输出操作符必须定义为类的非成员函数,因为输入输出操作符第一个操作数必须为输入输出流类型,从而保证和标准输入输出采用相同的使用方式。

 

赋值操作符、取址操作符和逗号操作符对类类型操作数有默认含义,如果没有特殊重载版本,编译器就自己定义以下这些操作符:

合成赋值操作符

赋值操作符和逗号操作符在类类型对象上执行,与在内置类型对象上的执行一样。

内置逻辑与和逻辑或操作符使用短路求值,如果重新定义该操作符,将失去操作符的短路求值特征。

重载逗号、取地址、逻辑与和逻辑或等操作符不是好做法。这些操作符具有有用的内置含义,如果定义了自己的版本,就不能再使用这些内置含义。

 

当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常也比用操作符更好。如果不是普通操作,没有必要为简洁而使用操作符。

 

赋值=,下表[],调用(),和成员访问箭头->等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

和赋值操作符一样,复合赋值操作符通常定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义为非成员复合赋值操作符,不会出现编译错误。

改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常定义为类成员。

对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

 

为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对ostream形参的引用。

 

一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符。

 

输入输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。如果可能,要确定错误恢复措施,这很重要。

 

为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。既定义了算术操作符又定义了相关复合赋值操作符的类,一般应使用复合赋值实现算术操作符。

 

定义了operator==的类更容易与标准库一起使用。有些算法,如find,默认使用==操作符,如果类定义了==,则这些算法可以无须任何特殊处理而使用该类类型。如果类具有一个操作,能确定该类型的两个对象是否相等,通常将该函数定义为operator==,而不是创建命名函数。如果定义了==,也应该定义!=。

定义类相等操作符的类一般也具有关系操作符,关联容器以及某些算法,使用默认<操作符,一般而言,关系操作符诸如相等操作符,应定义为非成员函数。

 

赋值操作符可以重载,无论形参为何种类型,赋值操作符必须定义为成员函数,这一点与复合赋值操作符有所不同。一般而言,赋值操作符与复合赋值操作符应返回左操作数的引用。

 

下标操作符必须定义为类成员函数。当类定义下标操作符时,一般需要定义两个版本:一个为const成员并返回引用,另一个为const成员并返回const引用。

 

箭头操作符必须定义为类成员函数,解引用操作符不要求定义为成员,但将它作为成员一般也是正确的。和下标操作符一样,我们需要解引用操作符的const和非const版本。重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。

 

不要求自增操作符或自减操作符一定为作为类的成员,但是,因为这些操作符改变操作对象的状态,所以更倾向于将它们定义为成员函数。为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用,后缀式操作符应返回旧值,并且应作为值返回,而不是返回引用。一般而言,前缀式和后缀式应该同时定义。

 

函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由参数的数目和类型加以区分。定义了调用操作符的类,其对象称为函数对象

 

函数对象通常用作通用算法的实参,比函数更灵活。标准库定义了一组算数、关系和逻辑函数对象类,还定义了一组函数适配器,使我们能够特化或者扩展标准库所定义的以及自己定义的函数对象类。这些标准库函数对象类型定义在functional头文件中。

 

每个标准库函数对象类表示一个操作符,不同的函数对象定义了执行不同操作的调用操作符。有两个一元函数对象类:一元减和逻辑非。为二元操作符符定义的调用操作符需要给定两给定类型的形参,为一元函数对象类型定义的操作符需要给定一个给定类型的形参。没给函数对象类又是一个类模板。

 

绑定器(brinder)通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象;求反器将谓词函数对象的真值取反。

 

转换操作符是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换,转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型。

Operator type();

这里的type表示内置类型名,类类型名或由类型别名所定义的名字。对任何可作为函数返回类型的类型(void除外)都可以定义转换函数,不运行定义转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用时可以的。

 

转换函数必须为成员函数,不能指定返回类型,并且形参列表必须为空。转换函数一般不应该改变被转换的对象,因此,转换操作符通常应定义为const成员。

 

只要存在转换,编译器将在可以使用内置转换的地方自动调用它。使用转换函数时,被转换类型不必与所需要类型完全匹配。只能应用一个类类型转换,类类型转换之后不能再跟另一个类类型转换。

 

如果小心使用,类型中转换可以大大简化类代码和用户代码。如果使用太过自由,可能产生较为晦涩的编译器错误。一般而言,给出一个类的两种内置类型的转换不是很好的选择。

 

如果两个转换都可以用在一个调用中,而且转换之后存在标准转换,则根据标准转换的类别选择最佳匹配;如果两个构造函数定义的转换都可以使用时,如果存在构造函数实参所需的标准转换,就用该标准转换的类别来选择最佳匹配;避免二义性最好的方法就是避免别写相互提供隐式转换的成对的类。

 

转换操作符的适当使用可以大大简化类设计者的工作并使类变得简单,但有两个潜在的风险:定义太多的转换操作符可能导致二义性,一些转换可能利大于弊。避免二义性最好的方法是,保证只有一种途径从一种类型转换为另一种类型,可以限制转换操作符的数目,尤其是到内置类型的转换只能有一个。不要定义没有明显映射关系的类型之间的转换。

 

重载确定的步骤:

确定候选函数集合:与被调用函数同名的函数

选择可行函数:形参数目和类型与函数调用中实参数目和类型相匹配的候选函数。

选择最佳匹配的函数

如果重载集中的两个函数可以用同一转换函数匹配,则使用转换之前或之后的标准转换序列的等级来选择最佳匹配;如果重载集中两个函数可以使用不同转换操作,则认为这两个转换是同样好的匹配,不管可能需要或不需要标准转换的等级如何。

 

可以通过显示转换和显示构造函数来消除重载确定的二义性。如果在调用重载函数时需要使用构造函数或强制类型转换来转换实参,时拙劣的设计。

 

重载操作符就是重载函数,使用重载函数确定相同的步骤来匹配。一般而言,重载函数调用的候选集只包括成员函数非成员函数,不会两者同时包括;而确定操作符使用时,操作符的成员版本非成员版本可能都是候选者。

 

既为算数类型提供转换函数,同时又为同一类类型提供重载操作符,可能导致重载操作符和内置操作符之间的二义性。

2012.02.02

 

 

CPP Primer 4th - chapter 12 类

摘要:

const必须同时出现在声明和定义中,如果只出现在其中一处,就会出现一个编译时错误。

在类内部定义的成员函数将自动作为inline处理,也可以显式将成员函数声明为inline。

当我们需要将一个对象作为整体引用而不是引用对象的一个成员时,最常见的情况是在函数返回对调用该函数的对象的引用。

基于成员函数是否为const,可以重载一个成员函数。

Const成员函数无法改变其成员变量的,但如果这个成员变量时mutable的,则可以修改。

必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。

只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。

具有类类型的成员通过运行各自的默认构造函数来进行初始化,内置和复合类型的成员,如指针和数组,值对定义在全局作用域中的对象才初始化,当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。

可以用单个实参类调用的构造函数定义了从形参类型到该类型的一个隐式转换。当构造函数被声明为explicit时,编译器将不使用它作为转换操作符。Explicit关键字只能用于类内部的构造函数声明上,在类定义体外部所做的定义上不在重复它。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显式构造对象。

Static数据成员时与类关联,不是与该类的对象关联。Static成员遵循类的访问控制级别。可以通过作用域操作符从类直接调用static成员,或者通过对象、引用或指针间接调用。

 

Notes

类类型常被称为抽象数据类型,抽象数据类型将数据和作用于数据的操作视为一个单元类,就是定义了一个新的类型和一个新的作用域。

 

关键字const置于形参之后,可以将成员函数声明为常量函数,const成员不能改变其操作的对象的数据成员。const必须同时出现在声明和定义中,如果只出现在其中一处,就会出现一个编译时错误。

 

类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程和设计技术,封装是一种将低层次的元素组合起来形成新的、高层次实体的技术。

 

Struct定义的类默认访问级别是public,class定义的类默认访问级别是private的。

 

并非所有类型都必须是抽象的,标准库中的pair类就是一个实用的、设计良好的具体类而不是抽象类。

 

在类内部定义的成员函数将自动作为inline处理,也可以显式将成员函数声明为inline。在声明和定义处指定inline都是合法的,像其它inline一样,inline成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中。

 

可以声明一个类而不定义它,称为前向声明,在声明之后和定义之前的类为不完全类。不完全类型只能以有限方式使用,不能定义该类型的对象,不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员,类的数据成员可以是指向自身的指针或引用。

 

定义对象时,将为其分配存储空间;但定义类型时不进行存储分配。类定义以分号结束,分号是必须的。将对象定义为类定义的一部分是一个坏主意,让人迷惑不解。

 

当我们需要将一个对象作为整体引用而不是引用对象的一个成员时,最常见的情况是在函数返回对调用该函数的对象的引用。

 

普通的非const成员函数中,this的类型是一个指向类类型的const指针,可以改变this所指向的值,但不能改变this所保存的地址;在const成员函数中,this类型是一个指向const类类型对象的const指针,既不能改变this所指向的对象,也不能改变this所保存的地址。不能从const成员函数返回指向类对象的普通引用,const成员函数只能返回*this作为一个const引用。

 

基于成员函数是否为const,可以重载一个成员函数。

 

可变数据成员永远不能为const,甚至当它是const对象的成员也是如此,const成员函数可以改变mutable成员。Const成员函数无法改变其成员变量的,但如果这个成员变量时mutable的,则可以修改。

 

类成员定义中的名字查找,首先检查成员函数局部作用域中的声明,如果找不到,则检查所有类成员的声明,如果在类中也找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明。

 

Const构造函数是不必要的。

 

有些成员必须在构造函数初始化列表中进行初始化。没有默认构造函数的类类型的成员,以及const或引用类型的成员,都必须在构造函数初始化列表中进行初始化。必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。按照与成员声明一致的次序编写构造函数初始化列表是个好主意,此外尽可能避免使用成员来初始化其他成员。

 

只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。如果类包含内置或复合类型的成员,则该类不应该依赖于合成的默认构造函数,它应该定义自己的构造函数来初始化这些成员。

 

具有类类型的成员通过运行各自的默认构造函数来进行初始化,内置和复合类型的成员,如指针和数组,值对定义在全局作用域中的对象才初始化,当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。

 

如果定义了其他构造函数,则提供一个构造函数几乎总是对的。通常,在默认构造函数中给成员提供的初始值应该指出该对象是空的。

 

可以用单个实参类调用的构造函数定义了从形参类型到该类型的一个隐式转换。当构造函数被声明为explicit时,编译器将不使用它作为转换操作符。Explicit关键字只能用于类内部的构造函数声明上,在类定义体外部所做的定义上不在重复它。

 

通常,除非有明显的理由向要定义隐式转换,否则,但形参构造函数应该为explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显式构造对象。

 

定义和使用构造函数几乎总是较好的。当我们为自己定义的类型提供一个默认构造函数时,允许编译器自动运行那个构造函数,以保证每个类对象在初次使用之前正确初始化。

 

友元机制允许一个类对其非公有成员访问权授予指定的函数或类。友元的声明以关键字friend开始,它只能出现在类定义的内部,友元声明可以出现在类的任何地方,通常放在类定义的开始或结束是个好主意。

 

友元声明将已命名的类或非成员函数引入到外围作用域中,此外友元函数可以在类的内部定义,该函数的作用域扩展到包围该类定义的作用域。类必须将重载函数集中每一个希望成为友元的函数都声明为友元。

 

Static数据成员时与类关联,不是与该类的对象关联。Static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。Static成员可以是私有成员,全局对象不可以。Static成员遵循类的访问控制级别。可以通过作用域操作符从类直接调用static成员,或者通过对象、引用或指针间接调用。和其他成员一样,类成员函数可以不用作用域操作符来引用类的static成员。

 

Static成员不是任何对象的组成部分,所以static成员函数不能被声明为const,而且static成员函数也不能被声明为虚函数。

 

Static数据成员必须在类定义体外部定义,不是通过类构造函数进行初始化,而是应该在定义时进行初始化。一个例外是只要初始化式是一个常量表达式,整型const static就可以在的定义体中进行初始化。Const static数据成员在类的定义体内初始化时,该数据成员仍必须在类的定义体之外进行定义。

保证对象正好定义一次的最好办法,就是讲static数据成员的定义放在包含类的非内联函数定义的文件中。

 

像使用任意的类成员一样,在类定义体外部引用类的static成员时,必须知道你给成员是在哪个类中定义的。然而static关键字只能用于类定义体内部的声明中,定义不能标示为static。

 2012.01.30

  

CPP Primer 4th - chapter 15 面向对象编程

摘要:面向对象基于三个基本概念:数据抽象,继承和动态绑定。虚函数是基类希望派生类重新定义的,通过基类的引用和指针调用虚函数时发生动态绑定。公有继承时“Is-A”的关系;private和protected派生类通常被称为实现类,派生类在实现中使用被继承类,但继承基类的部分并未成为其接口的一部分。

友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限,如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

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

将派生类对象传给希望接受引用的函数时,引用直接绑定到该对象,看起来像是在传递对象,实际上实参是该对象的引用,对象未被复制,并且不会在任何方面改变派生类对象;将派生类对象传递给希望接受基类类型对象的函数时,形参的类型是固定的,在编译和运行时都是基类类型对象,派生类对象的基类部分被复制到形参。

构造函数初始化列表为类的基类和成员提供初始值,并不能指定初始化的执行顺序。首先初始化基类,然后根据声明次序初始化派生类的成员。一个类只能初始化自己的直接基类。如果派生类显式定义自己的复制构造或赋值操作符,则该定义将完全覆盖默认定义。被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复制或赋值。

如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽。局部作用域中声明的函数不会重载全局作用域中定义的函数,同样派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类中没有定义该函数时,才考虑使用基类函数。如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。如果派生类相通过自身的类型使用所有的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重新定义。有时类需要仅仅重定义一个重载集中某些版本的行为,并且想要继承其他版本的含义,派生类可以使用using声明,在重定义必须定义的那些函数即可。

含有或继承一个或多个纯虚函数的类是抽象基类,除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象

 

 

Notes

面向对象:数据抽象,继承和动态绑定。

 

定义为virtual的函数是基类希望派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数

 

通过基类的引用和指针调用虚函数时发生动态绑定。

 

一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用Virtual保留字,但不是必须这样做。

 

动态绑定的两个条件:虚拟函数 + 基类指针或引用调用。

 

指针和引用的静态类型和动态类型可以不同,这是C++支持多态性的基石。如果调用虚函数,则知道运行时才能确定调用哪个函数;对于非虚函数,无论实际对象是什么类型,都执行基类类型所定义的函数。对象的动态类型和静态类型相同,运行的函数(虚函数、非虚函数)是由对象的类型定义的。非虚函数总是在编译时确定。

 

只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。

 

派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类忽略了这么做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。

 

通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。

 

无论派生类列表中是什么访问标号,所有继承Base的类对Base的成员具有相同的访问。派生类访问标号将控制派生类的用户从Base继承而来的成员的访问。

 

公有继承时“Is A”的关系。设计良好的类层次中,Public派生类的对象可以用在任何需要积累的地方;private和protected派生类通常被称为实现类。派生类在实现中使用被继承类但继承基类的部分并未称为其接口的一部分。

 

派生类可以恢复继承成员的访问级别,但不能使访问级别比基类原来指定的更严格或更宽松。

 

默认保护级别:class为私有继承,struct为公有继承。

 

友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限,如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。

 

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

 

如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针和引用进行赋值和初始化,对象则没有类似转换,编译器不会将派生类型对象转换为基类类型对象。但一般可以使用派生类对象对基类对象进行复制或初始化。

 

将派生类对象传给希望接受引用的函数时,引用直接绑定到该对象,看起来像是在传递对象,实际上实参是该对象的引用,对象未被复制,并且不会在任何方面改变派生类对象;将派生类对象传递给希望接受基类类型对象的函数时,形参的类型是固定的,在编译和运行时都是基类类型对象,派生类对象的基类部分被复制到形参。

 

用派生类对象对基类对象进行初始化或复制,实际上是在调用函数:初始化时调用构造函数,赋值时调用复制操作符。

 

???要确定到基类的转换是否可访问,可以考虑基类public成员是否可访问,如果可以,转换是可访问的,否则,转换是不可访问的。

如果public继承,用户代码和后代类都可以使用派生类到基类的转换;如果是private和protected继承派生的,用户代码不能将派生类型对象转换为基类对象;如果是private继承类派生的类不能转换为基类,如果是protected继承,则后续派生类的成员可以转换为基类类型。

 

从基类到派生类的转换是不存在的,需要派生类对象时不能使用基类对象。甚至当基类指针或引用实际绑定到派生类对象时从基类到派生类的转换也存在限制。如果知道从基类到派生类的转换是安全的,可以使用static_cast强制编译器进行转换。或者可以用dynamic_cast申请在运行时进行检查。

 

派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。

构造函数初始化列表为类的基类和成员提供初始值,并不能指定初始化的执行顺序。首先初始化基类,然后根据声明次序初始化派生类的成员。

 

一个类只能初始化自己的直接基类。派生类应通过使用基类构造函数来初始化基类成员,而不是在派生类构造函数函数体中对这些成员赋值。

 

合成操作对对象的基类部分连同派生部分的成员一起进行复制、赋值或销毁,使用基类的复制构造函数、复制操作符或析构函数对基类部分进行复制、赋值或销毁。

 

如果派生类显式定义自己的复制构造或赋值操作符,则该定义将完全覆盖默认定义。被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复制或赋值。

 

对象的撤销顺序与构造函数顺序相反:首先运行派生类析构函数,然后按照继承层次依次向上调用各基类析构函数。

 

即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。将类的赋值操作设为虚函数可能会令人混淆,而且不会有什么用处。

 

如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。

 

与基类成员同名的派生类成员将屏蔽对基类成员的直接访问,可以使用域操作符访问被屏蔽成员,设计派生类时,只要可能,最好避免与基类成员的名字冲突。

 

在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽。

局部作用域中声明的函数不会重载全局作用域中定义的函数,同样派生类中定义的函数也不重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类中没有定义该函数时,才考虑使用基类函数。

 

如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。如果派生类相通过自身的类型使用所有的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重新定义。有时类需要仅仅重定义一个重载集中某些版本的行为,并且想要继承其他版本的含义,派生类可以使用using声明,在重定义必须定义的那些函数即可。

 

确定进行函数调用的对象、引用或者指针的静态类型;在该类中查找,如果没有,查找其基类,如果还没有,继续沿继承提醒向上层查找,如果找不到,则调用错误;如果找到了,就进行常规类型检查,看调用是否合法;假设调用合法,编译器就生成代码,如果函数时虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

 

含有或继承一个或多个纯虚函数的类是抽象基类,除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。

 

因为派生类对象在赋值给基类对象时被“切掉”,所以容器与通过继承相关的类型不能很好地融合,可以使用容器保存对象的指针,但面临这管理对象和指针的问题。

 2012.01.16

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值