C++primer(第四版)复习笔记—第三篇: 类和数据抽象

数据抽象:是指定义数据和函数成员的能力;
封装:是指从常规访问中保护类成员的能力。
接口:成员函数定义了类的接口。通过将定义类所用到的数据和成员函数设置维为private来封装类。

第十二章: 类

1、 构造函数的初始化式只在构造函数的定义中而不是声明中指出。
2、 使用构造函数的初始化列表与在构造函数体中对类的成员变量进行赋值的区别:本质就在于前者是对变量进行初始化,而后者是对变量进行赋值。
理解:构造函数的执行分为两个阶段:先初始化阶段,再是普通计算阶段。
在初始化阶段构造函数将调用类类型的构造函数(没有初始化列表时调用默认构造函数)对类类型的成员变量进行初始化,在普通计算阶段才执行构造函数体的语句,因此在这时是对成员变量进行赋值,覆盖初始化阶段的初始值。 对内置类型,根据对象定义的位置不同,初始化阶段会不同(对全局对象,会将内置类型初始化为0,而局部变量则不初始化); 另外,对于那些const对象成员、引用成员、没有默认构造函数的类类型(否则会调用其默认构造函数,但其没有)等,则必须提供初始化列表进行显示初始化。
3、 建议构造函数使用初始化列表:一是效率:免去了普通计算阶段的复制过程;二是:对于那些const对象成员、引用成员、没有默认构造函数的类类型(否则会调用其默认构造函数,但其没有)等,则必须提供初始化列表进行显示初始化。
4、 注意:成员初始化的次序与成员定义的次序相同(而不是按在初始化列表中出现的次序进行),所以应尽量按定义的次序来给出初始化列表,并注意用一个成员来初始化其他成员时的先后问题。
5、 一个类只有没有定义任何构造函数时才会自动生成合成的默认构造函数。对类类型调用其默认构造函数进行初始化;而对内置类型,当对象定义在全局时才进行初始化,在局部对象中不进行初始化。
6、 如果类包括内置或复合类型(指针 引用)等成员,则不应依赖于合成的默认构造函数,应该定义自己的构造函数来初始化这些变量。
7、 类通常应该定义一个默认构造函数,并且在默认构造函数中给成员提供的初始应该指出对象时“空”的。
8、 为所有形参提供默认实参的构造函数也定义了默认构造函数。
9、 默认构造函数的使用误区:使用类A的默认构造函数定义对象:
A a();//这是错误的,因为这会被编译器解释为定义了一个参数为空,返回一个对象A的函数。因此用默认构造函数时不能在后面带括号(带有参数的非默认构造函数则可以带括号,在括号里给出实参值)。但这种用法是对的:A a=A();//右边调用默认构造函数创建对象,并用该对象初始化对象a.
10 、隐式类类型转换:用单个实参来调用的构造函数定义了从参数类型到类类型的以个隐式转换。如此,可在需要一个类类型的地方传递一个该参数类型,从而编译器会调用该构造函数并以该参数为实参构造一个所需的类类型的临时对象。 但注意:这种隐式转换是否为我们所需要的!否则应该避免。
11、 抑制由构造函数定义的隐式转换: 将构造函数声明为explict来防止构造函数被用作隐式类型转换之用: 在构造函数声明前加上关键字explicit(在类外的构造函数定义体上不能再加explicit)。 此时编译器不不再使用构造函数作为类型转换(编译器会报错)。
12、 为转换而显示的使用构造函数: 任何构造函数(包括默认)都可以显示的创建临时对象,即类名后面直接带括号,括号里面给出要调用的构造函数的参数,而不给出对象名。(如:A();//创建一个无名的临时对象)。
13、 通常,除非用明确的理由需要隐式转换,否则单参数的构造函数前都应该加上explict以避免错误。 当需要类型转换时可用显示使用构造函数来显示的创建临时对象。
13、 类成员的显示初始化:当没有定义构造函数,且所有数据成员都为共有时,可按初始化数组元素的方式来初始化所有数据成员
如: struct A { int i; int* p};
A a={0,0};//a.i=0; a.p=o
; (根据数据成员的声明次序进行初始化)
14、 友元机制允许一个类将其非共有成员的访问权授予指定的函数或类。 Friend只能出现在类内部。 友元声明可以在类中的任意位置:友元不是该类的成员,u因此它们不受其声明出现部分的访问控制影响。
15、 Static类成员:是类的组成部分,而不是某个对象的组成部分(为类的全体对象共有,因此可用于在类的全体对象间传递信息,如记录创建了多少个该类的对象)。Static的成员,只用在类中声明时指定static,在内外定义时不用重复指定为static。
16、 static数据成员:存在于类类型的每个对象中,且独立于该类的某个特定对象而存在,与该类关联,而非该类的某个对象。 Static数据成员必须在类外部定义(正好一次),因为其不是通过类的构造函数来初始化的,而是在定义时进行初始化。 常在类的非内联函数的定义文件中定义static数据成员(不用再重复指定为static),定义时与定义一般成员函数一样,需要在类型后,变量明前指定完全限定名,以指定属于哪个类,如:
int A::static_member=10;(注,只要出现全完限定名,其后面的内容就是在类作用域中。) 特殊的:对cosnt static int 数据成员,只要其初始化式是一个常量表达式,就可以直接在类中进行初始化,(但其他类型的cosnt static数据成员,还是需要在类外部进行定义初始化):

class A{
private:  
static const int a =30; //直接在类定义体中进行用常量表达式初始化
static const string str;//不能在类中进行初始化。
};

Static函数成员:没有this参数,可以直接访问所属类的静态成员,但不能直接使用非静态成员。
17、 static成员遵循正常的访问权限。 Static成员函数不能声明为const函数(因为const成员函数是为不修改其对象),其不能被声明为虚函数。Static数据成员可定义为任意数据类型。
18、 static成员可以像其他普通成员那样被对象引用,也可以直接使用作用域操作符直接以类调用:A::static_member.。 (static成员非对象所有,因此可以独立于对象而被使用)。 如:Static数据成员的类型可以是其类类型。

第十三章: 复制控制

1、 构造函数 和 复制控制成员(复制构造函数、赋值操作符函数、析构函数)不能被继承,但在派生类中能调用基类的这些函数,且不能定义为虚函数(除析构函数外),(赋值操作符函数:因为不能不被继承,所以派生类一定有自己的版本,因此定义为虚函数无意义;构造函数:在对象构造之前运行,此时对象的动态类型还不完整,因此定义为虚函数无意义)。每个类定义自己的构造函数和复制控制成员,如果不定义就使用合成版本。 派生类中如果自己定义了构造函数或复制控制成员,若没有显示调用直接基类的构造函数或复制控制成员,则会使用基类的默认版本来构造或复制基类部分(派生类的构造函数可在初始化列表中显示指出,而赋值操作符可在函数体中显示调用基类版base::operator=(rhs)本来完成基类部分)。 注意:赋值操作符中需要检查是否为自我赋值:

Drivied& Drivied::operator=(Derivied& rhs)
{
    if(*this!=rhs){
     base::operator=(rhs);  //赋值基类部分
    //继续复制派生类自己定义的成员
    }
    Return *this;
}

2、 只包含类类型、内置类型类可以不用定义自己的构造函数和复制控制成员以及析构函数;如果有指针成员则不能再依赖于合成版本。
3、 动态绑定只会发生在基类的引用或指针(可绑定到基类或派生类对象)在调用虚函数时。 可用派生类来给基类赋值或构造基类(基类的赋值构造函数),因为复制构造函数和赋值操作符的形参为基类的const引用,因此可绑定到派生类(该过程派生类对象发生假切割,即丢弃了派生自定义成员部分)。注意:虽然基类指针或引用可绑定到派生类对象(实际是其中的基类部分),还是只能访问派生类对象中的基类部分
4、 派生类析构函数不负责撤销基类对象的成员。编译器总是显示调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。对象的撤销顺序与构造相反:先运行派生类的析构函数,然后依次向上调用各基类的析构函数。
5、 虚析构函数:析构函数可定义为虚函数,且虚性质可被继承(但析构函数本身不能被继承)。当动态析构一个基类指针时,根据指针的动态类型来自动选择调用基类的析构函数还是派生类析构函数(因为析构函数声明为虚函数)。 因此就算基类的析构函数什么也不需要做,也需要定义一个虚析构函数。
6、 在构造与析构派生类对象期间,派生类对象的类型是变化的:在构造时,首先构造其基类部分,此时为基类类型;在析构时先析构其派生类部分,在调用基类析构函数时只剩下基类部分,因此为基类类型。
7、 重载、覆盖与隐藏
1).重载:成员函数具有以下的特征时发生”重载”
A.相同的范围(同一个类中)
B.函数的名字相同
C.参数类型不同(不能进行隐式类型转换)
D.Virtual关键字可有可无
2).覆盖(也叫”继承”):指派生类函数覆盖基类函数,特征是:
A.不同的范围(分别位于基类与派生类中)
B.函数名字相同
C.参数相同
D.基类函数必须有virtual关键字
3).隐藏:是指派生类的函数屏蔽了与其同名(参数列表,返回类型无关)的基类函数,规则如下:
A.如果派生类的函数与基类的函数同名,但是参数不同,此时不论有无virtual关键字,基类的函数都将被隐藏,注意别与重载混淆)
B.如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时基类的函数被隐藏
被隐藏的基类成员,派生类对象可通过基类名加作用域符来显示说明该处要调用基类的该成员。 在派生类中被隐藏的基类版本成员是存在的,只是被隐藏,不能通过派生类对象调用。
所以,隐藏(不同作用域)规则的底层原因其实是C++的名字解析过程(即先在派生类域中查找该名字的成员,一旦找到就停止,如果没有才会继续到基类域中查找。 重载必须在
同一作用域中,查找时是查找最优匹配)
覆盖规则造成的调用现象,其实就是类的虚函数实现原理生成的
8、 含有或继承有一个或多个纯虚函数的类是抽象基类。 抽象基类除了可作为其派生类对象的抽象基类组成部分外,不能创建抽象基类的对象。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。