c++虚函数与多态

什么是多态?

多态就是同一种事物的不同表现,比如开学,那么学校,老师,家长,学生会做不同的准备,这就是多态。
放在c++里面来说,多态表现的形式之一就是:用同一个函数名,实现不同的函数功能,即“一个接口,多种方法”。

我们知道在同一个类中,不能定义两个名字相同,参数个数和类型都相同的函数,否则就是重复定义;但在不同类中可以出现名字,参数个数和类型都相同的函数,但函数功能不相同,此时会根据同名覆盖原理来进行调用,例如:

所以人们提出,能否用一个调用形式,既能调用派生类又能调用基类的同名函数。在程序中,不通过对象名去调用,而是通过指针调用它,在调用前,给指针变量赋予不同的值,使之指向不同的类对象。。c++中虚函数就是用来解决这个问题的。

看代码如下:



 

class person
{
public:
	void display()
	{
		cout << "买全价价票" << endl;
	}

};

class student:public person
{
public:
	void display()
	{
		cout << "买半价票" << endl;
	}
};

void fun(person & p)//void fun(person *p)
{

	p.display();
}

int main() 
{
	person p;
	fun(p);
	student s;
	fun(s);
	system("pause");

}


运行结果
在这里插入图片描述

跟预想结果不一样,我们希望调用哪个类,就调用属于它的函数,c++就提供多态来解决这个问题
稍微做改变:

class person
{
public:
	virtual void display()                            //虚函数
	{
		cout << "买全价价票" << endl;
	}

};

class student:public person
{
public:
	virtual void display()                          //虚函数重写 
	{
		cout << "买半价票" << endl;
	}
};

void fun(person & p)//void fun(person *p)
{
		p.display();
}

int main() 
{
	person p;
	fun(p);
	student s;
	fun(s);
	system("pause");

}


在这里插入图片描述

虚函数----实现多态调用

在谈虚函数名称前加virtual
了解几个概念:

  1. 函数的重载:在同一作用域当中,函数名相同,参数的类型和个数有一个不同,就构成重载,返回值可以不同。
  2. 函数的重定义(同名隐藏):不在同一个作用域,子类成员函数与父类成员函数名相同。
  3. 函数的重写(覆盖):避灾同一作用域,子类成员的虚拟函数与父类成员的虚拟成员函数完全一样(函数名,参数,返回值都一样),基类函数必须有virtual。

多态构成条件:

  • 基类中必须有虚函数,且子类对基类的虚函数进行重写
  • 通过基类对象 的指针或者引用调用函数

普通调用和多态调用

普通调用:

class person
{
public:
	virtual void display()
	{
		cout << "买全价价票" << endl;
	}

};

class student:public person
{
public:
	virtual void display()
	{
		cout << "买半价票" << endl;
	}
};


int main() 
{
	person p;
	student s;
	//普通调用
	p.display();
	s.display();
//普通调用与对象类型有关
	system("pause");

}

多态调用:

class person
{
public:
	virtual void display()
	{
		cout << "买全价价票" << endl;
	}

};

class student:public person
{
public:
	virtual void display()
	{
		cout << "买半价票" << endl;  //重写父类虚函数
	}
};
void fun(person & p)//void fun(person *p)
{
   //多态调用:与对象有关,指向哪个对象,就调用哪个对象的函数
   //多态调用的条件:1.虚函数的重写。2.p必须是父类的引用或者指针
	//此为多态调用

	p.display();
}


int main() 
{
	person p;
	student s;
	fun(p);
	fun(s);       // 传s时完成了切片

	system("pause");

}


虚函数表(虚表)—多态原理

先看以下这个代码

class person
{
public:
	virtual void display()
	{
		cout << "买全价价票" << endl;
	}

};

class student:public person
{
public:
	virtual void display()
	{
		cout << "买半价票" << endl;
	}
};
void fun(person & p)//void fun(person *p)
{
	//此为多态调用

	p.display();
}


int main() 
{
	person p;
	student s;
	fun(p);
	fun(s);
	cout << sizeof(p) << endl;   //打印结果是?为什么?
	cout << sizeof(s) << endl;


	system("pause");

}

在这里插入图片描述

与设想的应该打印出1(一字节)不同,原因--------虚表指针

编译器会为包含虚函数的类加上以一个成员变量,这个成员变量就是一个指向该虚函数表的指针,称为虚表指针,也就是说如果一个类有虚函数,那么这个类的对象都含有虚表指针,虚表指针指向虚表

class person
{
public:
	virtual void display()
	{
		cout << "买全价价票" << endl;
	}
	virtual void fun()
	{
		cout << "this is base virtual fun" << endl;
	}

};

class student:public person
{
public:
	virtual void display()
	{
		cout << "买半价票" << endl;
	}
	virtual void fun()
	{
		cout << "this is devired virtual fun" << endl;
	}
};
void fun(person & p)//void fun(person *p)
{
	//此为多态调用

	p.display();
}


