将属性和行为作为一个整体,表现生活中的事物。语法:class 类名{访问权限:属性/行为}将属性和行为加以权限控制。 2、访问权限有三种:
public公共权限:类内可以访问 类外可以访问;在类的外部,只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员
protected保护权限:类内可以访问 类外不可以访问(子类可以访问父类中的保护内容)
private私有权限:类内可以访问 类外不可以访问(子类不可以访问父类中的私有内容)
成员属性设置为私有的好处:
- 可以自己控制读写权限
- 对于写权限,可以检测数据的有效性
(二)继承
1、意义
类有共性也有特性;可以考虑利用继承的技术,减少重复代码 2、语法
基本语法:class A : public B;
A 类称为子类 或 派生类
派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
B 类称为父类 或 基类
3、继承方式- 不管哪一种继承方式,子类都无法访问父类的私有权限的内容
公共继承: 父类中公共权限的内容变为公共权限、保护权限的内容变为保护权限保护继承: 父类中公共权限、保护权限的内容变为保护权限私有继承: 父类中公共权限、保护权限的内容变为私有权限 4、继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中
父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到 继承中构造和析构顺序
继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?子类对象可以直接访问到子类中同名成员子类对象加作用域可以访问到父类同名成员当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数 继承同名静态成员处理方式
名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名) 4、多继承
多继承
允许一个类继承多个类语法:class 子类:继承方式 父类1,继承方式 父类2多继承可能会引发父类中有同名成员出现,需要加作用域区分;实际开发中不建议用多继承 菱形继承
概念: 两个派生类继承同一个基类,又有某个类同时继承者两个派生类引发的问题:子类继承两份相同的数据,导致资源浪费以及毫无意义解决办法:利用虚继承可以解决菱形继承问题(虚继承继承的是指针而非数据) 虚函数和虚继承的区别
虚函数:是在函数声明/定义时,必须加上virtual关键字。作用就是让其派生类能够覆盖此函数,从而实现多态(运行时多态)。还有一点就是在派生类中,继承基类同名的虚函数后,不管派生类中显示写不写virtual,此函数依然是虚函数。
补充: 编译时多态性:通过重载函数和运算符重载实现。运行时多态性:通过虚函数和继承实现纯虚函数:纯虚函数在基类中没有定义,没有具体的实现。他把实现工作交给了派生类(大部分情况)。此时这个含有纯虚数的类称为抽象类,这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。虚继承:是在继承时是一种继承方式,虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:浪费存储空间和存在二义性问题。虚继承可以解决上面两个问题,其虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。vbptr指的是虚基类表指针(virtual base table pointer)该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类的两份同样的拷贝,节省了存储空间。
虚函数与虚继承对比:都用了虚指针和虚表——虚指针占用类空间,虚表都不占用。虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。 虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。
(三)多态
1、多态的优点:
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
2、实现方式一:静态多态:函数重载和运算符重载属于静态多态,复用函数名
运算重载符
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
注意:对于内置的数据类型的表达式的的运算符是不可能改变的;不要滥用运算符重载
关系运算符重载==/!=
作用:以让两个自定义类型对象进行对比操作
加号运算符重载+
作用:实现两个自定义数据类型相加的运算
左移运算符重载<<
作用:可以输出自定义数据类型;重载左移运算符配合友元可以实现输出自定义数据类型(cout:本质上是标准输出流对象)
递增运算符重载++
作用: 通过重载递增运算符,实现自己的整型数据
前置递增返回引用,后置递增返回值
赋值运算符重载=
c++编译器至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
函数调用运算符重载()
函数调用运算符 () 也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数;仿函数没有固定写法,非常灵活
多态满足条件:有继承关系;子类重写父类中的虚函数
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
虚函数:
虚函数(表)指针——>虚函数表:记录虚函数的地址
当父类的指针或者引用指向子类对象时,发生多态;(子类中的虚函数表内部会把父类的虚函数地址替换成子类的虚函数地址)
虚函数与非虚函数的区别
通过"基类的指针" 访问 子类(们)的成员函数。这叫动态多态。是用虚函数的技术完成的。这也叫动态绑定。
当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定(dynamic binding) 因为我们直到运行时才能知道到底调用了哪个版本的虚函数,可能是基类中的版本也可能是派生类中的版本,判断的依据是引用(或指针)所绑 定的对象的真实类型。
与非虚函数在编译时绑定不同,虚函数是在运行时选择函数的版本,所以动态绑定也叫运行时绑定(run-time binding)。
虚函数实现原理
虚指针:任何含有虚函数的类中,不论这个虚函数是继承的还是定义的,都有一个函数指针指向一个虚函数表,虚函数表是一个数组,数组中的每个元素都是一个虚函数指针
虚函数表:虚指针指向的就是虚函数表,本质是一个数组,存着所有的虚函数指针。如果父类的虚函数没有被子类改写, 那么子类的虚函数表中的元素就是父类的对应的虚函数指针;相反,如果子类改写了父类的虚函数,那么对应的虚函数表中的元素就是自己的虚函数指针,决议这个指向的过程发生在运行时,就是所谓的动态绑定!
多态使用条件:父类指针或引用指向子类对象
4、静态多态和动态多态区别: 静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
5、纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。因此可以将虚函数改为纯虚函数。当类中有了纯虚函数,这个类也称为抽象类语法:virtual 返回值类型 函数名(参数列表)=0;抽象类的特点
无法实例化对象子类必须重写抽象类中的纯虚函数,否则也属于抽象类 6、虚析构和纯虚析构
问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码解决方式:将父类中的析构函数改为虚析构或者纯虚析构虚析构和纯虚析构共性:可以解决父类指针释放子类对象时不干净的问题;都需要有具体的函数实现虚析构和纯虚析构区别:如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}纯虚析构语法:virtual ~类名() = 0;类名::~类名(){}



