【学习笔记】C++语言程序设计(郑莉):多态性


1. 多态性

面向对象程序设计的真正优势不仅仅在于继承,还在于将派生类对象当基类对象一样处理的功能。支持这种功能的机制就是多态和动态绑定

  • 多态的类型
    • 多态是指同样的消息被不同类型的对象接收时导致不同的行为
    • 面向对象的多态性可以分为4类:重载多态,强制多态,包含多态和参数多态。前两者称为专用多态,后两者称为通用多态。
    • 多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。前者是在编译的过程中确定了同名操作的具体操作对象,而后者则是在程序运行过程中才动态的确定操作所针对的具体对象。这种确定操作的具体对象的过程就是绑定
      • 绑定是指计算机程序自身彼此关联的过程,就是把一条消息和一个对象的方法相结合的过程,可以分为静态绑定和动态绑定。
      • 静态绑定:绑定工作在编译连接阶段完成的情况,如重载、强制和参数多态。
      • 动态绑定:绑定工作在程序运行阶段完成的情况,如包含多态。

2. 运算符重载

运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为

2.1 运算符重载的规则

  • C++中除了 “.” 、“.*” 、 “::” 和 “?:” 外,其它运算符全部可以重载,而且只能重载C++中已经有的运算符。

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

  • 可以重载为类的非静态函数成员和重载为非成员函数。其语法格式为:

      返回类型 operator 运算符(形参表){
      	函数体
      }
    

    返回类型指定了重载运算符的返回类型;operator是定义运算符重载函数的关键字;运算符即使要重载的运算符名称。

2.2 运算符重载为成员函数

  • 对于双目运算符,如果要重载为类的成员函数,使之能够实现表达式 oprd1 B oprd2,其中oprd1为A类的对象,则应当把B重载为A类的成员函数,该函数只有一个形参,形参的类型是oprd2所属的类型。经过重载之后,表达式oprd1 B oprd2就相当于函数调用 oprd1.operator B(oprd2)
  • 对于前置单目运算符U,如”-“等,如果要重载为类的成员函数,用来实现表达式U oprd,其中oprd为A类的对象,则U应当重载为A类的成员函数,函数没有形参。经过重载之后,表达式U oprd相当于调用oprd.operator U()
  • 对于后置运算符“++”和“–”,如果要重载为类的成员函数,用来实现表达式oprd++oprd–,其中oprd为A类的对象。重载之后,表达式oprd++和oprd–就相当于函数调用oprd.operator++(0)和oprd.operator–(0)

下面示例了复数类加减法运算重载为成员函数:

#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, 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;
}

运行结果为:
在这里插入图片描述
下面示例了将单目运算符“++”重载为成员函数:

#include<iostream>
using namespace std;

//将单目运算符“++”重载为成员函数
class Clock {
public:
	Clock(int hour = 0, int minute = 0, int second = 0);		//Clock类构造函数
	void showTime() const;										//时间显示函数
	Clock& operator++();										//前置单目运算符重载
	Clock operator++(int);										//后置单目运算符重载
private:
	int hour, minute, second;
};

Clock::Clock(int hour, int minute, int second) {				//Clock类构造函数实现
	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;
}

运行结果为:
在这里插入图片描述

