C++多态性

1.运算符重载

(1)运算符重载规则

C++几乎可以重载全部运算符,而且只能够重载C++中已经有的。

​ 不能重载的运算符:. .* :: ?:

重载后运算符的优先级和结合性都不会改变

运算符重载是针对新类型数据的实际要求,对原有运算符进行适当的该在。

例如:

使复述类的对象可以用“+”运算符实现加法;

​ 是时钟类对象可以用“++”运算符实现时间增加1秒。

重载为类的非静态成员函数;

重载为非成员函数。

(2)双目运算符重载为成员函数

重载为类成员的运算符函数定义形式:

函数类型  operator 运算符(形参)
{
......
}
参数个数=原操作数-1  (后置++--除外)

双目运算符重载规则

​ 如果要重载B为类成员函数,使之能够实现表达式oprd1 B oprd 2,其中oprd1为A类对象,则B应被重载为A类的成员函数,形参类应该是oprd2所属的类型。

​ 经重载后,表达式oprd1 B oprd2 相当于 oprd1.operator B(oprd2)

8-1 复数类加减法运算重载为成员函数
要求:
	将+-运算重载为复数类的成员函数。
规则:
	实部和虚部分别相加减。
操作数:
	两个操作数都是复数类的对象。
#include "iostream"
using namespace std;
class Complex {
public:
	Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {}
	//运算符+重载成员函数
	Complex operator + (const Complex& c2) const;
	//运算符-重载成员函数
	Complex operator-(const Complex& c2)const;
	void display() const;//输出复数
private:
	double  real;//复数实部
	double imag;//复数虚部
};
Complex Complex::operator+(const Complex& c2)const {
//创建一个临时无名对象作为返回值
	return Complex(real + c2.real, imag + c2.imag);
}

Complex Complex::operator-(const Complex& c2)const {
//创建一个临时无名对象作为返回值
	return Complex(real - c2.real, imag - c2.imag);
}

void Complex::display() const {
	cout << "(" << real << "," << imag << ")" << endl;
}

int main(){
	Complex c1(5, 4),  c2(2, 10), c3;
	cout << "c1="; c1.display();
	cout << "c2="; c2.display();
	c3 = c1 - c2;//使用重载运算符完成复数减法
	cout << "c3=c1-c2="; c3.display();
	c3 = c1 + c2;
	cout << "c3=c1+c2="; c3.display();
	return 0;
}
c1 = (5, 4)
c2 = (2, 10)
c3 = c1 - c2 = (3, -6)
c3 = c1 + c2 = (7, 14)

(3)单目运算符重载为成员函数

前置单目运算符重载规则

​ 如果重载U为类成员函数,使之能够实现表达式 U oprd,其中oprd为A类对象,则U应被重载为A类的成员函数,无形参。

经重载后,表达式U oprd 相当于 oprd.operator U()

后置单目运算符 ++ 和 --重载规则

​ 如果要重载 ++ 或 --为类成员函数,使之能够实现表达式oprd++ 或 oprd-- ,其中oprd为A类对象,则++ 或 --应被重载为A类的成员函数,且具有一个int类型形参。

​ 经重载后,表达式 oprd ++ 相当于 oprd.operator ++(0)

例:8-2重载前置++ 和后置++ 为时钟类成员函数
	前置单目运算符,重载函数没有形参
	后置++运算符,重载函数需要有一个int形参
	操作数是时钟类的对象
	实现时间增加1秒钟
	//例题8-2 重置前置++ 和后置++ 为时钟类成员函数
#include "iostream"
using namespace std;
class Clock {//时钟类定义
public:
	Clock(int hour = 0, int minute = 0, int second = 0);
	void showTime() const;
	//前置单目运算符重载
	Clock& operator ++();
	//后置单目运算符重载
	Clock operator ++(int);
private:
	int hour, minute, second;
};

