美文网首页c/c++编程笔记程序员
9-C++远征之多态篇-学习笔记

9-C++远征之多态篇-学习笔记

作者: 天涯明月笙 | 来源:发表于2018-07-22 20:44 被阅读60次

C++远征之多态篇

面向对象三大特征:封装,继承,多态

多态: 发出一条命令时,不同的对象接收到同样的命令做出的动作不同

多态篇会学习到的目录:

  1. 普通虚函数 & 虚析构函数
  2. 纯虚函数:抽象类 & 接口类
  3. RTTI (运行时类型识别)
  4. 异常处理
  5. 概念区分: 隐藏 & 覆盖 | 早绑定与晚绑定
  6. 虚函数表(核心部分)

多态的内容很多,概念也听起来有点变态

多态变态

不过这也是最精彩的部分了。

c++ 虚函数

什么是多态?

多态是指相同对象收到不同消息或不同对象收到相同消息时产生不同的动作

静态多态 & 动态多态

  • 静态多态(早绑定)
  • 动态多态(晚绑定)

静态多态:相同对象收到不同消息

例子:

矩形类有两个同名的计算面积的函数,参数不同,这是两个互为重载的函数。

class Rect
{
public:
    int calcArea(int width);
    int calcArea(int width,int height);//互为重载
}

int main(void)
{
    Rect rect;
    rect.calcArea(10);
    rect.calcArea(10,20);

    return 0;
}

当我们传入一个参数,两个参数会调用两个不同的同名函数。

计算机在编译阶段就会自动根据参数使用不同的参数来确定使用哪个函数。

这里程序在运行之前也就是编译阶段,就决定了运行哪个函数。很早的就决定运行哪个了,这种情况就叫做早绑定,或静态多态。

动态多态(晚绑定): 不同对象收到相同消息

虚函数:要求

附录代码 2-2-VirtualFunction:

Shape.h

#ifndef SHAPE_H
#define SHAPE_H

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape();
    ~Shape();
    double calcArea();
};

#endif

Shape.cpp

#include "Shape.h"

Shape::Shape()
{
    cout << "Shape()" << endl;
}

Shape::~Shape()
{
    cout << "~Shape()" << endl;
}

double Shape::calcArea()
{
    cout << "Shape - > calcArea()" << endl;
    return 0;
}

Circle.h

#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"
class Circle:public Shape
{
public:
    Circle(double r);
    ~Circle();
    double calcArea(); // 同名且参数返回值一致
protected:
    double m_dR;
};

#endif

Circle.cpp

#include "Circle.h"

Circle::Circle(double r)
{
    cout << "Circle()" << endl;
    m_dR = r;
 }

Circle::~Circle()
{
    cout << "~Circle()" << endl;
}
double Circle::calcArea()
{
    cout << "Circle-->calcArea()" << endl;
    return 3.14 * m_dR * m_dR;
}

Rect.h

#ifndef RECT_H
#define RECT_H

#include "Shape.h"
class Rect : public Shape
{
public:
    Rect(double width,double height);
    ~Rect();
    double calcArea();

protected:
    double m_dwidth;
    double m_dHeight;
};

#endif // RECT_H

Rect.cpp

#include "Rect.h"

Rect::Rect(double m_dwidth, double m_dHeight)
{
    cout << "Rect()" << endl;
    this->m_dHeight = m_dHeight;
    this->m_dwidth = m_dwidth;
}

Rect::~Rect()
{
    cout << "~Rect()" << endl;
}

double Rect::calcArea()
{
    cout << "Rect::calcArea()"<< endl;
    return m_dwidth * m_dHeight;
}

main.cpp

#include <iostream>
#include "Circle.h"
#include "Rect.h"
#include <stdlib.h>
using namespace std;

