OPP面向对象程序设计
- 数据抽象:类的接口声明和定义实现分离
- 继承:类构成的(树型)层次关系
- 动态绑定:忽略相似类型区别,用统一的方式使用
基类派生类:
- 继承:类名 + 冒号 + 访问说明符 + 基类列表(多重继承)
- 基类可以有普通的成员(派生类直接继承,不要改变的)和虚函数(想要选择性重新的)
- 对于继承的层次,一个底层派生类,将继承直接基类的所有成员,而直接基类有会继承它的直接基类的所有成员,因此一个底层派生类会包含它的直接基类以及每个间接基类对象
- 如果想要防止继承发生,可以在类定义的类名后加上final关键字,如果继承了这个类,将发生错误
虚函数:
- 在基类中,用virtual在函数声明前,表示派生类可以选择性重写函数,当没有重写时,虚函数类似普通成员函数,由派生类继承,
- 其中派生类重写覆盖的这个函数也都隐式的为虚函数
- 其中virtual仅能出现在类的构造函数外的非静态成员函数上
- //
- 重写时可以非必要的写virtual关键字,最好写overried关键字,因为它可以提醒我们是否正确的覆盖,当函数没有覆盖基类的函数(形参列表,返回类型不一致,或基类没有函数)都将报错
- 派生类结构:基类部分,派生类新增的自定义部分
- //
- 如果基类定义了static函数,整个继承层级都只包含一个static实例,并遵守访问说明符的访问权限
- 虚函数可以有默认实参
- 派生类仅声明不定义,不包含派生列表
- 一个类若想作为基类,必须定义,而非仅声明
- //
- 派生类可以转换为基类,我们无法做到对象的转换,仅可以转换为基类指针或引用,并且基类不可以转换为派生类,否则将包含派生类中特有,基类没有的部分
- 当派生类转换为基类时,实际会调用 拷贝构造或赋值运算符,这时的调用其实都是调用基类定义的版本
- //
- 构造函数:每个类都仅能初始化自己的成员,也就是说,派生类没法直接初始化基类的成员,都将执行默认初始化,但可以通过构造函数的初始化列表,调用基类的构造函数,从而初始化基类成员
- 动态绑定:使用基类指针或引用,调用虚函数成员,可能执行基类或派生类的版本,解析发生在运行时,否则普通继承的函数,发生在编译时
- 如果不希望动态绑定,希望调用特定的版本,可以用::作用域运算符实现
静态类型 VS 动态类型
- 静态:编译可知,基类类型 或 派生类类型, 编译时可以确定绑定的是哪一个
- 动态:运行可知,基类指针或引用,编译时无法确定绑定的是哪一个
抽象基类:
- 将虚函数最后面 =0表示声明纯虚函数,一个纯虚函数无需定义(虽然可以在类外定义),这样的类为抽象基类,我们无法直接创建类实例
- 表明纯虚函数是一个接口,仅声明没有定义,抽象类可以定义它
- 用派生类重写抽象类,否则派生类也是一个抽象类,无法实例化
访问控制:
- 基类中访问说明符:控制派生类的成员或友元对基类成员访问权限
- protected:受保护的成员,不可以由类实例访问,仅可由成员及派生类和派生类的友元访问,并且仅能通过派生类和派生类的友元访问直接访问,不可以间接从基类访问
- 派生类访问说明符:控制派生类对象和派生类的派生类对基类成员访问权限
- public公有继承:不改变对基类的访问权限
- private私有继承:将public和protected成员的访问权限设置为private
- protected保护继承:将public和protected成员的访问权限设置为protected
- 对于G继承D继承B:
- 只有当D公有地继承B时,用户代码才能使用派生类向基类的转换;如果D继承B的方式是受保护的或者私有的,则用户代码不能使用该转换
- 无论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的
- 如果D继承B的方式是公有的或者受保护的,则G的成员和友元可以使用D向B的类型转换,反之如果D继承B的方式是私有的,则不能使用
- 理解:
- private和protected继承,会改变派生类对象的访问权限
- private和protected继承,不会改变派生类的成员或友元的访问权限
- private和protected继承,会改变派生类的派生类的访问权限,且如果为protected,派生类仍可访问
- 友元不具有传递性,同样的继承也不可以传递,
- 可以通过using控制成员的访问权限,
- 对于class默认派生类访问说明符为private,对于struct默认派生类访问说明符为public