【c++】多态

多态概念

同一事物,在不同场景下表现出不同的形态


静态多态是在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换)。可推断要调用哪个函数,如果有对应的函数就调用该函数,否则编译出现错误

动态多态:在程序运行时确定程序的行为(具体调用哪个函数)

虚函数就是在类的成员函数(除构造函数,拷贝构造函数,静态成员函数)前加virtual关键字

class B
{
public:
	virtual void TestFunc()
	{
		cout<<"B::TestFunc()"<<endl;
	}
	int _b;
};

int main()
{
	B b;
	cout<<sizeof(B)<<endl;
	return 0; 
}

为什么打印结果是8呢?

在内存窗口可以看到,发现它的前四个字节好像是个地址,其实这里的地址就是虚函数的地址,在带有虚函数的类中会多开辟四个字节用来存放一张指向虚表的地址,虚表里存放的是虚函数的地址。要注意的是,虚函数只有在继承体系下才有意义,因为在非继承体系用不到,还多开辟了4字节空间,带有虚函数的类的对象模型如下


动态多态的条件

  • 基类中必须虚函数,并且派生类一定要对基类中的虚函数进行重写
  • 通过基类对象的指针或引用调用虚函数

重写:

  • 该函数在基类中必须为虚函数
  • 派生类虚函数要和基类中的虚函数原型相同(返回值,参数列表,函数名均相同)
  • 派生类虚函数可以与基类中虚函数的访问限定符不同
注意:协变和析构函数除外
协变:返回值可以不同,但是基类的虚函数必须返回基类对象的指针(引用),派生类的虚函数必须返回派生类对象的指针(引用)---这里不符合返回值相同,但也是重写

析构函数:析构函数也可以构成重写(函数名不同)

多态含义

  1. 如果基类的指针(引用)指向引用基类的对象,那么在调用虚函数时调用基类的虚函数
  2. 如果基类的指针(引用)指向引用派生类的对象,那么在调用虚函数时调用属于派生类的虚函
class Base
{
public:
	virtual void TestFunc()
	{
		cout<<"Base::TestFunc()"<<endl;
	}
	int _b;
};

class Derived :public Base
{
public:
	virtual void TestFunc()
	{
		cout<<"Derived::TestFunc()"<<endl;
	}
};

void Test(Base& b)//通过基类的引用调用虚函数
{
	b.TestFunc();
}

int main()
{
	Derived d;
	Base b;
	Test(d);
	Test(b);
	return 0; 
}

动态多态实现原理

  1. 编译器在带有虚函数的类背后维护了一张虚表(虚函数的入口地址)
  2. 虚函数的调用原理(通过基类的指针或者引用调用虚函数)
         >>从指针所指向对象(基类/派生类)前4个字节中取虚表的地址
         >>传递参数(this + 当前虚函数的参数) 
         >>根据从对象前4个字节取到的虚表的地址取对象的虚函数
         >>调用虚函数
单继承下的对象模型 (带有虚函数)           
基类:将类中的虚函数按照其声明次序放置到虚表中
派生类:

  1. 将基类虚表中虚函数地址拷贝一份
  2. 如果派生类重写了基类中的某个虚函数,将派生类虚表中相同偏移量位置的基类虚函数替换为派生类自己的虚函数
  3. 将派生类自己新增的虚函数按照其在派生类中的声明次序放置到虚表的最后
class Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Base::TestFunc1()"<<endl;
	}

	virtual void TestFunc2()
	{
		cout<<"Base::TestFunc2()"<<endl;
	}

	int _b;
};

class Derived :public Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Derived::TestFunc1()"<<endl;
	}

	virtual void TestFunc3()
	{
		cout<<"Derived::TestFunc3()"<<endl;
	}

	int _d;
};


int main()
{
	Derived d;
	d._b =10;
	d._d =20;

	cout<<sizeof(d)<<endl;
	return 0; 
}


普通成员函数和虚函数的区别:

最大的区别就是调用原理不同。普通成员函数柯直接调用

多继承下的对象模型(带有虚函数)
class B1
{
public:
	virtual void TestFunc1()
	{
		cout<<"B1::TestFunc1()"<<endl;
	}

	virtual void TestFunc2()
	{
		cout<<"B1::TestFunc2()"<<endl;
	}

	int _b1;
};

class B2
{
public:
	virtual void TestFunc3()
	{
		cout<<"B2::TestFunc3()"<<endl;
	}

	virtual void TestFunc4()
	{
		cout<<"B2::TestFunc4()"<<endl;
	}

	int _b2;
};

class Derived :public B1,public B2
{
public:
	virtual void TestFunc1()
	{
		cout<<"Derived::TestFunc1()"<<endl;
	}