int main()
{
    // 定义两个父类指针,指向子类对象
    Shape *shape1 = new Circle(3.0);
    Shape *shape2 = new Rect(3.0, 4.0);

    shape1->calcArea();
    shape2->calcArea();
    //当基类不添加virtual时。打印两遍基类的。

    delete shape1;
    shape1 = NULL;
    delete shape2;
    shape2 = NULL;

    system("pause");
    return 0;
}

上述代码问题1: 销毁父类指针是否可以连带销毁子类对象。
问题2: 使用指向子类对象的父类指针是否能直接调用到子类方法。

虚析构函数要求

2-5-VirtualDestructorFunction

Shape.h:

#ifndef SHAPE_H
#define SHAPE_H

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape();
    virtual ~Shape();
    virtual double calcArea();
};

#endif

Shape.cpp

#include "Shape.h"

Shape::Shape()
{
    cout << "Shape()" << endl;
}

Shape::~Shape()
{
    cout << "~Shape()" << endl;
}

double Shape::calcArea()
{
    cout << "Shape - > calcArea()" << endl;
    return 0;
}

Rect.h:

#ifndef RECT_H
#define RECT_H

#include "Shape.h"
class Rect : public Shape
{
public:
    Rect(double width,double height);
    ~Rect();
    double calcArea();

protected:
    double m_dwidth;
    double m_dHeight;
};

#endif // RECT_H

Rect.cpp:

#include "Rect.h"

Rect::Rect(double m_dwidth, double m_dHeight)
{
    cout << "Rect()" << endl;
    this->m_dHeight = m_dHeight;
    this->m_dwidth = m_dwidth;
}

Rect::~Rect()
{
    cout << "~Rect()" << endl;
}

double Rect::calcArea()
{
    cout << "Rect::calcArea()"<< endl;
    return m_dwidth * m_dHeight;
}

Circle.h 添加坐标类数据成员指针:

#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"
#include "Coordinate.h"
class Circle:public Shape
{
public:
    Circle(double r);
    ~Circle();
    double calcArea();
protected:
    double m_dR;
    Coordinate *m_pCenter;
};

#endif

Circle.cpp 实例化坐标对象,析构中释放:

#include "Circle.h"

Circle::Circle(double r)
{
    cout << "Circle()" << endl;
    m_dR = r;
    m_pCenter = new Coordinate(3, 5);
 }

Circle::~Circle()
{

    cout << "~Circle()" << endl;
    delete m_pCenter;
    m_pCenter = NULL;
}
double Circle::calcArea()
{
    cout << "Circle-->calcArea()" << endl;
    return 3.14 * m_dR * m_dR;
}

Coordinate.h

#ifndef COORDINATE_H
#define COORDINATE_H
#include <iostream>

using namespace std;

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
private:
    int m_iX;
    int m_iY;
};

#endif

Coordinate.cpp

#include "Coordinate.h"
#include <iostream>
using namespace std;

Coordinate::Coordinate(int x, int y)
{
    cout << "Coordinate()" << endl;
    m_iX = x;
    m_iY = y;
}
Coordinate::~Coordinate()
{
    cout << "~Coordinate()" << endl;
}

main.cpp:

#include <iostream>
#include "Circle.h"
#include "Rect.h"
#include <stdlib.h>
using namespace std;

int main()
{
    Shape *shape2 = new Rect(3.0, 4.0);
    Shape *shape1 = new Circle(3.0);
    shape1->calcArea();
    shape2->calcArea();

    //当基类不添加virtual时。打印两遍基类的。
    delete shape1;
    shape1 = NULL;
    delete shape2;
    shape2 = NULL;
    system("pause");
    return 0;
}
虚函数表指针要求

我们需要知道的一些概念:

  • 对象的大小: 在类实例化的对象当中数据成员所占据的大小,不包括成员函数。
  • Shape没有数据成员(理论上不占内存); Circle有一个int型的数据成员,应该占四个。
  • 对象的地址:通过类实例化的对象,它占据的内存单元的首地址
  • 对象成员的地址:当用一个类实例化一个对象之后,这个对象中可能与一个或多个数据成员,每一个数据成员所占据的地址就是这个对象的成员地址;对象的数据成员由于数据类型不同,占据的内存大小也不同,地址也是不同的。
  • 虚函数表指针:在具有虚函数的情况下实例化对象时,这个对象的第一个内存存储的是一个指针,即虚函数表的指针,占四个内存单元,因此我们可以通过计算对象的大小来证明指针的存在。

