【C++复习】第八章 多态性(1)(多态类型、运算符重载)

1、多态性

1.1 什么是多态?

多态是指相同消息不同对象接收后导致不同的行为,所谓消息是指对类成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数。

消息在C++编程中指的是对类的成员函数的调用。

举例解释

使用运算符“+”可以实现对整数,浮点数,双精度浮点数的加法运算。“+”是消息,被不同类型的对象接收后,采用不同方式进行运算

1.2 多态类型

1.2.1 总览

多态类型 介绍    
重载多态 普通函数重载和类成员函数重载(运算符) 专用多态 静态绑定
编译时多态


强制多态

强制类型转换

强制多态就是将一个变量的类型进行转换,以满足一个函数运算的要求。

静态绑定
编译时多态
包含多态

虚函数

包含多态是指类族中不同类的同名成员函数实现的操作不相同。

通用多态 动态绑定
运行时多态
参数多态

类模板

由类模板实例化得到的所有类都有相同的操作,但是被操作对象的类型不同,这就是参数多态。

静态绑定
编译时多态

1.2.2 编译时多态/运行时多态

从实现角度来看,多态有两种类型,编译时多态和运行时多态

编译时多态 编译过程确定了同名操作的具体对象
运行时多态 运行过程确定了同名操作的具体对象

这种确定调用同名函数的哪个函数的过程就叫做联编或者绑定。一般称其为绑定。绑定实际上就是确定某个标识符对应的存储地址的过程。按照绑定发生的阶段的不同可以分为:静态绑定动态绑定

如果绑定过程发生在编译链接阶段,则称为静态绑定。在编译链接过程中,编译器根据类型匹配等特征确定某个同名标识究竟调用哪一段程序代码,也就是确定通过某个同名函数到底调用哪个函数体。

如果绑定过程发生在程序运行阶段,则称为动态绑定。在编译链接过程中无法确定调用的具体函数,就要等到程序运行时动态确定。包含多态就需要使用动态绑定实现。

静态绑定
编译时多态

绑定过程发生在编译链接阶段

重载多态、强制多态、参数多态

动态绑定
运行时多态

绑定过程发生在程序运行阶段

包含多态

2、重载多态-运算符重载

2.1 引子——复数类

定义一个复数类:

class Complex //复数类
{
public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
    void display() const; //显示复数的值
private:
    double real;
    double imag;
};

定义复数类的对象:

Complex a(10, 20), b(5, 8);

我们希望对a和b进行加法运算,

  1. 可以写个函数,类内的函数或类外的函数都行。不建议
  2. 对“+”运算符进行重载。(“+”在作用于Complex类型对象时会执行对应程序)

 2.2 例8-1 复数类加减法运算重载——成员函数形式

先看看程序应该怎么写,再介绍运算符重载

/*
8-1 P309
复数类加减法运算重载——成员函数形式
*/
#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 Complex::operator-(const Complex &c2) const
{
    return Complex(real - c2.real, imag - c2.imag); //创建一个临时对象作为返回值,这里调用了Complex类的构造函数
}
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)

2.3 运算符重载规则

重载:函数的另一种调用形式,只是写着方便,看着方便,本质还是调用函数

  1. C++中几乎所有运算符都可重载,只能重载C++中已经存在的运算符
  2. 重载之后运算符的优先级和结合性都不会变
  3. 一般来将,重载的功能应与原有功能类似,不能改变原运算符的操作对象的个数。
    至少要有一个操作对象自定义类型
  4. 这些操作符无法被重载
    类属关系运算符 .
    成员指针运算符 .*
    作用域分辨符 ::
    三目运算符 ?:
  5. 可重载为类的非静态成员函数非成员函数
  6. 重载的一般语法
    //重载为类的成员函数
    返回类型 类名::operator 运算符(形参表)
    {
        函数体   
    }
    //重载为非成员函数
    返回类型 operator 运算符(形参表)
    {
        函数体   
    }

2.3.1 运算符重载为成员函数(非静态)

2.3.1.1 双目运算符

//双目运算符等价
oprd1 B oprd2 //等价于下一行表达式
oprd1.operator B(oprd2)
//例
A+B
A.operator +(B)
/*
由以上等价可知,要重载“+”为A所属类的成员函数,而不是B的。
形参类型应该为B所属类型
*/

现在再看看2.2中的重载函数,一目了然

Complex Complex::operator+(const Complex &c2) const
{
    return Complex(real + c2.real, imag + c2.imag); //创建一个临时对象作为返回值,这里调用了Complex类的构造函数
}
Complex Complex::operator-(const Complex &c2) const
{
    return Complex(real - c2.real, imag - c2.imag); //创建一个临时对象作为返回值,这里调用了Complex类的构造函数
}

有些运算符无法重载为成员函数,如二元运算符的左操作数不是对象,或者是不能有我们重载运算符的对象。
例:支持
a+5.0但不支持5.0+a

2.3.1.2 单目运算符(前置)

//前置单目运算符等价
B oprd //等价于下一行表达式
oprd.operator B()
//例
++a
a.operator ++()

函数应被重载为oprd所属类成员函数无形参

2.3.1.3 单目运算符(后置)

//后置单目运算符等价
oprd B //等价于下一行表达式
oprd.operator ++(0) 或
oprd.operator ++(int)//函数原型
//例
x++
x.operator ++(0) 或
x.operator ++(int)//函数原型

函数应被重载为oprd所属类成员函数,且有一个int类型形参

2.3.1.4 例8-2 重载前置++和后置++为时钟类成员函数

注意两种重载返回值的区别,前置++返回值是左值(引用),后置++返回值是右值

/*
8-2 P311
将单目运算符“++”重载为成员函数的形式
*/
#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

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

规则:

  1. 函数形参从左到右,表示各操作数
  2. 参数个数==原操作数个数(后置++,--除外)
  3. 至少有一个自定义类型的参数
  4. 后置单目运算符++和--的重载函数中,形参要增加一个int,不必写形参名
  5. 重载函数需要某类对象的私有成员,可将该重载函数声明为该类的友元函数

2.3.2.1 等价

//双目运算符B
oprd1 B oprd2
operator B(oprd1,oprd2)
//例
a+b
operator +(a,b)

//前置单目运算符
B oprd
operator B(oprd)
//例
++b
operator ++(b)

//后置单目运算符
oprd B
operator B(oprd,0)
//例
b++
operator ++(b,0)

2.3.2.2 例8-3 重载Complex的加减法和“<<”运算符为非成员函数

cout 是 ostream 类的对象,是一个全局变量。ostream 类和 cout 在头文件 <iostream> 中声明。

/*
8-3 P313
以非成员函数形式重载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;
}

参考:

C++语言程序设计(第5版),郑莉,清华大学

多态的概念和类型

posted @ 2023-02-08 14:16  尚方咸鱼  阅读(40)  评论(0编辑  收藏  举报