多态
-
同一名称,不同的功能实现方式。
-
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。虚函数是运行时多态的基础。
多态就是不同继承类的对象,对同一消息做出不同的响应(函数重写,不同继承类的对象的函数实现不一样)
多态是通过虚函数实现的,虚函数的地址保存在虚函数表中,虚函数表的地址保存在含有虚函数的类的实例对象的内存空间中,对象里存着对应虚函数表的地址。
基类的指针指向或绑定到派生类的对象,调用的时候会用派生类的函数,因为会用派生类的虚函数表。(但是如果不是重写,不是虚函数,没有在虚函数表里的话,调用的时候用的就是基类的函数)。
但是在构造函数和析构函数中调用虚函数不是多态,因为编译的时候即可确定调用的函数时自己的类或者基类中定义的函数,不会等运行时才决定。
虚函数
有虚函数的基类和派生类,都有个虚函数表,虚函数表里的指针指向对应虚函数的地址,就可以找到对应虚函数。而类的对象里只会存相应虚函数表的指针,也就是不管有多少个虚函数,对象里存的大小都不变,都只有一个指针。
编译器将虚函数表的指针放在类的实例对象的内存空间中,该对象调用该类的虚函数时,通过指针找到虚函数表,根据虚函数表中存放的虚函数的地址找到对应的虚函数。(因为是在运行的时候才通过指针去找虚函数表,所以是虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销)
类对象的内存中只保存了指向虚函数表的指针,虚函数表存在数据段的只读区域,虚函数本身是在代码段的。
子类是有自己的虚函数表的,对象里也有自己的虚函数表指针的。只是继承之后没有重写的话,子类虚函数表里指向的虚函数就还是原来的虚函数。子类有重写的话,表里指向的虚函数就是新的函数地址,改的是表里存的函数的地址。
-
在类中用 virtual 关键字声明的函数叫做虚函数;
-
存在虚函数的类都有一个虚函数表。当创建一个该类的对象时,该对象有一个指向虚函数表的虚表指针(虚函数表和类对应的,虚表指针是和对象对应);(一个类有一个虚函数表,不同的对象有自己的虚表指针)(虚函数表属于类,类的所有对象是共享这个虚函数表的,但是每个对象里都有存指向的指针)
-
当基类指针指向派生类对象,基类指针调用虚函数时,基类指针指向派生类的虚表指针,由于该虚表指针指向派生类虚函数表,通过遍历虚表,寻找相应的虚函数。
(如果未使用虚函数,则是隐藏,则父类指针指向子类对象时,使用的是父类的方法(与指针类型看齐,因为是父类指针)(到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。父类指针指向的要用的就是父类的,但是有虚函数的话,父类指针指向的虚函数表指针还是子类的,所以会调子类的虚函数。如果不是虚函数的话,就用的是父类的。))
-
没有对虚函数进行重写的话,用的就还是父类的函数,就会把父类的虚函数表也继承下来,有重写的话就把表里的改了
-
需要虚析构函数,就可以通过基类指针删除派生类对象,否则调的析构函数时基类的
纯虚函数
-
纯虚函数在类中声明时,加上 = 0;
-
含有纯虚函数的类称为抽象类(只要含有纯虚函数这个类就是抽象类),类中只有接口,没有具体的实现方法;
-
继承纯虚函数的派生类,如果没有完全实现基类纯虚函数,依然是抽象类,不能实例化对象。
-
抽象类对象不能作为函数的参数,不能创建对象,不能作为函数返回类型,但可以声明抽象类指针,可以声明抽象类的引用(就是不能有实际的对象);
-
子类必须继承父类的纯虚函数,并全部实现后,才能创建子类的对象。
重载、重写、隐藏
重载:是指同一可访问区内被声明几个具有不同参数列(参数的类型、个数、顺序)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。(函数名相同,参数不同,一个类内部的)
隐藏:是指派生类的函数屏蔽了与其同名的基类函数,主要只要同名函数,不管参数列表是否相同,对派生类的对象用该函数的话基类函数都会被隐藏,除非指定要用基类的函数。(函数名相同,在派生类里写的)
重写:是指派生类中存在重新定义的函数。函数名、参数列表、返回值类型都必须同基类中被重写的函数一致,只有函数体不同。派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有 virtual 修饰。(函数名、参数、返回值都相同,只有函数体不同,在派生类里写的,要加virtual)
重载 | 重写 | 隐藏 |
---|---|---|
一个类内部(同个作用域内) | 多个类间,父类与子类 | 多个类间,父类与子类 |
函数名相同,参数不同,返回值无所谓 | 函数名、参数、返回值相同,只有函数体不同 | 函数名相同,参数和返回值无所谓 |
父类中该函数要有virtual修饰,子类中可以没有(相当于提前想好了子类里要赋新的功能) | 当有virtual,但是参数、返回值不同的话也算隐藏,不能算重写 | |
相当于在子类里重新写函数的功能(感觉跟隐藏是类似的,只是要求更多) | 相当于在子类里隐藏了父类的同名函数 | |
不同的类有对应的虚函数表,每个对象都有指向自己虚函数表的指针。根据这个指针找到对应要用的函数(虽然是父类指针,但指向对象里的虚函数表指针指向的是子类的) | 就没有虚函数表指针。父类指针指向子类对象时,使用的是父类的方法(看指针类型,因为是父类指针) |
运算符重载:返回类型 operator 运算符(形参)
前置单目运算符重载返回类型是引用,不需要参数;后置单目运算符重载返回类型是普通的,是计算前的值,且需要参数(这个参数也只是要向编译器说明这是一个后缀形式)。