2-8-VirtualTablePointer

Shape.h

#ifndef SHAPE_H
#define SHAPE_H

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape();
    ~Shape();
    double calcArea();
    //virtual ~Shape();
    //virtual double calcArea();
};

#endif

Shape.cpp

#include "Shape.h"

Shape::Shape()
{
    //cout << "Shape()" << endl;
}

Shape::~Shape()
{
    //cout << "~Shape()" << endl;
}

double Shape::calcArea()
{
    cout << "Shape - > calcArea()" << endl;
    return 0;
}

Circle.h

#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"
class Circle:public Shape
{
public:
    Circle(int r);
    ~Circle();
protected:
    int  m_iR;
};

#endif

Circle.cpp

#include "Circle.h"

Circle::Circle(int r)
{
    m_iR = r;
}
Circle::~Circle()
{
}

main.cpp:

#include <iostream>
#include "Circle.h"
#include <stdlib.h>
using namespace std;

int main()
{
    Shape shape;
    cout << sizeof(shape) << endl;
    // Shape对象没有任何的数据成员。理论应该为0.
    Circle circle(100);
    cout << sizeof(circle) << endl;
    // Circle 有一个int数据成员 理论为4.

    system("pause");
    return 0;
}

运行结果:

普通虚函数有值,纯虚函数直接为0

当我们定义了一个纯虚函数,他同样会在虚函数表中出现,如图calcPerimeter ptr就是纯虚函数的指针,他的值是0(意思就是他没有指向代码区,不会实现任何方法)。他这样的目的是为了让子类在继承他的时候,再实现他的方法。

  • 在虚函数表中直接写为0,

  • 包含纯虚函数的类,就是抽象类。上面含有纯虚函数的shape类就是一个抽象类。

  • 纯虚函数无法调用,所以抽象类无法实例化对象

class Person
{
public:
    Person(string name);
    virtual void work() =0;
    virtual void printInfo() =0;
};
class Worker: public Person
{
public:
    Worker(string name)
    virtual void work() = 0;
    virtual void printInfo() { cout << m_strName <<endl;}
private:
    string m_strName;
};
class Dustman: public Worker
{
public:
    Worker(string name)
    virtual void work() {cout << "扫地"};
    virtual void printInfo() { cout << m_strName <<endl;}
private:
    string m_strName;
};
  • 抽象类的子类也有可能是抽象类。抽象类的子类只有把抽象类当中的所有纯虚函数都做了实现,子类才可以实例化对象。
  • 上面代码中work只把子类的两个实现了一个。只有dustman
    才能实例化对象。

抽象类代码示例

抽象类代码

如果Worker没有实现work。则不可以实例化work。
当Worker的子类dustman实现了work。就可以实例化dustman。

代码:

3-2-AbstractClass

Person.h

#ifndef PERSON_H//假如没有定义
#define PERSON_H//定义

#include <string>
using namespace std;

class Person
{
public:
    Person(string name);
    virtual ~Person() {};
    virtual void work() =0; // 纯虚函数
private:
    string m_strName;
};

#endif //结束符

Person.cpp

#include "Person.h"

Person::Person(string name)
{
    m_strName = name;
    // 不实现纯虚函数
}

Worker.h

#include <string>
using namespace std;
#include "Person.h"
class Worker:public Person
{
public:
    Worker(string name,int  age);
    //virtual void work();
    virtual ~Worker() {};
private:
    int  m_iAge;
};

Worker.cpp