Clock::Clock(int hour, int minute, int second) {
	if (0 <= hour && hour < 24 && 0 <= minute && minute < 60 && 0 <= second && second < 60) {
		this->hour = hour;
		this->minute = minute;
		this->second = second;
	}
	else
		cout << "Time error!" << endl;
}
void Clock::showTime()const {//显示时间
	cout << hour << ":" << minute << ":" << second << endl;
}
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;
}
int main()
{
	Clock myClock(23, 59, 59);
	cout << "first time output:";
	myClock.showTime();
	cout << "Show myClock++:";
	(myClock++).showTime();
	cout << "Show ++myClock:";
	(++myClock).showTime();
	return 0;
}
运行结果:
first time output : 23 : 59 : 59
Show myClock++ : 23 : 59 : 59
Show ++myClock : 0 : 0 : 1

(4)运算符重载为非成员函数

运算符重载为非成员函数的规则

函数的形参代表依次从左至右次序排列的个操作数。

重载为非成员函数时

​ 参数个数= 原操作数个数(后置++ 、除外)

​ 至少应该有一个自定义类型的参数。

后置单目运算符++ 和–的重载函数,形参列表中只要怎嫁一个int,但不必写形参名。

如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。

运算符重载为非成员函数的规则

双目运算符B重载后,表达式oprd1 B oprd2 等同于operator B(oprd1,oprd2)

前置运算符B重载后,表达式B oprd等同于operator B(oprd)

后置单目运算符++ 和–重载后,表达式oprd B等同于operaror B(oprd ,0)

8-3:重载Complex的加减法和“<<”运算度为非成员函数
#include"iostream"
using namespace std;

