c++复盘笔记8(多态)


#重载运算符
使同一个运算符用于不同的数据的时候有不同的行为
c++本来就已经重载了运算符了 比如+
可以int+int double+double float+float …
但是这些功能远远不能满足我们的需求 所以就要求我们还要;重载运算符
重载运算符的语法:
返回值类型 opetator (参数1,参数2);// 如果重载运算符的函数时类的成员就只要写一个参数 还有一个参数是该类的对象

c++中定义了的运算符除了下面五种运算符之外都可以重载
1、 . :;成员选择符
2、. * :成员指针运算符
3、 ? :三目运算符
4、:: :作用域说明符
5、 sizeof :计算数据大小的运算符

运算符重载的原则:
1、;重载后运算符的优先级和结核性不会改变
2、不能改变运算符的操作数的个数
3、不能重载c++中没有的运算符
4、不能改变运算符的原有语义(例如+ 原本语义是实现数据的相加,重载的时候不能让其进行减运算

示例:重载->指针运算符

//重载成员指针运算符能确保指向对象的指针都指向有意义的对象(有效地址) 即创建一个"智能指针" 否则返回错误信息 这样避免了对空指针或者垃圾类容指针内容的存取
#include<iostream>
using namespace std;
class Complex{//复数类
private:
double real;//实部
double image;//虚部
Complex(double real=0.0,double image=0.0)
{
this->real=real;
this->image=image;
}
void show()
{
cout<<"("<<real<<","<<image<<")"<<endl;
}
};
class PComplex{//复数指针类的定义
private :
Complex*P;
public:
PComplex(Complex*p=NULL)
{
P=p;
}

Complex* operator ->()
{
static Complex nullcomplex(,);//防止指针为空
if(PC==NULL)
return &nullcomplex;
else
return PC;

}
};
int main()
{
PComplex p;//未初始化的指针
p->display();//(0,0)
Complex c(10,20);
p1=&c;
p1->display();//(100,200)
return 0;
}

注意:
被重载的运算符只能是c++中定义了的运算符 在c++中定义了的运算符中除了

虚函数

虚函数就是在类的成员函数定义的时候在最前面加一个关键字 virtual

注意:
1、虚函数不能是静态函数或者友元函数 因为静态函数和友元函数不属于某个对象
2、内联函数是不能在运行中动态确定位置的 即使虚函数在类内定义 在编译的时候把它看成是非内联的
3、虚函数的声明出现在父类的定义中 因为虚函数仅适用于继承关系中的类对象 普通函数不能声明为虚函数
4、构造函数不能是虚函数 析构函数可以是虚函数 而且在编程的时候多把父类的析构函数声明为虚函数

为什么构造函数不能声明为虚函数:

虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚函数,就需要通过虚函数表中对应的虚函数指针(编译期间生成属于类)来调用,可对象目前还没有实例化,也即是还没有内存空间,何来的虚指针,所以构造函数不能是虚函数;
虚函数的作用在于通过父类的指针或者引用来调用它的成员函数的时候,能够根据动态类型来调用子类相应的成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,所以构造函数不能是虚函数;

静态联编和动态联编

1、静态联编
静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。

2、动态联编
动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。

动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)
实现动态联编需要同时满足以下三个条件: ① 必须把动态联编的行为定义为类的虚函数。 ②
类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来。 ③
必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。

声明:上面的引用部分摘自:https://blog.csdn.net/appleyuchi/article/details/118599541

下面用代码演示一下:

#include<iostream>
using namespace std;
class D {
public:
	void show()
	{
		cout << "D::show()";
	}
};
class A :public  D
{
public:
	void show()
	{
		cout << "A::show()" << endl;
	}
};
class B :public  D
{
public:
	void show()
	{
		cout << "B::show()" << endl;
	}
};
class C :public A, public B
{


};
int main()
{
	D* d = new B();//D类型的指针直线B类的对象
	D* d1 = new A();//D类型的指针指向A类的对象
	d->show();
	d1->show();
	return 0;
}