#include "Worker.h"
#include <iostream>
using namespace std;
Worker::Worker(string name,int age):Person(name)
{
    m_iAge = age;
}

//void Worker::work()
//{
//  cout << "work()" << endl;
//}

Dustman.h

#ifndef DUSTMAN_H
#define DUSTMAN_H

#include "Worker.h"
class Dustman :public Worker
{
public:
    Dustman(string name, int age);
    virtual void work();
};

#endif

Dustman.cpp

#include "Dustman.h"
#include <iostream>
using namespace std;

Dustman::Dustman(string name, int age) :Worker(name, age)
{

}
void Dustman::work() {
    cout << "扫地" << endl;
}

main.cpp

#include <iostream>
#include "Person.h"
#include "Worker.h"
#include <stdlib.h>
#include "Dustman.h"
int main()
{
    //Person person("张三"); // 报错:“Person”: 不能实例化抽象类
    //Worker worker("zhangsan", 17); // 报错:“Worker”: 不能实例化抽象类
    Dustman dustman("zhangsan", 20);

    system("pause");
    return 0;
}

一个抽象类之所以叫抽象类,是因为它里面有一个或以上的纯虚函数。纯虚函数的写法是:

// virtual 函数返回类型 函数名()=0;
// 纯虚函数里面不用写任何代码
virtual void work() =0; // 纯虚函数

类包含了纯虚函数就会无法实例化,抽象函数我们本身就不需要它实例化。

例如Circle继承了shape,Circle为了可以计算周长,定义了一个叫calcPerimeter的方法,因此把他父类Shape的纯虚函数calcPerimeter覆盖了,这样就可以成功实例化通过子类Circle来计算周长。

练习

  • 只有函数声明没有函数定义,直接等于0的虚函数是纯虚函数。
  • 含有纯虚函数的类叫做抽象类。
  • 不可以使用含有纯虚函数的类实例化对象。
  • 抽象类的子类也可以是抽象类。

单元练习

定义一个动物(animal)类,要求含有虚函数eat和纯虚函数move以及数据成员m_strName,并定义构造函数和虚析构函数
定义一个狗(Dog)类,要求公有继承动物类,定义构造函数和虚析构函数,并实现自己的eat和move函数

通过动物类实例化狗类,调用狗类当中的成员函数

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义动物类:Animal
 * 虚函数:eat()
 * 纯虚函数:move()
 * 数据成员:m_strName
 */
class Animal
{
public:
    // 默认构造函数
    Animal(){};
    // 含参构造函数
    Animal(string name){m_strName = name; cout << "Animal" << endl;}
    // 虚析构函数
    virtual ~Animal(){cout << "~Animal" << endl;}
    // 虚成员函数
    virtual void eat(){cout << "Animal--" << m_strName << "-- eat" << endl;}
    // 纯虚函数
    virtual void move() = 0;
public:
    // 数据成员
    string m_strName;
};

/**
 * 定义狗类:Dog
 * 公有继承动物类
 * 虚成员函数:eat()、move()
 */
class Dog: public Animal
{
public:
    // 默认构造函数
    Dog(){};
    // 含参构造函数
    Dog(string name){m_strName = name; cout << "Dog" << endl;}
    // 虚析构函数
    virtual ~Dog(){cout << "~Dog" << endl;}
    // 虚成员函数eat()
    virtual void eat(){cout << "Dog--" << m_strName << " -- eat" << endl;}
    // 虚成员函数move()
    virtual void move(){cout << "Dog--" << m_strName << " -- move" << endl;}
public:
    // 数据成员
    string m_strName;
};

int main(void)
{
    // 通过动物类实例化狗类
    Animal *p = new Dog("狗类");
    // 调用成员函数
    p ->eat();
    p ->move();
    // 释放内存
    delete p;
    p = NULL;
    
    return 0;
}

运行结果:

要求

3-6-InterfaceClass

Flyable.h

#ifndef FLYABLE_H
#define FLYABLE_H

