C++虚基类、虚函数、纯虚函数与抽象基类区分

#C++虚基类、虚函数、纯虚函数与抽象基类
在这里插入图片描述
来啦来啦,今天说一下在学习C++时困扰我好久的几个概念,并做一下简单的讲解(Java的内容在最下面)。

C++的一个最重要特点就是继承,关于继承有很多概念,如虚基类、虚函数、纯虚函数和抽象基类。这些概念容易混淆,以下我做一些简单的区分,可能有些地方说明不够充分,望见谅。

虚基类与虚函数容易混淆,虽然同是在继承环境下,但是毫无关系。虚基类
主要是在多重继承时使用。内容比较复杂,初学者只需要掌握一点就可以
有需要的可以查看其他资料。
| 如果子类C同时继承A与B,那么就会将父类的成员变量继承两遍,
|这个时候在继承时需要加上virtual,此时相当于只继承了父类的一个变量
虚基类使用场景

然后要说的就是虚函数的用法,这牵扯到多态性,它分为两大类

静态多态性(发生在编译时)
包括:函数重载,运算符重载以及模板。
在编译时同名的函数经过编译,不同函数被赋予不同的意义(系统自动操控,通过一些方法如修改名字来识别,对我们不可见),而在运行时多态就不存在了,同名函数在调用时已经可以被系统区分。

动态多态性(发生在运行时)
在运行时发生编译(因此也会增加运行的时间和内存开销),只有被调用才编译,一般而言,如果基类的成员函数会被子类覆盖就将其声明为虚函数。如果不加,则在调用子类继承父类的函数时,如果该函数也含有子类与父类同时具有的函数,执行的内容可能就是父类的该函数的内容(在编译时采用了就近原则)。

#include<iostream>
#include<string>
using namespace std;
const double pi = 3.14;
class circle
{
public:
	circle(double r) :radius(r) {}
	double area() { return pi * radius*radius; }
	void area_message(string message) { cout << message <<"'s area is"<< area() << endl; }
protected:
	double radius;
};
class cylinder :public circle
{
public:
	cylinder(double r, double l) :circle(r), length(l) {}
	double area() { return 2 * pi*radius*(radius + length); }
private:
	double length;
};
int main()
{
	cylinder p(2, 3);
	p.area_message("a_cylinder");
}

简单说明一下,定义了一个圆类,由于成员函数实现比较简单,就全部设为了内联函数,书写比较方便。 circle类中定义了一个area()类用于计算圆的面积,area_message()用于输出圆的信息。定义了一个子类cylinder()继承circle类,其中也有area()函数覆盖了父类的area()和一个area_message()函数继承的父类,那么在执行时调用的是哪一个area()呢,是父类的还是子类呢,当然我们希望是子类的,运行一下,我们观察一下结果。
运行结果
通过计算可以发现调用的是父类的area(),这是因为在编译时编译器采用就近原则,发现需要调用area()时就选取了最近的父类的area()。如何解决呢。虚函数就出现了,我们只需要做一个小小的改动,在父类的area()前面加上关键字virtual 让他成为虚函数

virtual double area() { return pi * radius*radius; }

再运行结果就是我们要的啦。
在这里插入图片描述
虚函数的应用场景也就是这样,在父类上加上virtual子类继承后运行时才进行编译,虽然耗费了时间,但是这才是我们的需要。同时要注意,如果子类继承的是虚函数,那么在子类该函数仍为虚函数,所以子类写double area()与virtual double area()是一个概念。当然,你可以偷个懒,还有个地方可以偷懒哦,virtual只要在函数声明时加就可以,定义时可以不加。

开始下一个啦

特别要声明的是,虚函数与抽象函数不同,如果类中有虚函数,对该类其他函数以及类都没有影响,父类仍然可以调用虚函数只是对子类继承后编译发生的时间进行改变。

纯虚函数和抽象基类的关系:如果类中有纯虚函数那么该类为抽象基类。
抽象基类不能用来创建对象,一般用来创建子类对象。
纯虚函数,虚函数,看名字就知道纯虚函数时虚函数的升级版,但是功能相差还是比较大的。

下面是一个工人类
Worker作为基类
AWorker是固定工资 salary
BWorker工资是日结 salary=天数*一日工资
CWorker是白领 salary=福利+月工资

