C++入门笔记(六)多态性

1、多态性概述

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

      面向对象的多态性类型可以分为:重载多态,强制多态,包含多态和参数多态。

      多态从实现的角度来讲可以分为:编译时的多态和运行时的多态。

      绑定是指计算机程序自身彼此关联的过程,就是把一条消息和一个对象的方法相结合的过程。

      绑定工作在编译连接阶段完成的情况称为静态绑定,绑定工作在程序运行阶段完成的情况称为动态绑定。

2、运算符重载

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

     (1)运算符重载的规则

              a、C++中的运算符除了少数几个外(关系运算符" . " ,成员指针运算符 " .* " ,作用域分辨符 " :: " ,三目运算符 " ?: "),全部可以重载,

而且只能重载C++中已经有的运算符。

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

              c、运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有功能相类似,

不能改变原运算符的操作对象个数,同时至少要有一个操作对象时自定义类型。

              一般语法格式

              返回类型 operator 运算符(形参表)

              {

                      函数体

              }

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

              实际使用中总是通过该类的某个对象来访问重载的运算符,如果是双目运算符,左操作数是对象本身的数据,由 this 指针指出,右
操作数则需要通过运算符重载函数的参数来传递。如果是单目运算符,操作数由对象的 this 指针给出,就不再需要任何参数。

              对于双目运算符B,如果要重载为类的成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1为A类的对象,则应该把 B 重载

为 A 类的成员函数,该函数只有一个形参,形参的类型是 oper2 所属类型。

              实例:

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

         运行结果:


              对于后置运算符“++”和“--”,如果要重载为成员函数,用来实现表达式 oprd++ 或 oprd-- ,其中 oprd 为 A类的对象,那么运算符

就应当重载为 A 类的成员函数,这时函数要带有一个整型(int)形参,int 类型参数只是用来区分与前置运算符(++、--)(对于前置单目

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

              实例:

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

         运行结果:

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

              这时,运算所需要的操作数都需要通过函数的形参类传递,在形参表中形参从左到右的顺序就是运算符操作数的顺序。如果需要访问

运算符参数对象的私有成员,可以将函数声明为类的友元函数。

              a、对于双目运算符 B,如果要实现 oprd1 B oprd2,其中 oprd1 和 oprd2 中只要有一个具有自定义类型,就可以将 B 重载为非成员函数

函数的形参为 oprd1 和 oprd2。重载后的表达式 就相当于函数调用 operator B(oprd1,oprd2)

              b、对于前置单目运算符 U,如“-”等,如果要实现表达式 U oprd,其中 oprd 具有自定义类型,就可以将 U 重载为非成员函数,函数的

形参为 oprd。经过重载后的表达式相当于函数调用 operator U(oprd)

              c、对于后置运算符 ++和 -- ,如果要实现表达式 oprd++ 或 oprd--,其中oprd为自定义类型,那么运算符就可以重载为非成员函数。

这时函数的形参有两个,一个是oprd,另一个是 int 类型形参(用于区分前置运算符函数),重载后的表达式就相当于函数调用

operator++(oprd,0) 和 operator·--(oprd,0)

              实例:

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

       运行结果:

       上例:“<<”操作符的左操作数为 ostream 类型的引用,右操作数是 Complex 类型的引用,这样在执行 cout << c1 时,

就会调用 operator << (cout, c1)

3、虚函数

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

     (1)一般虚函数

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

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

              运行过程中的多态需要满足3个条件,第一是类之间满足赋值兼容规则,第二是要声明虚函数,第三是要由成员函数来调用

或者是通过指针、引用来访问虚函数。

              实例:

#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:
    void display() const; //覆盖基类的虚函数
};
void Base2::display() const
{cout<<"Base2::display()"<<endl;}

class Derived:public Base2 //公有派生类 Derived 定义
{
public:
    void display() const; //覆盖基类的虚函数
};
void Derived::display() const
{cout<<"Derived::displat()"<<endl;}

void fun(Base1 * ptr) //参数为指向基类对象的指针
{
    ptr->display();   //对象指针->成员名
}

int main()
{
    Base1 base1;
    Base2 base2;
    Derived derived;

    fun(&base1);
    fun(&base2);
    fun(&derived);

    return 0;
}

          运行结果:

        本程序中,派生类并没有显示给出虚函数声明,这时系统会以以下规则判断派生类的一个函数成员是不是虚函数:

         a、该函数是否与基类的虚函数有相同的名称

         b、该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型

         c、该函数是否与基类的虚函数有相同的返回值或者满足赋值兼容规则的指针、引用型的返回值

         如果派生类的函数满足了上述条件,就会自动确定为虚函数,这时,派生类的虚函数便覆盖了基类的虚函数,不仅如此,派生类中的

虚函数还会隐藏基类中同名函数的所有其他重载形式。

      (2)虚析构函数

               C++ 中不能声明虚构造函数,但是可以声明虚析构函数: virtual ~类名();

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

4、纯虚函数与抽象类

     (1)纯虚函数

              声明格式: virtual 函数类型 函数名(参数表)=0;

              其与一般虚函数成员的原型的不同就在于后面加了个“=0”

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

     (2)抽象类

              带有纯虚函数的类是抽象类。

              抽象类的主要作用是通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性。

              抽象类不能实例化,即不能定义一个抽象类的对象,但是可以定义一个抽象类的指针和引用。

#include<iostream>
using namespace std;

class Base1 //基类 Base1 定义
{
public:
    virtual void display() const=0; //纯虚函数
};


class Base2:public Base1 //公有派生类 Base2 定义
{
public:
    void display() const; //覆盖基类的虚函数
};
void Base2::display() const
{cout<<"Base2::display()"<<endl;}

class Derived:public Base2 //公有派生类 Derived 定义
{
public:
    void display() const; //覆盖基类的虚函数
};
void Derived::display() const
{cout<<"Derived::displat()"<<endl;}

void fun(Base1 * ptr) //参数为指向基类对象的指针
{
    ptr->display();   //对象指针->成员名
}

int main()
{
    Base2 base2;
    Derived derived;

    fun(&base2);
    fun(&derived);

    return 0;
}

     运行结果:


     

猜你喜欢

转载自blog.csdn.net/u012561696/article/details/19394135