class Flyable
{
public:
    virtual void takeoff() = 0;//起飞
    virtual void land() = 0; //降落
};

#endif

Plane.h

#ifndef PLANE_H
#define PLANE_H
#include "Flyable.h"
#include <string>
using namespace std;
class Plane :public Flyable
{
public:
    Plane(string code);
    virtual void takeoff();
    virtual void land();
    void printCode();
private:
    string m_strCode;
};
#endif

Plane.cpp

#include "Plane.h"
#include <iostream>
using namespace std;
Plane::Plane(string code)
{
    m_strCode = code;
}
void Plane::takeoff()
{
    cout << "plane - takeoff" << endl;

}
void Plane::land()
{
    cout << "plane - land" << endl;

}
void Plane::printCode()
{
    cout << m_strCode << endl;
}

FighterPlane.h

#ifndef FIGHTERPLANE_H
#define FIGHTERPLANE_H

#include "Plane.h"
class FighterPlane:public Plane
{
public:
    FighterPlane(string code);
    virtual void takeoff();
    //因为plane已经实现过了,所以它可实现也可也不
    virtual void land();
};
#endif 

FighterPlane.cpp

#include <iostream>
#include "FighterPlane.h"
using namespace std;

FighterPlane::FighterPlane(string code) :Plane(code)
{
}
void FighterPlane::takeoff()
{
    cout << "FighterPlane -- takeoff" <<endl;
}
void FighterPlane::land()
{
    cout << "FighterPlane -- land" << endl;
}

main.cpp:

#include <iostream>
using namespace std;
#include <stdlib.h>
#include "FighterPlane.h"

void flyMatch(Flyable *f1,Flyable *f2)
{
    f1->takeoff();
    f1->land();
    f2->takeoff();
    f2->land();
}
int main(void)
{
    Plane p1("001");
    Plane p2("002");
    p1.printCode();
    p2.printCode();

    flyMatch(&p1,&p2);

    system("pause");
    return 0;
}
要求

4-2-RTTICode

Flyable.h

#ifndef FLYABLE_H
#define FLYABLE_H

class Flyable
{
public:
    virtual void takeoff() = 0;//起飞
    virtual void land() = 0; //降落
};

#endif

Plane.h

#ifndef PLANE_H
#define PLANE_H

#include <string>
#include "Flyable.h"
using namespace std;

class Plane :public Flyable
{
public:
    void carry();
    virtual void takeoff();
    virtual void land();
};

#endif

Plane.cpp

#include <iostream>
#include "Plane.h"
using namespace std;

void Plane::carry()
{
    cout << "Plane::carry()" << endl;
}
void Plane::takeoff()
{
    cout << "Plane::takeoff()" << endl;
}
void Plane::land()
{
    cout << "Plane::land()" << endl;
}

Bird.h

#ifndef BIRD_H
#define BIRD_H

#include "Flyable.h"
#include <string>
using namespace std;
class Bird :public Flyable
{
public:
    void foraging();
    virtual void takeoff();
    virtual void land();
};

#endif // !BIRD_H

Bird.cpp

#include <iostream>
#include "Bird.h"
using namespace std;

void Bird::foraging()
{
    cout << "Bird::foraging()" << endl;
}
void Bird::takeoff()
{
    cout << " Bird::takeoff()" << endl;
}
void Bird::land()
{
    cout << "  Bird::land()" << endl;
}

main.cpp:

#include <iostream>
#include "Bird.h"
#include "Plane.h"
using namespace std;
#include <stdlib.h>

void doSomething(Flyable *obj)
{
    cout << typeid(*obj).name() << endl;
    obj->takeoff();
    if (typeid(*obj) == typeid(Bird))
    {
        Bird *bird = dynamic_cast<Bird *>(obj);
            bird->foraging();
    }
    if (typeid(*obj) == typeid(Plane))
    {
        Plane *plane = dynamic_cast<Plane *>(obj);
        plane->carry();
    }

    obj->land();
}