运行结果:发现都是调用的D类的成员函数 这属于静态联编
在这里插入图片描述

#include<iostream>
using namespace std;
class D {
public:
	virtual void show()//把基类的函数设置为虚函数
	{
		cout << "D::show()";
	}
};
class A :public  D
{
public:
	void show()
	{
		cout << "A::show()" << endl;
	}
};
class B :public  D
{
public:
	void show()
	{
		cout << "B::show()" << endl;
	}
};
class C :public A, public B
{


};
int main()
{
	D* d = new B();//D类型的指针直线B类的对象
	D* d1 = new A();//D类型的指针指向A类的对象
	d->show();
	d1->show();
	return 0;
}

运行结果:时调用的指针指向对象的类的成员函数 这是动态联编
在这里插入图片描述

显然静态联编只是盲目地根据指针和引用的类型来调用函数 而不是根据指针实际指向对象的类型来调用函数

动态联编则在程序运行过程中根据指针和引用实际指向的目标来调用函数
也就是在程序运行的时候才决定调用哪个函数

虚析构函数的好处

首先上代码对比一下:
没有虚析构函数:

#include<iostream>
using namespace std;
class D {
public:
	virtual void show()
	{
		cout << "D::show()";
	}
	D()
	{
		cout << "D()的调用" << endl;
	}
	~D()
	{
		cout << "~D()的调用" << endl;
	}
};
class A :  public  D
{
public:
	void show()
	{
		cout << "A::show()" << endl;
	}
	A()
	{
		cout << "A()的调用" << endl;
	}
	~A()
	{
		cout << "~A()的调用" << endl;
	}
};

int main()
{
	D* d = new A();
	delete d;
	
	return 0;
}

运行结果:子类的对象没有析构(如果子列对象中动态分配得有内存,那么就会造成内存泄露)
在这里插入图片描述

有虚析构函数:

#include<iostream>
using namespace std;
class D {
public:
	virtual void show()
	{
		cout << "D::show()";
	}
	D()
	{
		cout << "D()的调用" << endl;
	}
virtual ~D()
	{
		cout << "~D()的调用" << endl;
	}
};
class A :  public  D
{
public:
	void show()
	{
		cout << "A::show()" << endl;
	}
	A()
	{
		cout << "A()的调用" << endl;
	}
	 ~A()
	{
		cout << "~A()的调用" << endl;
	}
};

int main()
{
	D* d = new A();
	delete d;
	
	return 0;
}

运行结果:调用了派生类得析构函数
在这里插入图片描述
所以父类中的析构函数尽量写为虚函数

抽象类

先引入纯虚函数的概念:
纯虚函数是为类为派生类提供一个一致的接口
其定义的语法为:
virtual 返回值类型 函数名 (参数表)=0;

抽象类无法实例化
抽象类中至少有一个纯虚函数
而且它的派生类中必须重写纯虚函数

上代码:

class Base
{
public:
	virtual void Examp() = 0;//纯虚函数
 
	virtual  ~Base()
	{
		cout << "父类的析构" << endl;
	}
};
 
class Son:public Base
{
public:
	void Examp()
	{
		cout << "重写了父类的纯虚函数" << endl;
	}
	
	~Son()
	{
		cout << "子类的析构" << endl;
	}
};
 
int main()
{
	Son p1;
	p1.Examp();
	system("pause");
}
//上面是最简单的纯虚函数代码,子类必须得重写重写抽象类中的纯虚函数,不然不能实例化对象。

注意:
1、抽象类不能实例化 至少含有一个纯虚函数
2、派生类如果不给出基类中所有纯虚函数的定义的话 派生类也是抽象类
3、抽象类不能作为 参数类型 函数返回值 强制类型转换’
4、可以定义一个抽象类的指针或引用 通过指针或引用指向派生类对象之后来访问派生类的成员 这种访问是具有多态特征的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pp不会算法^v^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值