int main() 
{
	person p;
	student s;
	


	system("pause");

}


在这里插入图片描述

虚函数表底层:虚函数表存放在哪里?首先不可能在栈,因为,栈一般用来发生函数栈帧,也不可能在堆,堆一般用来动态开辟,那么只能在静态区,或者常量区,一般放在静态区,因为这张表是一个类的所有对象所共享的。
在这里插入图片描述

虚函数在虚表中存放的位置按基类先后声明的顺序存放。
在这里插入图片描述

单继承中子类与父类都有自己的虚表,里面分别存放着自己的虚函数,若子类,同时,子类继承了父类,但是会覆盖掉父类的同名函数,只留一份。
在这里插入图片描述

多继承的虚表。
默认多继承子类的虚函数放在第一个继承的类中,即多继承子类的虚表:存放在先继承的第一个父类的虚函数表中,同名的会发生覆盖留下一份
在这里插入图片描述

协变—特殊的虚函数重写

  • 虚函数重写要求参数,函数名,返回值都相同,但是,协变例外,协变就是参数,函数名都相同,返回值可以不同,但返回值必须是父子类的指针或者引用。
  • 派生类的重写的虚函数可以不写virtual。
  • 只有成员函数才可以定义才虚函数
//协变
class person
{
public:
	virtual person& display()
	{
		cout << "买全价价票" << endl;
	}
	

};

class student:public person
{
public:
	virtual student & display()
	{
		cout << "买半价票" << endl;
	}
	
};
  • 静态成员函数不能是虚函数(因为静态成员没有this指针,不属于某个对象,属于全局,所以就没有虚表指针,也就没有虚表了),
  • 构造函数、拷贝构造不能是虚函数(原因:它是初始化虚表指针,编译完成后为虚表指针开辟了空间,构造函数除了初始化成员,还会初始化虚表指针,即让它指向虚表;)
  • operator=最好不要定义成虚函数,它们的参数不同,比如父类里面传的是父类的对象 ,子类里面传的是子类的对象,没有构成重写,没有意义。
  • 析构函数最好定义成虚函数(特殊情况使用,假如没有定义成虚函数,则不构成重写,就不能多态调用,只能以类型调用。比如子类在堆上开辟了空间,父类却没有,析构函数没有形成多态,就会调用父类的析构,就不会进行资源的清理;另外,派生类和基类的析构函数名称不一样,但构成覆盖,这是因为编译器做了特殊的处理)
    情景如下:
int main()
{
person *p = new student;   //开空间,调用子类析构,若子类中有新增的内容
delete p;  //调用基类的析构,无法析构子类新增内容,从而造成资源泄漏。
return 0;
}
  • 内联函数不能定义成虚函数(因为内联函数可以说是没有地址)

抽象类—包含纯虚函数的类

在虚函数的形参列表后面写上0,则成员函数为纯虚函数。包含纯虚函数的类叫抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在派生类中重新定义以后,派生类才能实例化出 对象

class person
{
public:
	virtual void  display()=0    //纯虚函数
	{
		cout << "买全价价票" << endl;
	}
	

};
int main() 
{
	person p1,p2;  //抽象类不能实例化出对象
    system("pause");

}



但是它的派生类可以实例化出对象

class person
{
public:
	virtual void  display()=0  ;  //纯虚函数
	
	

};

class student:public person
{
public:
	virtual void  display()             //派生类必须重写,不重写的话,子类也就是抽象类,不能实例化出对象
	{
		cout << "买半价票" << endl;
	}
	
};

int main()
{
student s1;   //可以实例化对象
return0;
}

既然抽象类完成了虚函数,那么就可以实现多态调用

class person
{
public:
	virtual void display() = 0;   //纯虚函数
	
	

};

class student1:public person
{
public:
	virtual void display()
	{
		cout << "买半价票" << endl;
	}
	
};
class student2 :public person
{
public:

	virtual void display()
	{
		cout << "我也买半价票" << endl;
	}
};

void fun(person &p)  //void fun(person *p)
{
	//此为多态调用

	p.display();
}


int main() 
{
	student1 s1;
	student2 s2;
	fun(s1);
	fun(s2);//用引用

	
	//fun(&s1);  //用指针
	//fun(&s2);


	system("pause");

}


问题:
什么是函数重载,同名隐藏,重写?

哪些函数不定义成虚函数?为什么?

  1. 不能被继承的函数
  2. 不能被重写的函数(普通函数,友元函数,构造函数,内联函数,静态成员函数)
静态绑定和动态绑定(了解)

静态绑定:发生在编译期间,简单来说就是普通调用
动态绑定:发生在运行期间,简单来说就是多态调用

通常,虚函数是动态绑定,非虚函数是静态绑定

查看虚表地址 和虚表里面的虚函数地址,及调用虚函数

https://blog.csdn.net/liu_zhen_kai/article/details/82827890

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值