• c++多态


    1.多态概念:去完成某个行为,当不同对象去完成时会产生出不同的效果
    2.多态的构成条件:
    ①.虚函数重写
    ②.父类的指针或引用去调用
    3.虚函数
    被virtual修饰的类成员函数就是虚函数
    4.虚函数重写(覆盖)
    派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数三同(函数名相同,参数列表完全相同,返回值相同)),称子类的虚函数重写了基类的虚函数
    5.协变
    派生类在重写基类虚函数时,与基类虚函数返回值类型不同,即基类虚函数返回基类对象的引用或指针,派生类虚函数返回派生类对象的引用或指针

    #include
    using namespace std;
    class A{
    	
    };
    class B:public A{
    	
    };
    class Person{
    	public:
    		virtual A*fun(){
    			cout<<"A"<<endl;
    			return NULL;
    		}
    };
    class Student:public Person{
    	public:
    		virtual B*fun(){
    			cout<<"B"<<endl;
    			return NULL;
    		}
    };
    int main(){
    	Person*ptr=new Student;
    	ptr->fun();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    如上面这种情况,虽然不符合三同中的返回值类型相同,但是由于返回类型分别为基类与派生类的指针或引用,故也构成多态
    6.析构函数的重写
    如果基类的析构函数为虚函数,此时派生类虚构函数只要定义,无论是否加virtual,都与基类的析构函数构成重写。编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,为了防止如下情况发生

    #include
    using namespace std;
    class Person{
    	public:
    	virtual ~Person(){
    		cout<<"~Person"<<endl;
    	}
    };
    class Student :public Person{
    	~Student(){
    		cout<<"~Student"<<endl;
    	}
    };
    int main(){
    	Person*ptr=new Person;
    	delete ptr;
    	ptr=new Student;
    	delete ptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    7.override和final
    final可以修饰一个类,这个类不能被继承
    final可以修饰一个虚函数,虚函数不能被重写

    virtual void Drive() final{
    }
    
    • 1
    • 2

    override检查派生类虚函数是否重写了基类某个虚函数,如果没有重写报错
    8.抽象类
    在虚函数的后面加上=0,则这个函数为纯虚函数,纯虚函数可以有函数体,但意义不大,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,派生类继承后也不能实例化出对象。只有重写虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,纯虚函数更加体现了接口继承。抽象类可以定义指针,而且经常这样做,其目的就是用父类指针指向子类从而实现多态。
    接口继承与实现继承
    普通函数的继承是一种实现继承,可以使用函数,继承的是函数的实现
    虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口,所以如果不实现多态,不要把函数定义成虚函数
    9.虚函数表与虚函数表指针

    class Base{
    	public:
    	virtual void func1(){
    	}
        private:
    	int b=1;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    sizeof(base)的大小为8
    除了b成员,还多一个_vfptr放在对象的前面,对象中的这个指针叫做虚函数表指针(v代表virtuak,f代表function),一个含有虚函数的类都至少含有一个虚函数表指针,因为虚函数的地址要放在虚函数表中
    派生类虚表的生成
    ①.先将基类中的虚表内容拷贝一份到派生类虚表中
    ②.如果派生类重写了基类的某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数(为什么重写又叫覆盖?覆盖就是指虚表中虚函数的覆盖,重写是语法的叫法,覆盖是原理层的叫法)
    ③.派生类自己新加的虚函数按其在派生类的声明次序加到派生类虚表的最后
    如果子类不重写虚函数,那么子类和父类的虚函数表是否相同?
    不相同,不同的类不会共用一张虚表,相同的类会共用一张虚表

    虚函数表的本质
    是一个存虚函数指针的数组,这个数组最后放了一个nullptr

    演示

    #include
    using namespace std;
    class Base{
    	public:
    		virtual void func1(){
    			cout<<"Base :func1"<<endl;
    		}
    		virtual void func2(){
    			cout<<"Base :func2"<<endl;
    		}
    }; 
    
    class Derive :public Base{
    	public:
    		virtual void func1(){
    			cout<<"Derive :func1"<<endl;
    		}
            virtual void func3(){
            	cout<<"Derive :func3"<<endl;
    		}		
    };
    typedef void(*vfptr)();
    void print(vfptr a[]){
    	for(int i=0;a[i]!=NULL;i++){
              printf("%d %p",i,a[i]);	
    		  	a[i]();	
    	}
    
    }
    int main(){
    	Derive d;
    	print((vfptr*)(*(int*)(&d)));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    通过将函数指针 typedef, typedef void(vfptr)();
    将该类的前四个字节强转为函数指针vfptr,但是不能直接强转,强转要求要有一定的关联
    故采用 (vfptr
    )((int)&d)
    在这里插入图片描述
    多继承的虚函数表
    多继承派生类重写的虚函数放在第一个继承基类部分的虚函数表中

    继承了几个父类,有几个父类有虚函数,就有几个虚表
    菱形继承的虚表,
    A有虚函数,B,C继承A,D继承B,C ,有两张虚表
    A有虚函数,B,C虚拟继承A,D继承B,C,也是有两张虚表,B,C共享A的虚表,D单独创建一个虚表
    10.静态绑定与动态绑定
    静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
    动态绑定又称为后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态
    11.虚函数与普通函数一样,都存在于代码段,同时把虚函数地址存了一份到虚函数表
    虚函数表也存在于代码段
    12.inline函数可以是虚函数吗
    可以,如果是普通调用,inline起作用,如果是多态调用,inline不起作用(inline函数没有地址,无法把地址放到虚函数表中)
    13.构造函数可以是虚函数吗
    不可以,因为对象中的虚函数表指针在构造函数初始化列表阶段才开始初始化
    (虚函数表编译就生成可,虚函数表指针构造时才初始化给对象)
    14.析构函数可以是虚函数吗
    可以,并且最好把基类的析构函数定义成虚函数
    15.对象访问普通函数快还是访问虚函数快
    如果是普通对象,是一样快的,如果是指针对象或者是引用对象,则调用普通函数快,因为构成多态,运行调用虚函数需要到虚函数表中去查找

    class A
    {
    public:
      A ():m_iVal(0){test();}
      virtual void func() { std::cout<<m_iVal<<‘ ’;}
      void test(){func();}
    public:
      int m_iVal;
    };
    class B : public A
    {
    public:
      B(){test();}
      virtual void func()
      {
        ++m_iVal;
        std::cout<<m_iVal<<‘ ’;
      }
    };
    int main(int argc ,char* argv[])
    {
      A*p = new B;
      p->test();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    new B时先调用父类A的构造函数,执行test()函数,在调用func()函数,由于此时还处于对象构造阶段,多态机制还没有生效,所以,此时执行的func函数为父类的func函数,打印0,构造完父类后执行子类构造函数,又调用test函数,然后又执行func(),由于父类已经构造完毕,虚表已经生成,func满足多态的条件,所以调用子类的func函数,对成员m_iVal加1,进行打印,所以打印1, 最终通过父类指针p->test(),也是执行子类的func,所以会增加m_iVal的值,最终打印2, 所以答案为 0 1 2

    16.静态函数可以是虚函数吗
    不能,调用静态成员函数不需要实例,但是调用一个虚函数需要从一个实例中指向虚函数表的指针得到函数的地址,因此调用虚函数需要一个实例

  • 相关阅读:
    java 企业工程管理系统软件源码 自主研发 工程行业适用
    Python基础手册
    Kubernetes 基本概念
    完成基础实验【硬件课程设计】
    含文档+PPT+源码等]精品基于PHP实现的高校兼职招聘系统-前台Uniapp[包运行成功]计算机PHP毕业设计项目源码
    Python实现基于DFS和BFS算法的吃豆人寻路实验
    淘宝/天猫获取购买到的商品订单物流 API 返回值说明
    [附源码]java毕业设计网络身份认证技术及方法
    2024湖南师范大学计算机考研分析
    Elasticsearch跨集群检索配置
  • 原文地址:https://blog.csdn.net/C1238888/article/details/133977610