C++ Primer第五版之(十二)面向对象程序设计

×核心思想是数据抽象、继承和动态绑定。×

静态类型与动态类型

  1. 将基类指针或引用绑定到派生类上,派生类向基类的隐式类型转换,智能指针也支持。
    ①派生类向基类的隐式类型转换,不存在基类向派生类的隐式类型转换。
    ②派生类向基类的自动类型转换只对指针和引用有效,在派生类型和基类型之间不存在这样的转换。

  2. 变量和表达式的静态类型在编绎时是已知的,它是变量声明时的类型或表达式生成的类型,动态类型则是变量或表达式表示的内存中对象的类型,动态类型运行时才可知。
    ①如果表达式既不是引用也不是指针,它的动态类型与静态类型一致。
    ②基类指针或引用的静态类型与动态类型可能不一致。

  3. 编绎器在编绎时只能通过检查指针或引用的静态类型推断转换是否合法。
    ①基类中含有虚函数的可使用dynamic_cast请求类型转换,该转换的安全检查在运行时执行。
    ②如果已知某个基类向派生类的转换是安全的,可使用static_cast强制覆盖编绎器的检查工作。

  4. 当使用派生类对象为基类对象初始化或赋值时,例接受引用参数的基类拷贝和移动操作,派生类中的基类部分会被拷贝、移动或赋值,派生类部分被忽略。

虚函数

  1. 基类的虚函数,在派生类中隐式地也是虚函数。
    ①派生类可重载、可不重载基类虚函数,重载基类虚函数时,在函数形参列表后增加一个override关键字。
    ②C++11新标准中使用override关键字说明派生类中的虚函数。
    ③当派生类重载虚函数时,形参必须与基类中严格匹配。
    ④除构造函数之外的任何非静态函数都可以是虚函数。
    ⑤virtual只能出现在类内部的声明语句之前,不能用于类外部的函数定义。

  2. 引用和指针的动态类型与静态类型不同正是C++语言支持多态性的根本所在。
    ①当使用基类引用或指针调用虚成员函数时执行动态绑定,运行时确定调用绑定到指针或引用上对象动态类型相匹配的版本。
    ②当通过普通类型(非引用非指针)的表达式调用虚函数,在编绎时调用版本已确定。

  3. 虚函数使用默认实参时,实参值由本次调用的静态类型决定。
    ①通过基类指针或引用调用函数,使用基类中定义的默认实参,如实际运行派生类的函数版本,但代入派生类函数的还是基类的默认实参。
    ②基类与派生类默认实参最好一致。

  4. 可使用作用域运算符回避虚函数动态绑定.。
    ①派生类虚函数调用基类版本时,必须使用作用域运算符调用基类版本,否则派生类调用自身,无限递归。
    ②通常情况下,成员函数(或友元)需要作用域运算符回避虚函数,调用在编绎时完成解析。

  5. C++11新标准下防止继承的发生,在类名后跟关键字final。
    ①可将函数指定为final,拒绝后续继承覆盖该函数。

抽象基类

  1. 在函数声明语句后=0声明虚函数为纯虚函数,可在类外部提供纯虚函数的定义。

  2. 含有纯虚函数的类是抽象基类,负责定义接口。
    ①不可创建抽象基类对象,派生类覆盖接口定义。
    ②未覆盖直接继承纯虚函数的派生类也是抽象基类。

访问控制与继承

  1. 类的用户不可访问受保护成员、私有成员。
    ①派生类成员和派生类友元可访问基类公有成员、受保护成员,不可访问基类私有成员。
    ②派生类成员和派生类友元可通过派生类对象访问基类的受保护成员,不可通过基类对象访问受保护成员,不可访问基类私有成员。

  2. 每个类控制自己成员初始化过程,派生类不能直接初始化基类继承的成员。
    ①通过基类的构造函数初始化从基类中继承而来的成员。
    ②如果基类定义了静态成员,在整个继承体系中只存在该成员唯一定义。

  3. 派生访问说明符控制派生类用户,包括派生类的派生类对基类访问权限。
    ①派生类成员和友元对基类成员的访问权限一与基类访问说明符有关,二与派生访问说明符有关。

  4. 编绎器隐式执行派生类到基类的类型转换,派生类向基类转换的可访问性:
    ①当公有继承时,用户代码可使用派生类向基类的转换,如果非公有继承,用户代码不能使用该转换。
    ②派生类的成员和友元永远可访问派生类向基类的类型转换。
    ③当公有或受保护继承,派生类的派生类成员和友元可使用派生类向基类的类型转换,私有继承时不可使用。
    ④即如果基类的公有成员是可访问的,则派生类向基类的类型转换也是可访问的。

友元与继承

  1. 友元关系不能传递不能继承。

  2. 基类友元可通过派生类对象访问基类可访问成员,不可访问派生类成员。

  3. 在类的内部可使用using声明语句,标记直接或间接基类中可访问成员,访问权限由using语句所处的访问说明符决定。
    ①private: using BaseClassName::membername;

  4. struct和class定义的类具有不同的默认访问说明符,默认派生运算符与默认访问说明符相同。

