【和春笋一起学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. 如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果只重新定义一个版本,则另外两个版本将被隐藏。
相关推荐
zmzb010320 小时前
C++课后习题训练记录Day157
开发语言·c++
从零开始的代码生活_1 天前
C++ 多态详解:虚函数、动态绑定、抽象类与虚表原理
开发语言·c++·后端·学习·算法
大彼方..1 天前
C++ STL Vector 深度剖析:从内存管理到性能优化
开发语言·c++
炸薯条!1 天前
从零开始学C++ (内存管理)
java·jvm·c++
cpp_25011 天前
P6625 [省选联考 2020 B 卷] 卡牌游戏
数据结构·c++·算法·前缀和·贪心·洛谷题解·省选
旖-旎1 天前
《LeetCode 416 分割等和子集》
c++·算法·leetcode·动态规划·背包问题
Mortalbreeze1 天前
深入理解 Linux 线程机制(四):线程同步——条件变量与信号量
linux·运维·服务器·开发语言·c++
郝学胜-神的一滴1 天前
中级OpenGL教程 020:巧用数组与循环实现多点点光源渲染,告别冗余代码重构方案
c++·unity·游戏引擎·godot·图形渲染·unreal
zmzb01031 天前
C++课后习题训练记录Day160
开发语言·c++
沫璃染墨1 天前
现代C++⊂C++11篇(一)列表初始化全解 & std::initializer_list
开发语言·c++