int main()
{
    Bird b;
    doSomething(&b);

    system("pause");
    return 0;
}

运行结果:

要求

5-2-ErrorDeal

Exception.h

#ifndef EXCEPTION_H
#define EXCEPTION_H
class Exception
{
public:
    virtual void printException();
    virtual ~Exception() {}
};
#endif

Exception.cpp

#include "Exception.h"
#include <iostream>
using namespace std;

void Exception::printException()
{
    cout << " Exception::printException()" << endl;
}

IndexException.h

#ifndef INDEX_EXCEPTION_H
#define INDEX_EXCEPTION_H

#include "Exception.h"
class IndexException:public Exception
{
public:
    virtual void printException();
};
#endif

IndexException.cpp

#include "IndexException.h"
#include <iostream>
using namespace std;

void IndexException::printException()
{
    cout << "提示:下标越界" << endl;
}

main.cpp

#include <iostream>
#include <stdlib.h>
#include "IndexException.h"
using namespace std;
void test()
{
    throw 0.1;
}
int main(void)
{
    try
    {
        test();
    }
    catch (double)
    {
        cout << "exception" << endl;
    }
    system("pause");
    return 0;
}

throw 1.0, double类型捕获。

markmark
catch (double &e)
    {
        cout << e << endl;
    }
markmark

可以打印出抛出来的异常值:如0.1

main.cpp

#include <iostream>
#include <stdlib.h>
#include "IndexException.h"
using namespace std;
void test()
{
    throw IndexException();
}
int main(void)
{
    try
    {
        test();
    }
    catch (IndexException &e)
    {
        e.printException();
    }
    system("pause");
    return 0;
}

运行结果:

markmark

可以看到成功的捕获到了下标越界异常。

int main(void)
{
    try
    {
        test();
    }
    catch (Exception &e)
    {
        e.printException();
    }
    system("pause");
    return 0;
}

依然打印出数组的提示,父类的引用可以使用到子类的处理函数。

int main(void)
{
    try
    {
        test();
    }
    catch (...)
    {
        cout << "error" << endl;
    }
    system("pause");
    return 0;
}

通过(...)可以捕获到所有异常。

练习题

  • 在C++中异常处理通常使用try...catch...语法结构。
  • 一个try语句可以对应一个或多个catch语句,但不能没有catch语句
  • C++中使用throw抛出异常,通过catch捕获异常

巩固练习

函数division的两个参数为dividend(被除数)和divisor(除数)
要求用户输入除数和被除数,并作为参数传递给division函数
如果除数为0,则抛出异常,并被捕获,将异常的内容显示到屏幕上

#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;

/**
 * 定义函数division
 * 参数整型dividend、整型divisor
 */
int division(int dividend, int divisor)
{
    if(0 == divisor)
    {
        // 抛出异常,字符串“除数不能为0”
        throw string("除数不能为0");
    }
    else
    {
        return dividend / divisor;
    }
}

int main(void)
{
    int d1 = 0;
    int d2 = 0;
    int r = 0;
    cin >> d1;
    cin >> d2;
    // 使用try...catch...捕获异常
    try{
        r = division(d1,d2);
        cout << r << endl;
    }catch(string &str){
        cout << str <<endl;
    }

    return 0;
}

运行结果:

markmark

相关文章

网友评论

  • Y攻城狮:因为父类使用虚析构函数。所以释放内存。
    因为子类种有父类的同名函数,所以在子类的虚函数表中指向方法的指针被覆盖。
    实现了父类对象调用子类方法。
    纯虚函数 & 抽象类

    上面第二句话说错了吧?应该是父类的指针被覆盖
    天涯明月笙:@Y攻城狮 嗯嗯, 是的,这里应该是子类的虚函数表中原本指向父类方法的指针被覆盖为自己的函数指针。

本文标题:9-C++远征之多态篇-学习笔记

本文链接:https://www.haomeiwen.com/subject/ctwlmftx.html