	virtual void TestFunc3()
	{
		cout<<"Derived::TestFunc3()"<<endl;
	}

	virtual void TestFunc5()
	{
		cout<<"Derived::TestFunc5()"<<endl;
	}

	int _d;
};


int main()
{
	Derived d;
	d._b1=10;
	d._b2=20;
	d._d =30;

	cout<<sizeof(d)<<endl;
	return 0; 
}


可以看出,这两个地址分别指向两张虚表,一张为继承B1的,另一张为继承B2的,第一张虚表还会存放派生类中特有的虚函数。

菱形继承(带有虚函数)
class B
{
public:
	virtual void TestFunc1()
	{
		cout<<"B::TestFunc1()"<<endl;
	}

	virtual void TestFunc2()
	{
		cout<<"B::TestFunc2()"<<endl;
	}

	int _b;
};


class C1:public B
{
public:
	virtual void TestFunc1()
	{
		cout<<"C1::TestFunc1()"<<endl;
	}

	virtual void TestFunc3()
	{
		cout<<"C1::TestFunc3()"<<endl;
	}

	int _c1;
};


class C2:public B
{
public:
	virtual void TestFunc2()
	{
		cout<<"C2::TestFunc2()"<<endl;
	}

	virtual void TestFunc4()
	{
		cout<<"C2::TestFunc4()"<<endl;
	}

	int _c2;
};

class Derived :public C1,public C2
{
public:
	virtual void TestFunc1()
	{
		cout<<"Derived::TestFunc1()"<<endl;
	}

	virtual void TestFunc2()
	{
		cout<<"Derived::TestFunc2()"<<endl;
	}

	virtual void TestFunc5()
	{
		cout<<"Derived::TestFunc5()"<<endl;
	}

	int _d;
};


int main()
{
	Derived d;
	d.B::_b =1;
	d._c1 =2;
	d._c2 =3;
	d._d =4;

	cout<<sizeof(d)<<endl;
	return 0; 
}

对象模型如下:

虚拟继承(带有虚函数)
class Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Base::TestFunc1()"<<endl;
	}

	int _b;
};

class Derived :virtual public Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Derived::TestFunc1()"<<endl;
	}

	int _d;
};


int main()
{
	Derived d;
	d._b =10;
	d._d =20;

	cout<<sizeof(d)<<endl;
	return 0; 
}

上面的是派生类没有自己新增的虚函数,现在新加一个虚函数

class Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Base::TestFunc1()"<<endl;
	}

	int _b;
};

class Derived :virtual public Base
{
public:
	virtual void TestFunc1()
	{
		cout<<"Derived::TestFunc1()"<<endl;
	}

	virtual void TestFunc2()
	{
		cout<<"Derived::TestFunc2()"<<endl;
	}

	int _d;
};


int main()
{
	Derived d;
	d._b =10;
	d._d =20;

	cout<<sizeof(d)<<endl;
	return 0; 
}

算起来大小比上面的多了4个字节,其实就是如果派生类自己新增的虚函数,就会多开辟四个字节,指向另一张虚表,这张虚表存放派生类自己的虚函数地址

菱形虚拟继承(带有虚函数)

class B
{
public:
    virtual void TestFunc1()
    {
        cout << "B::TestFunc1()" << endl;
    }

    virtual void TestFunc2()
    {
        cout << "B::TestFunc2()" << endl;
    }

    virtual void TestFunc3()
    {
        cout << "B::TestFunc3()" << endl;
    }

    int _b;
};

class C1 : virtual public B
{
public:
    virtual void TestFunc1()
    {
        cout << "C1::TestFunc1()" << endl;
    }
    int _c1;
};

class C2 : virtual public B
{
public:
    virtual void TestFunc2()
    {
        cout << "C2::TestFunc2()" << endl;
    }
    int _c2;
};

class D : public C1, public C2
{
public:
    virtual void TestFunc3()
    {
        cout << "D::TestFunc3()" << endl;
    }

    int _d;
};


int main()
{
    D d;

    cout << sizeof(C1) << endl;
    cout << sizeof(D) << endl;
    d._b = 1;
    d._c1 = 2;
    d._c2 = 3;
    d._d = 4;

    return 0;
}

哪些函数不能作为虚函数

  • 构造函数不能作为虚函数(虚表中没有对象地址)
  • 拷贝构造不能作为虚函数
  • 赋值运算符重载可以作为虚函数----建议:最好不要作为虚函数
  • 析构函数可以为虚函数---建议:在带有虚函数的类中,最好将基类中的析构函数作为虚函数,否则会造成内存泄漏
  • 静态函数不能作为虚函数(没有this指针)


猜你喜欢

转载自blog.csdn.net/lw__sunshine/article/details/80965001