【和春笋一起学C++】(六十三)虚函数特性(二)

虚函数的基本特性:

  • 在基类函数的声明中使用关键字virtual可使该函数在基类以及所有的派生类中是虚拟的;
  • 使用指向对象的引用或指针来调用虚函数,则程序将使用对象类型定义的方法,而不是使用指针或引用类型定义的方法;
  • 如果定义的类将被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚拟的;

目录

[1. 构造函数](#1. 构造函数)

[2. 析构函数](#2. 析构函数)

[3. 友元函数](#3. 友元函数)

[4. 虚函数没有重新定义](#4. 虚函数没有重新定义)

[5. 重新定义基类虚函数](#5. 重新定义基类虚函数)


1. 构造函数

基类的构造函数不能是虚拟的。

在创建派生类对象时,首先调用派生类的构造函数,然后派生类的构造函数再使用基类的一个构造函数初始化基类中数据成员,这种调用方式不同于继承机制,因此,派生类不继承基类的构造函数。所以将基类构造函数声明为虚拟的是没有意义的。

2. 析构函数

析构函数应当是虚函数,除非类不用作基类。假设在《类继承》文章中的CulturalStudent类中添加一个char*成员,代表了学生想要考取的意向学校,如下:

cpp 复制代码
class CulturalStudent :public Student
{
private:
	unsigned short lishi_score;
	unsigned short zhengzhi_score;
	
public:
	CulturalStudent(string name_, unsigned short yw, unsigned short sx, unsigned short yy,
		unsigned short ls, unsigned short zz);
	CulturalStudent(const Student& st, unsigned short ls, unsigned short zz);
	virtual ~CulturalStudent();
	unsigned short get_lishi_score() { return lishi_score; }
	unsigned short get_zhengzhi_score() { return zhengzhi_score; }
	virtual void show_score()
	{
		cout << "姓名:" << get_name() << endl;
		cout << "意向学校:" << target_schools << endl;
		Student::show_score();
		cout << "历史分数:" << lishi_score << endl;
		cout << "政治分数:" << zhengzhi_score << endl;
	}
	virtual unsigned short GetMean();
	char* target_schools;///意向学校
};

方法定义如下:

cpp 复制代码
CulturalStudent::CulturalStudent(string name_, unsigned short yw, unsigned short sx, unsigned short yy,
	unsigned short ls, unsigned short zz):Student(name_,yw,sx,yy)
{
	target_schools = new char[128];
	memset(target_schools, 0, 128 * sizeof(char));
	const char *temp = "中山大学";
	strcpy(target_schools, temp);
	lishi_score = ls;
	zhengzhi_score = zz;
}

CulturalStudent::CulturalStudent(const Student& st, unsigned short ls, unsigned short zz):Student(st)
{
	target_schools = new char[128];
	memset(target_schools, 0, 128 * sizeof(char));
	const char *temp = "中山大学";
	strcpy(target_schools, temp);
	lishi_score = ls;
	zhengzhi_score = zz;
}

CulturalStudent::~CulturalStudent()
{
	delete[]target_schools;
	target_schools = NULL;
}

调用程序如下:

cpp 复制代码
Student *st2 = new CulturalStudent("xiaoming", 82, 99, 84, 90, 91);
st2->show_score();
cout << "mean: " << st2->GetMean() << endl;;
delete[] st2;

如果析构函数没有设置为虚拟的,那么使用delete[] st2语句将只调用~Student()析构函数,只释放CulturalStudent对象中基类部分指向的内存,但不会释放CulturalStudent类中新成员指向的内存,因此,target_schools,lishi_score,zhengzhi_score数据成员所占据的内存空间没有被释放。但如果基类的析构函数设置为虚拟的,则delete[] st2语句将先调用派生类析构函数释放派生类中数据成员占据的内存空间,然后调用基类析构函数释放基类数据成员占据的内存空间。

即使基类不需要显式析构函数,也不应依赖于默认析构函数,而应提供虚析构函数,即使它不执行任何操作。即使一个类不用作基类,给这个类定义一个虚析构函数也不会发生错误,它只是效率层面的问题。

3. 友元函数

友元函数不能是虚函数,因为友元不是类成员,只有类成员才能是虚函数。

4. 虚函数没有重新定义

如果基类中的虚函数在派生类中没有被重新定义,则将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本。

5. 重新定义基类虚函数

如果在派生类中定义了基类的同名但不同函数特征标的函数,如下所示:

cpp 复制代码
class Student
{
public:
	virtual void show_score()
	{
		cout << "语文分数:" << yuwen_score << endl;
		cout << "数学分数:" << shuxue_score << endl;
		cout << "英语分数:" << yingyu_score << endl;
	}
	virtual unsigned short GetMean();
private:
	std::string name;
	unsigned short yuwen_score;
	unsigned short shuxue_score;
	unsigned short yingyu_score;
};

class CulturalStudent :public Student
{
private:
	...
public:
	virtual void show_score(int a)
	{
		cout << "test a=" << a << endl;
		cout << "姓名:" << get_name() << endl;
		cout << "意向学校:" << target_schools << endl;
		Student::show_score();
		cout << "历史分数:" << lishi_score << endl;
		cout << "政治分数:" << zhengzhi_score << endl;
	}
	virtual unsigned short GetMean();
};

执行以下语句

cpp 复制代码
Student *st2 = new CulturalStudent("xiaoming", 82, 99, 84, 90, 91);
st2->show_score();///派生类中show_score函数的特征标与基类的show_score函数不同,无法实现虚函数的特性,将调用基类的show_score函数
st2->show_score(5);///语法错误,编译无法通过
cout << "mean: " << st2->GetMean() << endl;
delete[] st2;

CulturalStudent test= CulturalStudent("xiaohong", 85, 90, 84, 90, 91);
test.show_score(5);///将调用派生类的show_score函数

如果在派生类中重新定义了函数,则将隐藏同名的基类的方法。

重新定义基类中的虚函数,有以下几点需要注意:

  1. 如果派生类中重新定义基类的方法,应确保与基类方法的原型完全相同。
  2. 针对第1条规则,有一种情况是特例,如果基类方法的返回类型是基类引用或指针,则派生类中的返回类型可以修改为指向派生类的引用或指针,这种特性称为返回类型协变。(注:这种例外只适用于返回值,不适用于参数)
  3. 如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果只重新定义一个版本,则另外两个版本将被隐藏。
相关推荐
历程里程碑2 小时前
MySQL事务深度解析:ACID到MVCC实战+万字长文解析
开发语言·数据结构·数据库·c++·sql·mysql·排序算法
鲸渔2 小时前
【C++ 跳转语句】break、continue、goto 与 return
开发语言·c++·算法
syker3 小时前
AIFerric v2.0 项目总结报告
c语言·开发语言·c++
ShineWinsu3 小时前
对于Linux:进程间通信IPC(命名管道)的解析
linux·c++·面试·笔试·进程·ipc·命名管道
️是783 小时前
信息奥赛一本通—编程启蒙(3371:【例64.2】 生日相同)
开发语言·c++·算法
张小姐的猫4 小时前
【Linux】进程信号(质变)—— 信号捕捉 | 中断 | 内核态
linux·运维·服务器·c++
佩洛君4 小时前
如何在Ubuntu22.04中安装ROS2-Humble
c++·python·ros2
Xiu Yan4 小时前
Java 转 C++ 系列:函数对象、谓词和内建函数对象
java·开发语言·c++
炘爚5 小时前
C++实现分布式集群聊天服务器
服务器·c++·分布式