C++多态性----运算符重载与虚函数

一、多态性

①概述多态是指同样的消息被不同类型的对象接收时导致的不同行为。

②类型:

  可以分为四类:重载多态、强制多态、包含多态、参数多态。

       ------------------------ ---------------------------

                  专用多态      通用多态

③实现:

   从实现角度可以划分为两类:编译时多态运行时多态

   绑定:就是把一条消息和一个对象的方法结合的过程。

   静态多态在编译过程中确定了同名操作的具体对象。绑定工作在编译连接阶段完成的情况(实现手段:函数/运算符重载、模板)。

  动态多态在程序运行过程中才动态的确定操作所针对的具体对象。绑定工作在程序运行阶段完成(实现手段:基类和派生类中建立同名函数,但功能不同)。

二、运算符重载

1、定义运算符重载是对已有的运算符赋予多层含义,使同一个运算符作用于不同类型的数据是导致不同的行为。(实质:就是函数重载

2、规则:①C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已经有的运算符。

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

        ③运算符重载是针对新数据类型的实际需要,对原有运算符进行适当的改造。

        ④C++标准规定,有些操作符是不能重载的,它们是类属关系运算符“.”、成员指针运算符“*”、作用域分辨符“::”、和三目运算符“?:”。

3、运算符重载的两种形式重载为类的成员函数重载为非成员函数

①重载为类的成员函数:

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

②重载为非成员函数:

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

注意返回类型指定了重载运算符的返回类型,也就是运算结果类型;

           operator是定义运算符重载的关键字;

           运算符即是要重载的运算符名称;

           形参表中给出重载运算符所需要的参数和类型。

③程序实例

1)重载为成员函数

例8-1

#include<iostream>
using namespace std;
class Complex {
public:
    Complex(float a = 0, float b = 0) :real(a), imag(b) {};//构造函数
    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;//c3=c1.operator-(c2);相当于函数的调用
    cout << "c3=c1-c2="; c3.display();
    c3 = c1 + c2;//运用重载运算符+号
    cout << "c3=c1+c2="; c3.display();//展示出来
    return 0;
}

注解:将复数的加减法这样的运算重载为类的成员函数,除了使用了关键字operator外,运算符重载成员函数与类的普通成员函数没有什么区别。

在使用时可以直接通过运算符、操作数的方式来完成函数调用(相当于函数的调用)。“+”“-”的原有功能不变,同时添加了新的针对复数运算的功能

运行结果:

 

思考若是复数加整数呢?如:c=c1+5

将主函数修改:

int main() {
    Complex c1(5, 4), c2(2, 10), c3,c;//定义三个对象
    cout << "c1="; c1.display();
    //cout << "c2="; c2.display();
    //c3 = c1 - c2;
    //cout << "c3=c1-c2="; c3.display();
    c = c1 + 5;
    cout << "c=c1+5="; c.display();
    //c3 = c1 + c2;//运用重载运算符+号
    //cout << "c3=c1+c2="; c3.display();//展示出来
    return 0;
}

可得运行结果:

可见:复数+整数不会形象重载运算符的使用。因为整数5在传递过程中已经转化为了复数形式

 

思考:若改为整数+复数的形式,程序还可以运行吗?例c=5+c1;

c = 5 + c1;
    cout << "c=5+c1="; c.display();

程序报错:

 

原因:数字在前无法转换为复数形式,常数在前无法调用函数5.operator+(c1);

可以使用友元函数来修改。即运算符重载为非成员函数。

2)重载为非成员函数(利用友元函数)

例8-3

#include<iostream>
using namespace std;
class Complex {
public:
    Complex(float a = 0, float b = 0) :real(a), imag(b) {};
    void display() {
        cout <<"("<< real << "," << imag << ")" << endl;
    }
    friend Complex operator+(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
    friend Complex operator-(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
    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);
}
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;
}

分析将运算符重载为类的非成员函数,就必须把操作数全部通过形参的方式传递给运算符重载函数。

运行结果:

 

思考:由此方法可以解决我们上例问题:整数+复数

例c=5+c1;

class Complex {
public:
    Complex(float a = 0, float b = 0) :real(a), imag(b) {};
    void display() {
        cout <<"("<< real << "," << imag << ")" << endl;
    }
    friend Complex operator+(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
    friend Complex operator-(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
    friend Complex operator+(int, Complex);
    double real, imag;
};
Complex operator+(int c, Complex d)
{
    Complex sum;
    sum.real = c + d.real;
    sum.imag = d.imag;
    return sum;
}
main()
{
        c = 5 + c1;
    cout << "c=5+c1="; c.display();
}

运行结果:

 

注意:

    一般在C++中只要定义非成员函数,就使用友元函数。(可以访问私有成员)

3)单目运算符的重载

例8-2  “++”的重载

注意:

单目运算符分为前置++和后置++。所以规定:

         对于前置单目运算符,重载函数没有形参;

