C++学习(六)类的继承&&虚函数

1.类的继承

类继承:从已有的类派生出新的类,而派生类继承原有的类的(基类)的特征,包括方法。
通过类继承可以完成:(1)在已有的类的基础上添加新的功能。
(2)可以给类添加数据;
(3)可以修改类方法的行为。

//基类
class TableTennisPlayer
{
private:
string firstname;
string lastname;
bool hasTable;
public:
TableTennisPlayer(const string &fn = " none", const string &ln = " none", bool ht = false);
void Name() const;
bool HasTable() const { return hasTable; };
void ResetTable(bool v)
{
	hasTable = v;
}
}
//派生类继承

class RatedPlayer :public TableTennisPlayer
{


};

派生类构造函数的一般形式为:
派生类构造函数名(总参数表):基类构造函数名(参数表)

{

  派生类中新增加数据成员初始化语句

}

RatedPlayer::RatedPlayer(unsigned int r, const string &fn,
const string &ln, bool ht) :TableTennisPlayer(fn, ln, ht)
{
	rating = r;
}

在建立一个对象时,执行构造函数的顺序是:

a.派生类构造函数先调用基类构造函数;

b.再执行派生类构造函数本身(即派生类构造函数的函数体)

而在派生类对象释放时,先执行派生类析构函数,再执行其基类析构函数
总结:继承中派生类可以添加特性,所以叫这种关系为is-a关系更准确。
不能被继承:

  • 构造函数
  • 析构函数
  • 赋值运算符

2.虚函数

建议参考皓神https://blog.csdn.net/haoel/article/details/1948051
定义:简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。
虚函数的作用:C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

#include <iostream>
#include <string>
using namespace std;
//声明基类Student
class Student
{
public:
Student(int, string,float);  //声明构造函数
void display( );//声明输出函数
protected:  //受保护成员,派生类可以访问
 int num;
 string name;
 float score;
};
//Student类成员函数的实现
Student::Student(int n, string nam,float s)//定义构造函数
{
  num=n;
 name=nam;
 score=s;
}
void Student::display( )//定义输出函数
{
  cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\n\n";
}
//声明公用派生类Graduate
class Graduate:public Student
{
public:
 Graduate(int, string, float, float);//声明构造函数
	void display( );//声明输出函数
private:float pay;
};
// Graduate类成员函数的实现
void Graduate::display( )//定义输出函数
	{
   cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;
}
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){}
//主函数
int main()
{
   Student stud1(1001,"Li",87.5);//定义Student类对象stud1
   Graduate grad1(2001,"Wang",98.5,563.5);//定义Graduate类对象grad1
   Student *pt=&stud1;//定义指向基类对象的指针变量pt
   pt->display( );
   pt=&grad1;
   pt->display( );
   return 0;
}

在上面 程序中假如想输出grad1的全部数据成员,当然也可以采用这样的方法:通过对象名调用display函数,如grad1.display(),或者定义一个指向Graduate类对象的指针变量ptr,然后使ptr指向gradl,再用ptr->display()调用。这当然是可以的,但是如果该基类有多个派生类,每个派生类又产生新的派生类,形成了同一基类的类族。每个派生类都有同名函数display,在程序中要调用同一类族中不同类的同名函数,就要定义多个指向各派生类的指针变量。这两种办法都不方便,它要求在调用不同派生类的同名函数时采用不同的调用方式,正如同前面所说的那样,到不同的目的地要乘坐指定的不同的公交车,一一 对应,不能搞错。如果能够用同一种方式去调用同一类族中不同类的所有的同名函数,那就好了。
虚函数的使用
(1)
用虚函数就能顺利地解决这个问题。下面对程序作一点修改,在Student类中声明display函数时,在最左面加一个关键字virtual,即

virtual void display( );

在main中pt是同一个基类指针,可以调用同一类族中不同类的虚函数。这就是多态性,对同一消息,不同对象有 不同的响应方式。

  • 说明:本来基类指针是用来指向基类对象的,如果用它指向派生类对象,则进行指针类型转换,将派生类对象的指针先转换为基类的指针,所以基类指针指向的是派生类对象中的基类部分。在程序修改前,是无法通过基类指针去调用派生类对象中的成员函数的。虚函数突破了这一限制,在派生类的基类部分中,派生类的虚函数取代了基类原来的虚函数,因此在使基类指针指向派生类对象后,调用虚函数时就调用了派生类的虚函数。 要注意的是,只有用virtual声明了虚函数后才具有以上作用。如果不声明为虚函数,企图通过基类指针调用派生类的非虚函数是不行的。

(2)由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。

(3)虚函数的使用方法是:
在基类用virtual声明成员函数为虚函数。
这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。虚函数处理可以理解为纵向重载。但与重载不同的是:同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)。

2.1 虚函数原理

普及知识:联编是指一个计算机程序的不同部分彼此关联的过程。
静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。
动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低
在这里插入图片描述

在这个类的设计中,只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

在这里插入图片描述

原理:虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

2.2纯虚函数vs虚函数

#include<iostream>
using namespace std;
class Virtualbase
{
public:
	virtual void Demon() = 0;//纯虚函数
	virtual void Base() { cout << "this is his father class" << endl; }

};
class  SubVirtual:public Virtualbase
{
	void Demon()
	{
		cout << "this is SubVirtual!" << endl;
	}
	void Base()
	{

		cout << "this is subclass Base" << endl;

	}
};

void main()

{

	Virtualbase* inst = new SubVirtual(); //multstate pointer

	inst->Demon();

	inst->Base();

	system("pause");

	return;

}

(1)纯虚函数的定义形式:virtual { } = 0;
(2)纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。如果不实现,编译器将报错,错误提示为:

error LNK****: unresolved external symbol "public: virtual void __thiscall
ClassName::virtualFunctionName(void)"

(3)虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class) 只有声明而没有定义。
(4)虚函数在子类里面也可以不重载的;但纯虚函数必须在子类去实现,这就像Java的接口一样。通常把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为很难预料到父类里面的这个函数不在子类里面不去修改它的实现。
(5)虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然大家也可以完成自己的实现。纯虚函数关注的是接口的统一性,实现由子类完成。
(6)带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值