名字查找与继承

  1. 假设调用p->mem()(或obj.mem())
    ①首先确定p(或obj)的静态类型,类型必须是类类型。
    ②在p(或obj)静态类型对应的类中查找mem,如果找不到,依次在直接基类中查找直到继承链顶端,仍然找不到,编绎器报错。
    ③找到mem进行类型检查,确认本次调用合法。
    ④假设调用合法,若mem是虚函数且通过引用和指针进行的调用,编绎器在运行时根据对象的动态类型确定调用哪个版本,如果不是虚函数或是通过对象不是引用或指针进行的调用,则编绎器常规函数调用。
    ⑤名字查找先于类型检查。

  2. 派生类成员与基类成员重名,派生类在其内层作用域隐藏该基类成员,即使派生类成员和基类成员参数列表不一致仍会被隐藏。
    ①可使用基类作用域运算符调用基类成员。
    ②虚函数和成员函数都可以被重载,当基类存在多个重载函数,如果派生类重载其中一个则覆盖所有,如果想所有重载版本都是可见的,要不不重载要不重载所有,此时可使用using声明指定函数名不指定形参列表,可以把基类该函数所有重载版本派加进派生类,派生类再定义特有的函数即可。
    ③using BaseClassName::membername;
    ④访问派生类没有重新定义的重载版本是对using声明点的访问。

  3. 调用非虚函数不会发生动态绑定,实际调用版本由指针的静态类型决定。

动态绑定

  1. 在C++中,当使用基类的引用或指针调用一个虚函数发生动态绑定,调用可能执行基类的版本,也可能执行派生类的版本。

  2. 基类定义,作为继承关系根节点的类通常会定义一个虚析构函数。

虚析构函数

  1. 动态分配的基类指针,指向的派生类对象,当删除该指针时需要虚析构函数,编绎器必须正确的执行对象的析构函数。

  2. 如果类需要析构函数,同样也需要拷贝和赋值操作。
    ①基类析构函数除外。
    ②基类可将析构函数设定为虚函数,函数体内容为空,无法由此推断基类是否需要赋值运算符或拷贝构造函数。

  3. 虚析构函数阻止合成移动操作,当移动对象时实际使用的是拷贝操作。

构造函数与拷贝控制

  1. 基类或派生类的合成拷贝控制成员的行为与其他合成的拷贝控制成员类似。
    ①对类本身成员依次进行初始化、赋值或销毁操作。
    ②合成成员还负责使用直接基类中对应的操作对对象直接基类部分进行初始化、赋值或销毁操作。

  2. 无论基类控制成员是合成版本还是自定义版本,要求相应控制成员是可访问的且不是被删除的。
    ①如果基类中默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的或不可访问的,则派生类中对应的控制成员是被删除的,因编绎器不能使用基类成员执行派生类对象中基类部分的构造、赋值或销毁操作。
    ②如果在基类中有一个不可访问或被删除的析构函数,派生类中合成的默认和拷贝构造函数是被删除的,因编绎器无法销毁派生类对象的基类部分。
    ③基类的析构函数是删除的或不可访问的,编绎器不会合成一个删除的移动操作,派生类自定义的移动构造函数是被删除的。
    ④当使用=default请求一个移动操作时,如果基类中对应操作是删除的或不可访问的,则派生类中该函数是被删除的,因派生类对象的基类部分不可移动。

  3. 当派生类定义了拷贝或移动操作时,该操作需要拷贝或移动包括基类部分成员在内的整个对象。
    ①基类默认构造函数会初始化派生类对象的基类部分,但在需要拷贝或移动基类部分时,必须在派生类的构造函数初始值列表中显式地使用基类的拷贝或移动操作。
    ②派生类的拷贝和移动构造函数在拷贝和移动自有成员同时,需要调用基类拷贝和移动基类部分成员,类似赋值运算符。
    ③派生类的赋值运算符也必须显式地为基类部分赋值。
    ④无论基类的构造函数或赋值运算符是自定义版本或合成版本,派生类的对应操作都能使用它们。
    ⑤析构函数只负责销毁派生类自己分配的资源。

  4. 对象销毁的顺序与创建顺序相反,派生类析构函数首先执行,然后是基类析构函数,沿着继承体系的反方向直至最后。

  5. 如果构造函数或析构函数调用了某个虚函数,则应该执行构造函数或析构函数所属类的虚函数版本。因构造函数和析构函数的执行顺序,当执行基类的构造函数时,派生类部分是未被初始化状态,当执行基类析构函数时,派生类部分已被销毁。因此构造函数或析构函数中调用虚函数时不可交叉,执行当前类虚函数版本即可。

  6. 在C++11新标准中,派生类可重用直接基类定义的其他构造函数,不可重用默认、拷贝和移动构造函数,如果派生类没有定义这些构造函数,编绎器会合成它们。

  7. using声明语句using BaseName::BaseName;
    ①声明基类构造函数在派生类中可见,此声明作用于构造函数时,编绎器会产生代码,在派生类中生成形参列表完全相同的构造函数。
    ②生成形似derived(parms) : base(args){}的构造函数,derived是派生类名字,base是基类名字,parms是构造函数的形参列表,args将派生类构造函数的形参传递给基类构造函数。
    ③此时派生类数据成员默认初始化。
    ④构造函数using声明不论出现在哪,其访问权限依据基类中该构造函数的访问权限,且不能指定explicit和constexpr,与基类拥有相同属性。

  8. 如果基类构造函数含有默认实参,派生类获得多个含有默认实参数的继承构造函数,每个构造函数分别省略一个默认实参的形参。

  9. 与派生类具有相同参数列表的构造函数不会被继承,默认、拷贝和移动构造函数不会被继承。

容器与继承

  1. 在容器中存放具有继承关系的对象时,存放基类的指针(最好为智能指针)而不是对象,指针所指动态类型可能是基类类型,可能是派生类类型。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值