c++:多态,看这一篇就足够了

目录

一:什么是多态

二:如何构建多态

三:虚函数

定义:

格式:

重写:

重写三大特性

协变:

析构函数的重写:

C++11新增的 override 和 final

override

 final

 重载、覆盖(重写)、隐藏(重定义)的对比

四、多态的原理:

虚函数表

总结:

动态绑定与静态绑定

动态绑定:

静态绑定:


一:什么是多态

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

举个例子:买票

普通人全价,学生半价,军人全价但优先购票....

这就是对买票这一个具体行为,不同人去完成时产生的不同结果。

二:如何构建多态

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

好吧是不是看不懂,那我还是举例说明

class human//定义普通人的类
{
public:
	virtual void buy()
	{
		cout << "普通人 全价 100元" << endl;
	}
};
class student :public human//定义继承了普通人类的学生类
{
public:
	virtual void buy()//子类完成对父类虚函数的重写
	{
		cout << "学生 半价 50元" << endl;
	}
};
void BuyTicket(human* p)
{
	p->buy();//必须通过指针和引用调用虚函数
}

//void BuyTicket(human& p)也可以这样子,这样传参的时候下面就不加引号
//{
//	p.buy();
//}

int main()
{
	student st;
	human hu;
	BuyTicket(&st);//子类传对象切片
	BuyTicket(&hu);父类对象传地址
	return 0;
}

大概是这样的

 好吧,我们大概搞懂了是这样传值的流程,可是这个virtual虚函数是个什么东西?重写又是个啥?

三:虚函数

定义:

虚函数:即被virtual修饰的类成员函数称为虚函数。

格式:

virtual +成员函数;

好吧看起来是不是挺简单的 

重写:

在介绍重写这部分的时候,不知道大家对于函数重载还有没有概念。

函数重载举一个例子:在同一个作用域内,函数名相同,参数不同

class A
{
public:
	void fun(int i)
	{
		cout << i << endl;
	}
	void fun()
	{
		cout << "haha" << endl;
	}
};

重写三大特性

1.函数名相同

2.返回值类型相同

3.参数列表相同

好的我们看看刚才的代码

class human//第一个类
{
public:
	virtual void buy()//返回类型相同,函数名相同,参数相同
	{
		cout << "普通人 全价 100元" << endl;
	}
};


class student :public human//第二个类
{
public:
	virtual void buy()//返回类型相同,函数名相同,参数相同
	{
		cout << "学生 半价 50元" << endl;//这里就是重写了
	}
};

所以说只要牢记重写的三大特点,以及是否在同一作用域,就很容易区分重写和重载的区别

这里还是有两个例外,大家作为了解就可以了,现实中也不常用到

协变:

返回值类型不同,子类返回子类虚函数的指针或引用,父类返回父类虚函数的指针或引用

举例:

class Father
{};
class Son : public Father
{};

class A {
public:
	virtual Father* f()//父类返回父类的指针或引用
	{ 
		return new Father; 
	}
};

class B : public A {
public:
	virtual Son* f()//子类返回子类的指针或引用 
	{
		return new Son;
	}
};

析构函数的重写:

在c++的编译器中,析构函数这里默认都给了同一个名称destructor,你看上去,这里是virtual~A和virtual~B,实际上都是~virtual destructor

这样其实也就满足了我们重写的特性,返回值类型相同,函数名相同,参数相同

class A
{
public:
	virtual~A()//实际上是virtual~destructor()
	{
		cout << "HHHHHHHHHHHHHHHHHH" << endl;
	}
};
class B :public A
{
public:
	virtual~B()//实际上是virtual~destructor()
	{
		cout << "666666666666666" << endl;
	}
};
int main()
{
	B b;
	return 0;
}

C++11新增的 override 和 final

override

检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。(定义在子类)

class A
{
public:
	virtual void fun()
	{
	}
};



class B :public A
{
public:
	virtual void fun(int a)override//我这里在参数列表中加入了一个int,就不构成重写,编译器就会报错
	{
		
	}
};

 final

修饰虚函数,表示该虚函数不能再被继承(定义在父类)

class A
{
public:
	virtual void fun()final//加了final后,子类无法继承该函数
	{
	}
};
class B :public A
{
public:
	virtual void fun()//所以这里会报错
	{
		
	}
};

 重载、覆盖(重写)、隐藏(重定义)的对比

四、多态的原理:

虚函数表

我们先来看一个经典例题

//求B,也就是sizeof(A)的大小
class A
{
public:
    virtual void fun()
        {
        cout << "fun()" << endl;
    }
private:
    int  age;
};
int main()
{
    A a;
    int B=sizeof(a);
    cout << B << endl;
	return 0;
}

 按照正常的思维,这里我们只定义了一个int age的private,那么应该是只有四个字节

然后,结果如下:

 这是因为一个含有虚函数的类中都至少都有一个虚函数表指针

我们看调试代码

 这个_vfptr就是我们的虚函数表指针因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

然后我们再对上面的代码改造一下

我们对classA增加两个函数,然后再新增一个class B继承

class A
{
public:
    virtual void fun1()
        {
        cout << "fun1()" << endl;
    }
    virtual void fun2()//新增有virtual
    {
        cout << "fun2()" << endl;
    }
    void fun3()//新增无virtual
    {
        cout << "fun3()" << endl;
    }
private:
    int  age;
};
class B :public A//新增类继承
{
public:
    virtual void fun1()//重写
    {
        cout << "fun1()" << endl;
    }
private:
    int id;
};
int main()
{
    A a;
    B b;
    int B=sizeof(a);
    int C = sizeof(b);
    cout << B << endl << C << endl;
	return 0;
}

然后我们调用调试代码

同时结果如下:

解释一下,8=我们自定义int的4个字节+virtual的4个字节(父类就一共八个字节)

12= 8(子类拷贝父类的虚表,有覆盖就覆盖,没覆盖就直接拷贝)+4我们自定义的int类型4个字节(子类拷贝覆盖的父类虚函表+子类自定义)

同时我们也需要知道,其实我们并不是直接创立的虚函数表,而是virtual这个虚表指针指向的虚函数表头,如下:

这就表明了,我们子类的虚函数,不仅继承了父类虚函数中的_vfptr还能重写了父类的虚函数

 好吧看一大堆是不是有点看不懂,那直接看总结吧

总结:

1.虚函数自身自带一个_vfptr的虚函数表

2.子类虚函数继承父类虚函数时,不仅继承父类虚函数表,也可以对虚函数进行重写。

3.子类虚函数继承父类虚函数时,非虚函数不会存放在_vfptr中

4.虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。

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

动态绑定与静态绑定

动态绑定:

动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

理解到这一点之后,我们重新来看最开始多态是如何用重写来找到对应的函数的。

 在运行该代码的时候,我们编译器自动在_vfptr,也就是虚函数表中寻找对应的函数来匹配,所以不是在编译时确定的,是运行起来以后到对象的中去找的我们称为动态绑定

静态绑定:

静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值