多态

多态:通过绑定实现,所谓绑定,将一个标识符名称与一段函数代码结合起来,如函数调用表达式与函数体结合,这就是一种绑定,根据实现的时机,分为编译时绑定和运行时绑定。

多态,使得操作接口具有表现多种不同形态的能力,在不同环境下对不同对象具有不同的处理方式
不是所有的运算符都可以放在类体中作为成员函数来重载

重载分为两种:

  1. 重载为类的非静态成员函数
  2. 重载为非成员函数
    双目运算符重载为类的成员函数,左操作数是类的对象,参数个数=原操作数个数-1(后置++,--除外)
    重载区分前置++和后置++,通过参数表,后置的重载参数表要比前置的多一个参数,只用来区分不同的重载形式,在函数体中不能使用它。
    前置单目运算符,重载函数没有形参
    后置++运算符,重载函数需要有一个int 形参,一般写0就行
Clock & Clock::operator ++ () {	//前置单目运算符重载函数
	second++;
	if (second >= 60) {
		second -= 60;
		minute++;
		if (minute >= 60) {
			minute -= 60;
			hour = (hour + 1) % 24;
		}
	}
	return *this;
}

Clock Clock::operator ++ (int) {	//后置单目运算符重载
	//注意形参表中的整型参数
	Clock old = *this;
	++(*this);	//调用前置“++”运算符
	return old;
}

前置运算符,得返回它自己,返回的是左值,所以返回值是本类对象的引用。
后置运算符实质:返回的是没有加1的旧的值,其实自己暗地里已经加了1,使用前已经被加1,只不过用的是没有加1的副本,返回的是副本,不是对象自己,所以返回的是右值,右值,即通过这个值不能改变对象,返回的是复制品,复制品用没问题,但触及不到对象本身。

运算符重载为类外非成员函数:重载为类的成员函数,左操作数必须是类的对象

如下几种情况,需要重载为类外非成员函数

  1. 若左操作数不是类的对象,要重载为类外的普通全局函数
    重载为非成员函数时,至少应该有一个自定义类型的参数
  2. 左操作数是类 的对象,但这个类不是由我们自己定义的,比如说是类库中现成的类的对象,这个类不是由我们自己设计的,也没有权利给这个类增加一个重载运算符函数,要想这个类与我们另外自定义的一个类对象运算的话,只能重载为类外非成员函数。

虚函数:实现动态绑定函数

在编译阶段,编译器无法根据指针去判断在运算时它会去指向一个什么类型的对象,所以它只能是指针是什么类型的,它就去调用那个类型定义的函数
想要告诉编译器,不要在编译阶段确定,推迟决定,采用vitual关键字
在函数前面加vitual关键字,告诉编译器,凡是遇到这样原型的函数的调用,你都不要在编译的时候马上做决定,决定它该调用哪个函数的函数体,先把这个滞后,指示编译器不要在编译阶段做静态绑定,要为运行阶段动态绑定做好准备。
不能把函数的实现放在类体中作为内联函数,不要在编译阶段处理,要求在运行阶段再去决定对函数的调用该执行哪个函数的函数体,内联函数在编译处理阶段就嵌入到程序代码中去,两者矛盾,所以加了vitual关键字的函数都要在类外实现。
用vitual关键字说明的函数都是虚函数,虚函数是实现运行时多态的基础。
虚函数必须是非静态的成员函数,是属于对象的,不是属于整个类的,在运行时需要用指针去定位到它所要指的对象是谁,然后决定调用哪个函数体

虚函数:

  1. 一般成员函数可以是虚函数。
  2. 构造函数不能是虚函数
  3. 析构函数可以是虚函数

虚函数声明只能出现在类定义的函数原型声明中,有了vitual关键字,在派生类中可以对基类中的成员函数覆盖,达到多态性。

虚析构函数:

什么情况下使用:打算允许其他程序代码通过基类指针调用派生类对象的析构函数,那就需要让基类的析构函数称为虚函数
派生类析构函数执行后,紧接着执行基类析构函数

#include <iostream>
using namespace std;

class Base {
public:
	virtual ~Base();
};
Base::~Base() {
	cout<< "Base destructor" << endl;
 }

class Derived: public Base {
public:
	Derived();
	~Derived();
private:
	int *p;
};
Derived::Derived() {
	p = new int(0);
}
Derived::~Derived() { 
	cout <<  "Derived destructor" << endl;
	delete p;
}

void fun(Base* b) {
	delete b;
}

int main() {
	Base *b = new Derived();
	fun(b);
	return 0;
}

编译器预先埋下伏笔:
虚表与动态绑定:
为什么可以在运行时实现动态绑定,运行时只有操作系统,谁帮我们做决定执行哪个函数体
虚表:每个多态的类都有一个虚表
虚表中有一个当前类的各个虚函数的入口地址
每个具有虚表的类,具有多态性的类,它的对象都隐含有一个指向当前类的虚表的指针。

当要通过指针调用函数时,首先都会通过这个指针找打对象里面的这个虚表指针,然后找到虚表,从虚表里找打指向函数的指针,去调用相应的函数体。

原文地址:https://www.cnblogs.com/ymd12103410/p/9577779.html