深入了解继承与多态(virtual)

本文探讨了C++中的继承、多态和virtual关键字。通过示例代码解析了virtual如何影响函数调用,强调了动态绑定和虚函数表的概念。同时,介绍了虚函数的条件、纯虚函数的应用,以及虚函数在内存布局中的体现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  我们先了解一下下面这段代码:

#include<iostream>
using namespace std;
class Base
{
public:
	Base(int a =10):ma(a){}
	void show()
	{
		cout<<"ma="<<ma<<endl;
	}
protected:
	int ma;
};
class Derived:public Base
{
public:
	Derived(int b ):mb(b),Base(b){}
	void show()
	{
		cout<<mb<<endl;
	}
private:
	int mb;
};
int main()
{
	 Base* pb = new Derived (10);

	std::cout << "Base size:" << sizeof(Base) << std::endl;
	std::cout << "Derived size:" << sizeof(Derived) << std::endl;

	std::cout << "pb type:" << typeid(pb).name() << std::endl;
	std::cout << "*pb type:" << typeid(*pb).name() << std::endl;
	return 0;
}

运行结果为:

如果我们给基类(Base)的show()函数中加入一个virtual,那么结果会改变的:

#include<iostream>
using namespace std;
class Base
{
public:
	Base(int a =10):ma(a){}
	virtual  void show()
	{
		cout<<"ma="<<ma<<endl;
	}
protected:
	int ma;
};
class Derived:public Base
{
public:
	Derived(int b ):mb(b),Base(b){}
	void show()
	{
		cout<<mb<<endl;
	}
private:
	int mb;
};
int main()
{
	 Base* pb = new Derived (10);

	std::cout << "Base size:" << sizeof(Base) << std::endl;
	std::cout << "Derived size:" << sizeof(Derived) << std::endl;

	std::cout << "pb type:" << typeid(pb).name() << std::endl;
	std::cout << "*pb type:" << typeid(*pb).name() << std::endl;
	return 0;
}

 则运行结果为:

 那么加了virtual后,其中改变了什么?

virtual

在了解多态之前,我们先了解重载,隐藏,覆盖

     1 成员函数重载特征:
           a 相同作用域(在同一个类中)
            b 函数名字相同
            c 参数不同
            d virtual关键字可有可无
      2 重写(覆盖)是指派生类函数覆盖基类函数,特征是:
           a 不同作用域,分别位于基类和派生类中
           b 函数的名字相同
           c 参数相同
           d 基类函数必须有virtual关键字
     3 重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
           a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
           b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。(如果父类的成员和子类的成员属性名称相同,我们可以通过作用域操作符来显式的使用父类的成员,如果我们不使用作用域操作符,默认使用的是子类的成员属性。)

        C++的多态性 :在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数

 

  在派生类中加入了虚函数指针,虚函数指针的优先级最高,虚函数指针指向了虚函数表

  虚函数表:

       

那么,在生成派生类的时候,他们的存储形式就变为了:

    

因此我们就可以解释上面代码的运行结果了:

std::cout << "Base size:" << sizeof(Base) << std::endl;//多了一个虚函数指针,增加了4个字节大小
std::cout << "Derived size:" << sizeof(Derived) << std::endl;;//多了一个虚函数指针,增加了4个字节大小

std::cout << "pb type:" << typeid(pb).name() << std::endl;//基类的指针指向的是派生类,实际指向了上图红色区域的部分
std::cout << "*pb type:" << typeid(*pb).name() << std::endl;//对其解引用的收时候,vfptr指针优先级最高,所以就指向了派生类                                                                                                 的对象(对基类的show()进行覆盖)

动多态的发生条件

  指针或引用调用虚函数  对象必须完整
    静态绑定  早绑定
    动态绑定  晚绑定

  虚函数表在编译期间生成

 成为虚函数条件

       1.能取地址  2.依赖对象调用

哪些函数可以成为虚函数

      基类指针指向派生类对象   基类设置虚析构

那些函数不可以成为虚函数

   1.inLine(内联)函数,因为内联函数不能取地址

  2.普通函数  不依赖对象调用

  3.构造函数   不依赖对象调用

  4.static 静态成员函数,不依赖对象调用

注意:

 1.基类的虚构为虚析构,则派生类的析构为虚析构

#include<iostream>
#include<vld.h>
using namespace std;
class Base
{
public:
	Base(int a =10):ma(a){}
	void show()
	{
		cout<<"ma="<<ma<<endl;
	}
	virtual ~Base()
	 {
		 cout<<"基类的析构"<<endl;
	 }
protected:
	int ma;
};
class Derived:public Base
{
public:
	Derived(int b ):mb(b),Base(20){}
    void show()
	{
		cout<<mb<<endl;
	}
     ~Derived()
	{
		cout<<"派生类的析构"<<endl;
	}
private:
	int mb;
};
int main()
{
	 Base* pb = new Derived (50);
	 delete pb;
    return 0;
}

运行结果为:

