IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++关键字virtual的用法 -> 正文阅读

[C++知识库]c++关键字virtual的用法

一、基础概念

我们知道,函数重载,运算符重载,模板属于编译时的多态形式,而虚函数则是运行时的多态形式。因此,在这里我们先来介绍一下什么时静态联编和动态联编

静态联编

函数的重载,运算符的重载在编译器编译代码时,根据参数列表的匹配关系,就可以确定下来具体调用的函数,也就是在程序执行之前,就已经确定了函数调用关系。这属于静态联编
参看如下代码:

#include<iostream>
using namespace std;
class Base
{
public:
	void fun()
	{
		cout << "Base" << endl;
	}
};
class Drive :public Base
{
public:
	void fun()//派生类函数覆盖
	{
		cout << "Drive" << endl;
	}
	void fun(int a)//派生类中函数的重载
	{
		cout << "Drive(a)" << endl;
	}
};
int main()
{
	Base base;
	Drive drive;
	drive.fun();//调用覆盖函数
	drive.fun(5);//调用重载函数
	drive.Base::fun();//调用继承的函数
	return 0;
}
/*
运行结果如下:
Drive
Drive(5)
Base
分析结果如下:
在派生类中有三个重名的函数fun(),通过派生类的对象drive调用函数时,
编译器在编译阶段根据对象的this指针关联相应的函数。但是派生类中只有
重载的函数fun(5)而没有覆盖函数fun()时,则会执行drive.fun()时,会因为
找不到相匹配的参数而在编译期间报错。
*/

动态联编

程序中函数调用与函数体代码之间的关联需要推迟到运行阶段才能确定,就是动态联编

二、virtual在c++多态中的用法(虚函数)

在继承的类体系中,基类定义虚函数,派生类可自定义虚函数的不同实现版本,当程序用基类的指针或者引用调用该虚函数时将引发动态联编系统会在运行阶段根据基类指针具体指向的对象调用该对象所属类的虚函数版本,从而实现运行时的多态。
参看如下代码:

#include<iostream>
using namespace std;
class Base
{
public:
	void fun()
	{
		cout << "Base" << endl;
	}
};
class Drive :public Base
{
public:
	void fun()
	{
		cout << "Drive" << endl;
	}
};
void f(Base *p)//基类指针p可以指向基类和派生类
{
	p->fun();//基类指针调用函数

}
int main()
{
	Base base;
	Drive drive;
	f(&base);//调用基类的函数
	f(&drive);//派生类对象地址传递给基类指针,实现类型转换
	return 0;
}
/*
运行结果如下:
Base
Base
分析结果如下:
注意,这里有的人会以为输出结果为Base,Drive,其实不是这样的。虽然派生类重新定义了函数,
但是,当实参是派生类对象drive的地址时,基类指针p指向了派生类对象,在f()函数中通过基类指针
调用的依然是基类的函数,并不能根据传递对象不同调用不同的函数,这是因为系统在编译阶段根据
基类指针类型已经确定了调用的时基类的函数,实现的是静态多态。
所以必须使用虚函数机制来确定运行时对象的类型并调用合适的成员函数。即将基类的函数声明为虚函数,
实现多态联编。
这里随便说一句,在visualc++MFC中大量使用在派生类中重新定义虚函数的行为,以实现动态联编。
*/
#include<iostream>
using namespace std;
class Base
{
public:
	virtual void fun()
	{
		cout << "Base" << endl;
	}
};
class Drive :public Base
{
public:
	virtual void fun()//virtual可以省略
	{
		cout << "Drive" << endl;
	}
};
void f(Base *p)//基类指针p可以指向基类和派生类
{
	p->fun();//基类指针调用虚函数

}
int main()
{
	Base base,*basePoint;
	Drive drive,*drivePoint;
	f(&base);//调用基类的函数
	f(&drive);//派生类对象地址传递给基类指针,实现类型转换

	basePoint=new Base;//建立动态基类对象,运行时才能确定对象的地址
	f(basePoint);
	delete basePoint;

	drivePoint = new Drive;//建立动态基类对象,运行时才能确定对象的地址
	f(drivePoint);
	delete drivePoint;
	
	return 0;
}
/*
运行结果如下:
Base
Drive
Base
Drive
分析结果如下:
当用基类指针调用虚函数时,将引发动态联编,系统将在运行阶段根据基类指针所指向
的对象类型调用相应的虚函数。
当程序分配了动态对象时,利用基类指针指向不同的动态对象,从而调用不同的虚函数。
需要注意的是:
虚函数作为类中一种特殊的成员函数,是实现动态多态的关键,而且必须用基类指针或引用
调用类层次中的不同实现版本。如果不要基类指针或引用,而是用类的对象直接调用,则不会
引发多态。
*/