#include<iostream>
#include<string>
#include<iomanip>
using namespace std;
class Worker
{
public:
	Worker()
	{
		cin >> name;
	}
	virtual void display_information() =0;
	virtual double get_salary() =0;//工人类
	//void x() { cout << "x"<<endl; } 可以存在不是纯虚函数的函数
protected:
	string name;
};
class AWorker:public Worker//工人种类A
{
public:
	AWorker()
	{
		cin >> salary;
	}
	void display_information()
	{
		cout <<name << ' ' << salary<<endl;
	}
	double get_salary()
	{
		return salary;
	}
private:
	double salary;
};
class BWorker :public Worker//工人种类B
{
public:
	BWorker()
	{
		int n;
		double m;
		cin >> n >> m;
		salary=n*m;
	}
	void display_information()
	{
		cout <<name << ' ' << salary<<endl;
	}
	double get_salary()
	{
		return salary;
	}
private:
	double salary;
};
class CWorker :public Worker//工人种类C
{
public:
	CWorker()
	{
		double n;
		double m;
		cin >> n >> m;
		salary = n + m;
	}
	void display_information()
	{
		cout << name << ' ' << salary<<endl;
	}
	double get_salary()
	{
		return salary;
	}
private:
	double salary;
};
int main()
{
	int n;
	cin >> n;
	char ch;
	double sum=0;
	Worker *p=NULL;//赋值NULL,防止在下面可能存在未对p赋值而引用
	               //安全!!去掉就不对,会报错
				   //若不加NULL,则在每次确定会分配后才能delete p;
				   //否则删除的是一个野指针(p中未分配地址前的一个未知地址)
	//Worker q; 抽象基类不能创建对象 
	for (int i = 1; i <= n; i++)//根据输入的工人种类创建对象
	{
		cin >> ch;
		if (ch >= 'A'&&ch <= 'C')
		{
			if (ch == 'A')
				p = new AWorker;
			else if (ch == 'B')
				p = new BWorker;
			else if (ch == 'C')
				p = new CWorker;
			p->display_information();
			sum += p->get_salary();
			//p->x();
			delete p;
		}
		else
			cout << "error";
	}
	cout << fixed<<setprecision(2)<<sum / n;
}

注意!!

1.含有纯虚函数的类叫做抽象基类,子类继承后所有的纯虚函数都要实现,并且子类对象可以由父类(抽象基类)来创建。
2.抽象基类对象不可以被创建。
3.抽象基类可以含有不是纯虚函数的函数,子类继承后可以直接使用而不用进行覆盖。

那为什么要用纯虚函数而不用虚函数呢?纯虚函数有什么优点呢?在这里插入图片描述
1.不需要对父类的函数进行实现和添加返回值。
比如,该题中virtual double get_salary(),如果不加=0,会提示你需要返回值,但是他只是一个工人类,并不知道工人种类,工资没办法计算,子类继承后才需要实现返回值,所以加上=0让它成为纯虚函数,只是为了让编译器在编译时知道这里不需要做任何东西。
又比如display_information(),我们如果定义为虚函数,我们在父类需要去实现,但是实际上我们并不需要,他只是工人的一个基本特征,需要具体的工人去实现,这时候加上=0,将它变为纯虚函数即可
2.实现对父类的一个指针定义就可以指向所有的子类对象(父类作为上转型对象)
比如该题,在主函数中,我希望根据输入的ABC来创建不同的工人对象,但是如果我不用父类,直接用子类去创建,会非常麻烦,我需要去分别定义子类对象,造成代码冗余。使用父类指针可以避免代码冗余
举个例子,你会更好的理解,比如现在的王者荣耀游戏,所有的英雄都可以使用一个抽象基类叫Hero类(一定会用的),那么如果一个英雄攻击一个英雄需要一个函数来实现,那么如果没有抽象基类怎么办?比如其中一个英雄的函数void attack(huamulan a),那么都多少英雄就需要多少个这样的函数进行重载,不同的只是参数里面的英雄类不同,而如果使用抽象基类,我们只需要一个void attack(Hero a),由于Hero可以创建子类对象,所以传参的时候Hero类可以接受所有的英雄。并且在添加新的英雄后,该函数不用改变,否则就要创建新的重载函数来完成不同英雄的攻击。极其麻烦。

C++的到此就结束啦,下面是Java的,不需要的朋友可以溜啦。
在这里插入图片描述

Java中有接口,它的作用和此处抽象基类基本相同,在继承后都需要实现。不同的是Java中的接口所有的函数都必须为抽象的(public abstract),不能定义变量,只能有常量(publci static final)。

Java中还有一个概念就是抽象类,与C++也是有区别的(可以与虚函数做对比),Java中类只要含有抽象函数,该类就为抽象类,该类也不能创建对象,子类继承后也要全部重写(注意重写时一定要写上public访问符),当然也可以含有不为抽象函数的函数和变量。

Java中抽象基类相当于比较严格的抽象函数,使用比抽象函数更加广泛。

拜拜
拜拜

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值