其中,指针如下图这样指向,先析构派生类的资源,再析构 基类的资源。

 

如果没有在基类的析构中加入在虚函数,则运行的结果为:

 

其中,指针指向为下图,因为pb只指向基类的内存,所以只调运了基类的虚构,没有调用派生类的析构函数

如果我们仅仅在派生类的一个函数中加入虚函数,那么在释放内存的时候会崩溃

#include<iostream>
#include<vld.h>
using namespace std;
class Base
{
public:
	Base(int a =10):ma(a){}
	void show()
	{
		cout<<"ma="<<ma<<endl;
	}
	 ~Base()
	 {
		 cout<<"基类的析构"<<endl;
	 }
protected:
	int ma;
};
class Derived:public Base
{
public:
	Derived(int b ):mb(b),Base(20){}
    virtual void show()
	{
		cout<<mb<<endl;
	}
     ~Derived()
	{
		cout<<"派生类的析构"<<endl;
	}
private:
	int mb;
};
int main()
{
	 Base* pb = new Derived (50);
	 delete pb;
	return 0;
}

 运行奔溃了:

原因很简单:如下图,在释放的内存的时候,指针指向派生类中的基类,这次指针指的不是派生类的内存的开始,所以释放的时                        候,会崩溃。

2.基类指针指向派生类对象,基类设置虚析构

3.虚表的写入时机:构造函数的第一行代码执行那个执行之前

4.在派生类对象生成的时候,会有vfptr多次赋值

       基类中,有一个函数为虚函数,那么就会有一个vfptr,则派生类中同名同参的函数也会变为虚函数,有一个vfptr,那么这两个虚表会合并,生成一个vfptr

5.基类的指针指向派生类对象:指向的是派生类对象中基类的起始位置

纯虚函数

下面通过一个例子来说明纯虚函数的定义方法:

class Animal
{
public:
	Animal(string name):mname(name){}
	virtual void bark() = 0;//纯虚函数
	virtual void show()
	{
		cout<<"mname= "<<mname<<endl;
	}
private:
	string mname;
};

     在这个类当中,我们定义了一个普通的虚函数,并且也定义了一个纯虚函数。那么,纯虚函数是什么呢??从上面的定义可以看到,纯虚函数就是没有函数体,同时在定义的时候,其函数名后面要加上“= 0”

      纯虚函数和普通虚函数的实现原理是基本差不多的,都有一个虚指针vfptr,虚指针指向一个虚表,保存函数的入口地址,那么下面我们通过一段代码,来了解纯虚函数;

#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
	Animal(string name):mname(name){}
	virtual void bark() = 0;//纯虚函数
	virtual void show()
	{
		cout<<"mname= "<<mname<<endl;
	}
public:
	string mname;
};
class Dog:public Animal
{
public:
	Dog(string name):Animal(name){}
	void bark()
	{
		cout<<"wang wang wang"<<endl;
	}
};
class Cat:public Animal
{
public:
	Cat(string name):Animal (name){}
	void bark()
	{
		cout<<"miao miao miao"<<endl;
	}
};
int main()
{
	Dog*dog = new Dog("dog");
	dog->bark();
	dog->show();
	Cat*cat = new Cat(" cat");
	cat->bark();
	cat->show();
	return 0;
}

运行结果为:

内存布局为:

Dog类的内存布局为:

   

vfptr指针所指向的虚函数表为:

 

 Cat类的内存布局为:

 vfptr指针所指向的虚函数表为:

注意:

 

#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
	Animal(string name):mname(name){}
	virtual void bark() = 0;//纯虚函数
	virtual void show()
	{
		cout<<"mname= "<<mname<<endl;
	}
public:
	string mname;
};
class Dog:public Animal
{
public:
	Dog(string name):Animal(name){}
	void bark()
	{
		cout<<"wang wang wang"<<endl;
	}
};
class Cat:public Animal
{
public:
	Cat(string name):Animal (name){}
	void bark()
	{
		cout<<"miao miao miao"<<endl;
	}
};
int main()
{
	Animal* Ani = new Animal("GG");
	return 0;
}

 程序错误:

交换两个虚指针 

#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
	Animal(string name):mname(name){}
	virtual void bark() = 0;//纯虚函数
	virtual void show()
	{
		cout<<"mname= "<<mname<<endl;
	}
public:
	string mname;
};
class Dog:public Animal
{
public:
	Dog(string name):Animal(name){}
	void bark()
	{
		cout<<"wang wang wang"<<endl;
	}
};
class Cat:public Animal
{
public:
	Cat(string name):Animal (name){}
	void bark()
	{
		cout<<"miao miao miao"<<endl;
	}
};
int main()
{
	Dog*dog = new Dog("dog");
	Cat*cat = new Cat(" cat");

	int*d1 = (int *)dog;
	int*c1 = (int *)cat;

	int tmp = *d1;
	*d1 = *c1;
	*c1 = tmp;

	dog->bark();
	cat->bark();

	delete cat;
	delete dog;

	return 0;
}

运行结果为:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值