虚函数的注意事项:

  1. 虚函数必须是类的成员函数,不能是全局函数或者静态成员函数。
  2. 在派生类中重定义一个虚函数是,必须与基类的虚函数原型完全相同,否则其虚函数特性将丢失而成为普通的重载函数。
  3. 虚函数实现运行时多态的关键之处在于必须使用基类指针或引用调用虚函数。
  4. 一旦一个函数被声明为虚函数,其将保持虚函数特性,而不管其经历了多少层派生。

三、virtual在c++继承中的用法(虚继承)

在多继承中,如果多条继承路径上有一个公共的基类,则在这些路径的汇合点,便会产生来自不同路径的公共基类的数据成员的多份副本。如果想要只保留一份副本,就必须使用关键字virtual把这个公共基类定义为虚基类,这种继承被称为虚继承。虚继承使得最终的派生类对象中只保留公共基类(虚基类)的一份数据成员,避免了二义性问题。
eg:菱形继承就可以用虚基类(虚继承)来解决二义性问题。

四、virtual在析构函数中的用法(虚析构)

类中的析构函数可以声明为虚函数,即虚析构函数。
当用一个基类指针指向一个动态申请的派生类对象时,就需要用虚析构函数正确释放派生类对象。
注意:
构造函数不能是虚函数,原因是在建立一个派生类对象时必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数,不能在选择性地调用构造函数。

#include<iostream>
using namespace std;
class Base
{
public:
	 ~Base()
	{
		cout << "Base::~Base is called" << endl;
	}
};
class Drive :public Base
{
public:
	 ~Drive()
	{
		cout << "Drive::~Drive called" << endl;
	}
};
int main()
{
	Base *basePoint=new Drive;
	Drive *drivePoint=new Drive;
	cout << "delete first object" << endl;
	delete basePoint;
	cout << "delete second object" << endl;
	delete drivePoint;
	return 0;
}
/*运行结果如下:
delete first object
Base::~Base is called
delete second object
Drive::~Drive called
Base::~Base is called
结果分析:
由运行结果可以知道,drivePoint指向派生类的对象被正确释放,但是
basePoint指向派生类的对象在释放时只调用了基类的析构函数,而正确
的析构应该是先调用派生类的构造函数再调用基类的构造函数。
*/

注意比较这两段代码的不同。

#include<iostream>
using namespace std;
class Base
{
public:
	 virtual ~Base()
	{
		cout << "Base::~Base is called" << endl;
	}
};
class Drive :public Base
{
public:
	virtual ~Drive()
	{
		cout << "Drive::~Drive called" << endl;
	}
};
int main()
{
	Base *basePoint=new Drive;
	Drive *drivePoint=new Drive;
	cout << "delete first object" << endl;
	delete basePoint;
	cout << "delete second object" << endl;
	delete drivePoint;
	return 0;
}
/*运行结果如下:
delete first object
Drive::~Drive called
Base::~Base is called
delete second object
Drive::~Drive called
Base::~Base is called
结果分析:
由运行结果可以知道,当基类的析构函数被声明为虚析构函数后,派生类的析构
函数也自动成为虚析构函数,basePoint指向派生类的对象在释放时先调用派生类
的构造函数再调用基类的构造函数,正确释放了对象。
*/

五、纯虚函数与抽象类

在上述二、virtual在c++多态中的用法时我们已经具体阐述了虚函数。接下来要叙述的是纯虚函数。
在许多情况下,在基类中并不能给出有意义的虚函数定义,这是就可以把它声明纯虚函数,即不需要具体定义函数的函数体,而把它的实现留给派生类来实现。
在基类定义虚函数的函数首部加上"=0",就是定义纯虚函数的语句。
含有纯虚函数的类我们称为抽象类,抽象类不能生成对象。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出,如果派生类没有定义纯虚函数而只是继承基类的纯虚函数,那么这个派生类仍然还是一个抽象类,不能够生成对象。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-12 12:59:34  更:2021-09-12 13:00:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 1:53:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码