class Complex {
public:
	Complex(double r = 0.0, double i = 0.0): real(r), imag(i){}
	friend Complex operator +(const Complex& c1, const Complex& c2);
	friend Complex operator -(const Complex& c1, const Complex& c2);
	friend ostream & operator << (ostream & out, const Complex & c);
private:
	double real;//复数实部
	double imag;//复数虚部
};
Complex operator +(const Complex& c1, const Complex& c2) {
	return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
Complex operator -(const Complex& c1, const Complex& c2) {
	return Complex(c1.real - c2.real, c1.imag - c2.imag);
}
ostream& operator <<(ostream& out, const Complex& c) {
	out << "(" << c.real << "," << c.imag << ")";
	return out;
}

int main()
{
	Complex c1(5, 4), c2(2, 10), c3;
	cout << "c1=" << c1 << endl;
	cout << "c2=" << c2 << endl;
	c3 = c1 - c2;//使用重载运算符完成复数减法
	cout << "c3 = c1 - c2=" << c3 << endl;
	c3 = c1 + c2;//使用重载运算符完成复数加法
	cout << "c3 = c1 + c2=" << c3 << endl;
	return 0;
}
运行结果:
c1 = (5, 4)
c2 = (2, 10)
c3 = c1 - c2 = (3, -6)
c3 = c1 + c2 = (7, 14)

2.虚函数

初识虚函数

  • 用virtual关键字说明的函数
  • 虚函数时实现运行时多态性基础
  • C++中的虚函数是动态绑定的函数
  • 虚函数必须是非静态的成员杉树,虚函数经过派生之后,就可以实现运行过程中的多态。
8-4:通过虚函数实现运行时多态
#include"iostream"
using namespace std;
class Base1 {//基类Base1定义
public:
	virtual void display() const; //虚函数
};
	void Base1::display()const {
		cout << "Base1::display()" << endl;
	}

class Base2 :public Base1 {//公有派生类Base2定义
public:
	virtual void display() const;
};
	void Base2::display()const {
		cout << "Base2::display()" << endl;
	}

class Derived :public Base2 {//公有派生类Derived定义
public:
	virtual void display() const;
};
	void Derived::display()const {
		cout << "Derived::display()" << endl;
	}

void fun(Base1* ptr) {//参数为指向基类对象的指针
	ptr->display();//对象指针->成员名
}
int main() {
	Base1 base1;//声明Base1类对象
	Base2 base2;//声明Base2类对象
	Derived derived;//声明Derived类对象

	fun(&base1);//用Base1对象的指针调用fun函数
	fun(&base2);//用Base2对象的指针调用fun函数
	fun(&derived);//用Derived对象指针调用fun函数

	return 0;
}
运行结果:
Base1::display()
Base2::display()
Derived::display()

什么函数可以是虚函数?

  • 一般成员函数可以是虚函数
  • 构造杉树不能是虚函数
  • 析构函数可以是虚函数

一般虚函数成员

  • 虚函数的声明

    virtual 函数类型 函数名 (形参表);

  • 虚函数声明只能出现在类定义中的函数原型声明中,而不能再成员函数实现的时候。

  • 在派生类中可以对基类中的成员函数进行覆盖。

  • 虚函数一般不声明为内敛函数,因为对虚函数的调用需要动态绑定,而对内敛函数处理事静态的。

虚析构函数

8-5:虚析构函数举例
#include"iostream"
using namespace std;
class Base {
public:
	virtual ~Base(); //不是虚函数
};
Base::~Base() {
	cout << "Base destructor" << endl;
}
class Derived :public Base {
public:
	Derived();
	virtual ~Derived();//不是虚函数
private:
	int* p;
};
Derived::Derived() {
	p = new int(0);
}
Derived::~Derived() {
	cout << "Derived desturactor" << endl;
	delete p;
}
void fun(Base* b) {
	delete b;//静态绑定,只会调用~Base
}

int main() {
	Base* b = new Derived();
	fun(b);
	return 0;
}
运行结果:    
Derived desturactor
Base destructor

虚表与动态绑定

虚表

  • 每个多态类有一个虚表(virtual table)
  • 虚表中有当前的各个虚函数的入口地址
  • 每个对象有一个指向当前类的虚表的指针(虚指针vptr)

动态绑定的实现

  • 构造函数中对象的虚指针赋值
  • 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
  • 通过该入口地址调用虚函数

3.抽象类

纯虚函数

  • 纯虚函数是一个在基类中声明的虚函数,在基类中没有定义具体的操作内容,要求各派生类根据实际需要定义的版本。

    纯虚函数的声明格式为:

virtual 函数类型 函数名(参数表) = 0;

抽象类的语法

  • 带有纯虚函数的类称为抽象类:

    class 类名
    {
    virtual 类型 函数名(参数表)= 0//其他成员......
    }
    

抽象类作用

  • 将有关数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
  • 都与暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现

注意

  • 抽象只能作为基类来使用。
  • 不能定义抽象类的对象。
8-6:抽象类举例
#include"iostream"
using namespace std;
class Base1 {
public:
	virtual void display() const = 0;// 纯虚函数
};

class Base2 :public Base1 {
public :
	virtual void display() const;//覆盖基类的虚函数
};
void Base2::display() const {
	cout << "Base2::dispaly()" << endl;
}

class Derived :public Base2 {
public:
	virtual void display() const;//覆盖基类的虚函数
};
void Derived::display() const {
	cout << "Derived::diaplay()" << endl;
}
void fun(Base1* ptr) {
	ptr->display();
}
int main() {
	Base2 base2;
	Derived derived;
	fun(&base2);
	fun(&derived);
	return 0;
}
运行结果:
Base2::dispaly()
Derived::diaplay()

4.OVERRIDE与FINAL

override

  • 多态行为的基础:基类声明虚函数,派生类声明一个函数覆盖该虚函数;
  • 覆盖要求:函数签名(signature)完全一致。
  • 函数签名包括:函数名 参数列表 const

c++11引入显示式函数覆盖,在编译期而非运行期捕获错误信息。在虚函数显示重载运用,编辑器会检查基类是否存在以虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会报错误

4.OVERRIDE与FINAL

override

  • 多态行为的基础:基类声明虚函数,派生类声明一个函数覆盖该虚函数;
  • 覆盖要求:函数签名(signature)完全一致。
  • 函数签名包括:函数名 参数列表 const

c++11引入显示式函数覆盖,在编译期而非运行期捕获错误信息。在虚函数显示重载
运用,编辑器会检查基类是否存在以虚拟函数,与派生类中带有声明override的虚拟
函数,有相同的函数签名(signature);若不存在,则会报错误

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值