         对于后置单目运算符,重载函数有一个int型形参

#include<iostream>
using namespace std;
class Complex {
public:
    Complex(float a = 0, float b = 0) :real(a), imag(b) {};
    void display() {
        cout <<"("<< real << "," << imag << ")" << endl;
    }
    Complex& operator ++();//++的前置运算符重载
    Complex operator ++(int);//++的后置重载,需要用int参数来区分
private:
    float real, imag;
};
Complex& Complex::operator ++()//前置
{
    real++;
    imag++;
    return (*this);
}
Complex Complex::operator ++(int) {//后置,返回一个对象
    return Complex(real++, imag++);
}
int main() {
    Complex c1(5, 4), c2(2, 10), c3, c4;
    c3 = c1++;//前置运算
    c4 = ++c2;//后置运算
    c1.display();
    c2.display();
    c3.display();
    c4.display();
    return 0;
}

运行结果:

 

 分析:

前置单目运算符与后置单目运算符最主要的区别就在于重载函数的形参

本例中后置++运算符int型参数在函数体中并未使用,纯粹用来区别前置与后置,因此参数表中可以只给出类型名,没有参数名。

三、虚函数

虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。

1、一般数函数成员

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

实际上就使用关键字virtual来限定成员函数,虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。

注意:

运行过程中的多态需要满足三个条件:①满足赋值兼容规则;②要声明虚函数;③由成员函数来调用或者通过指针引用来访问虚函数。

程序实例:例8-4

#include<iostream>
using namespace std;
class Base1{
    public:
        virtual void display()const;//虚函数 
};
void Base1::display()const{
    cout<<"Base1::display()"<<endl;
}
class Base2:public Base1{
    public:
        virtual void display()const;
};
void Base2::display()const{
    cout<<"Base2::display()"<<endl;
}
class Derived:public Base2{
    public:
        virtual void display()const;
};
void Derived::display()const{
    cout<<"Derived::display()"<<endl;
}
void fun(Base1 *p){
    p->display();
}
int main(){
    Base1 base1;
    Base2 base2;
    Derived derived;
    fun(&base1);
    fun(&base2);
    fun(&derived);
    return 0;
}

分析:

程序中Base1,Base2,和Derived属于同一个类族,而且是通过公有派生而来的,因此满足赋值兼容规则。同时,基类Base1的成员函数display()声明为虚函数,程序中使用对象指针来访问成员函数。这样的绑定过程就是在运行中完成,实现了运行中的多态。如果没有虚函数的话,那么程序将会进行静态绑定,fun函数中用的是Base1的指针,那么不管传进去的是什么指针,都会调用Base1的display函数。

 

2、虚析构函数

①声明语法:

virtual ~类名();

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

②作用:

  析构函数设置为虚函数之后,在使用指针引用时可以动态绑定,实现运行时多态,保证使用基类类型指针就能调用适当的析构函数针对不同的对象进行清理工作。

③举例:

练习8-9

#include <iostream>
using namespace std;
class BaseClass
{
public:
    virtual ~BaseClass() { cout << "BaseClass killed" << endl; }
};
class DerivedClass :BaseClass
{
public:
    ~DerivedClass() { cout << "DerivedClass killed" << endl; }
};
int main()
{
    BaseClass* pBase = (BaseClass*)new DerivedClass();
    delete pBase;
    return 0;
}

运行结果:

 

注意

    如果有可能通过基类指针来调用对象的析构函数(通过delete),就需要让基类的析构函数称为虚函数,否则会产生不良的后果。

3、纯虚函数与抽象类

①纯虚函数

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

声明格式:

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

注意:

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

区别于函数体为空的虚函数:纯虚函数根本没有函数体,而空的虚函数的函数体为空

②抽象类

带有纯虚函数的类是抽象类。(抽象类不能实例化)

例:习题8-6

#include<iostream>
using namespace std;
class Shape {
public:
    virtual double GetArea() = 0;  //纯虚函数
    virtual double GetPerim() = 0;  //纯虚函数
};
class Rectangle :virtual public Shape 
{
public:
    Rectangle(double a, double b) {
        len = a;
        kuan = b;
    }
    double GetArea() {
        return len * kuan;
    }
    double  GetPerim() {
        return 2 * (len + kuan);
    }
private:
    double len;
    double kuan;
    };
class Circle :virtual public Shape {
public:
    Circle(double a) {
        r = a;
    }
    double GetArea() {
        return 3.14 * r * r;
    }
    double  GetPerim() {
        return 2 * 3.14 * r;
    }
private:
    double r;
};
int main()

{
    double len, kuan, r;
    cout << "请输入长,宽:" << endl;
    cin >> len >> kuan;
    Rectangle rect(len, kuan);
    cout << "面积是:" << rect.GetArea() << endl << "周长是:" << rect.GetPerim() << endl;
    cout << "请输入半径:" << endl;
    cin >> r;
    Circle c(r);
    cout << "面积是:" << c.GetArea() << endl << "周长是:" << c.GetPerim() << endl;
    return 0;
}

分析:因为矩形的面积周长与圆的面积周长计算方法不同,所以声明为纯虚函数。具体计算方式由各派生类根据实际需要给出各自的定义

 

 

 

四、总结

①在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即程序需要修改或增加功能时,只需改动或增加较少的代码。

②使用多态能起到精简代码的作用。

 

posted @ 2019-10-26 14:57  nanaa  阅读(1129)  评论(0编辑  收藏  举报