2.3 运算符重载为非成员函数

  • 对于双目运算符B,如果要实现oprd1 B oprd2,其中oprd1和oprd2中只要有一个具有自定义类型,就可以将B重载为非成员函数,函数的形参为oprd1和oprd2。经过重载之后,表达式oprd1 B oprd2就相当于调用operator B(oprd1, oprd2)
  • 对于前置单目运算符U,如“-”等,如果要实现表达式U oprd,其中oprd具有自定义类型,就可以将U重载为非成员函数,函数的形参为oprd。经过重载之后,表达式U oprd相当于调用operator U(oprd)
  • 对于后置运算符“++”和“–”,如果要实现表达式oprd++或oprd–,其中oprd为自定义类型,那么运算符就可以重载为非成员函数。函数的形参为oprd和int类型参数。重载之后,表达式oprd++和oprd–就相当于函数调用operator++(oprd, 0)和operator–(oprd, 0)

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

  • 函数的形参代表依自左至右次序排列的各操作数
  • 重载函数为非成员函数时
    • 参数个数=原操作数个数(后置++、–除外)
    • 至少应该有一个自定义类型的参数
  • 后置单目运算符++和–的重载函数,形参列表中要增加一个int,但不必写形参名
  • 如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元
  • 双目运算符B重载后,
    • 表达式oprd1 B oprd2 等同于 operator B(oprd1, oprd2)
    • 前置单目运算符B重载后,表达式 B oprd 等同于 operator B(oprd)
    • 后置单目运算符++和–重载后,表达式 oprd B 等同于 operator B(oprd, 0)

下面示例了以非成员函数形式重载Complex的加减法运算和“<<”运算符:

#include<iostream>
using namespace std;

//以非成员函数形式重载Complex的加减法运算和“<<”运算符
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, 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+c3=" << c3 << endl;							//使用重载运算符完成复数减法
	return 0;
}

运行结果为:
在这里插入图片描述


3. 虚函数

  • virtual关键字说明的函数。
  • 虚函数是实现运行时多态性的基础。
  • C++中的虚函数是动态绑定的函数。
  • 虚函数必须是非静态的成员函数,虚函数经过派生后,就可以实现运行过程中的多态。

3.1 一般虚函数成员

一般虚函数成员的声明语法:

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

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

如果派生类没有显式的给出虚函数声明,这是系统就会遵循以下规则来判断派生类的一个函数成员是不是虚函数:

  • 该函数是否与基类的虚函数有相同的名称;
  • 该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型;
  • 该函数是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型的返回值。

如果从名称、参数及返回值3方面检查之后,派生类的函数满足上述条件,就会自动确定为虚函数。这时称派生类的虚函数覆盖了基类的虚函数

3.2 虚析构函数

虚析构函数的声明语法为:

virtual ~类名();

如果一个类的析构函数是虚函数,那么由她派生而来的所有子类的析构函数也是虚函数。

3.3 纯虚函数与抽象类

抽象类是一种特殊的类,是为了抽象和设计的目的而建立的。抽象类是带有纯虚函数的类

  • 纯虚函数

    • 纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要给出各自的定义。即对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。纯虚函数的声明格式为:

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

    声明为纯虚函数之后,基类中就可以不再给出函数的实现部分。纯虚函数的函数体由派生类给出。

  • 抽象类

    • 带有纯虚函数的类是抽象类。抽象类的主要作用是通过它为一个类族建立一个公共的接口,使它们能够更有效的发挥多态稳定性。
    • 抽象类不能实例化。即不能定义一个抽象类的对象,但是可以定义一个抽象类的指针和引用。通过指针和引用,就可以指向并访问派生类的对象,进而访问派生类的成员,这种访问是具有多态性的。

3.4 关键字override 与 final

override与final都不是语言关键字,只有在特定的位置才有特别的含义,其它地方仍旧可以作为一般标识符使用。

  • override
    • 显式函数覆盖
    • 声明为该函数必须覆盖基类的虚函数,编译器可发现“未覆盖”错误
    • 覆盖要求
      • 函数签名(signature)完全一致
      • 函数签名包括:函数名 参数列表 const
    • 显式覆盖的作用
      • 声明显式函数覆盖,在编译期间发现未覆盖的错误;
      • 运用显式覆盖,编译器会检查派生类中声明override的函数,在基类中是否存在可被覆盖的虚函数,若不存在,则会报错。
  • final
    • 用来避免类被继承,或是基类的函数被覆盖

课程链接:https://www.bilibili.com/video/BV1iF411Y74v

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值