面向对象——多态

多态性是指发出同样消息被不同对象接收时有可能导致完全不同的行为。

 

多态的实现:

  • 函数重载
  • 运算符重载
  • 虚函数

静态的多态:编译时的多态(函数重载)

动态的多态:运行时的多态(虚函数)

 

运算符重载(不使用友元):

#include <iostream>

using namespace std;

class Complex {
   public:
    Complex(double a = 0.0, double b = 0.0) {
        r = a;
        i = b;
    }
    void display() { cout << "Real=" << r << '\t' << "Image=" << i << '\n'; }
    Complex operator+(Complex&);

   private:
    double r, i;
};

Complex Complex::operator+(Complex& t1) {
    double r, i;
    r = t1.r + this->r;
    i = t1.i + this->i;
    Complex result(r, i);
    return result;
}

int main() {
    Complex c1(3.2, 1.2), c2(1.1, 1.1), c3;
    c3 = c1 + c2;
    c3.display();
    return 0;
}

 

拷贝构造函数

#include <iostream>

using namespace std;

class Complex {
public:
    Complex(double a = 0.0, double b = 0.0) {
        r = a;
        i = b;
        cout << r << " is construct.......\n";
    }
    Complex(const Complex& t) {
        r = t.r;
        i = t.i;
        cout << r << " is copy construct.......\n";
    }
    ~Complex() { cout << r << " is deconstruct.......\n"; }
    void display() { cout << "Real=" << r << '\t' << "Image=" << i << '\n'; }
    Complex operator+(Complex&);

private:
    double r, i;
};

Complex Complex::operator+(
    Complex& t1) {  // 如果不使用引用的话,这里会调用一次拷贝构造函数
    double r, i;
    r = t1.r + this->r;
    i = t1.i + this->i;
    Complex result(r, i);
    return result;
}

int main() {
    Complex c1(3.2, 1.2), c2(1.1, 1.1), c3;
    c3 = c1 + c2;        // 这里也会执行一次拷贝构造函数
    c3.display();
    return 0;
}

 

虚函数

联编:把函数名与函数体的程序代码连接在一起的过程

 

静态联编:

  • 在编译阶段完成的联编
  • 通过匹配实参和形参,确定调用哪个函数
  • 要求在编译时就知道调用函数的全部信息
  • 静态联编的函数调用速度很快,效率高,但缺乏灵活性

 

动态联编:

指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况动态地确定

 

虚函数表:

  首先先定义两个类,他们都继承自People类

 

  物理结构

 

  p指针指向manager的物理地址,在调用doWork()时,会在manager的虚函数表中,寻找manager的doWork()方法

void dispatch_work(Employee* p) {
    p->doWork();
}
dispatch_work(&manager);

  虚函数只对指针或引用(满足赋值兼容规则的指针或引用)有意义,如果不是指针和引用类型的话虚函数就表现不出多态的特性了。

 

 

 

#include <string.h>

#include <iostream>

using namespace std;

class Student {
   public:
    Student(int n, string nam, float s) {
        num = n;
        name = nam;
        score = s;
    }
    // void display() {
    //     cout << "num:" << num << "\nname:" << name << "\nscore:" << score
    //          << "\n\n";
    // }

    virtual void display() {
        cout << "num:" << num << "\nname:" << name << "\nscore:" << score
             << "\n\n";
    }

   protected:
    int num;
    string name;
    float score;
};
class Graduate : public Student {
   public:
    Graduate(int n, string nam, float s, float p)
        : Student(n, nam, s), pay(p) {}
    // 如果基类中是虚函数,派生类中的同名函数会自动的变成虚函数
    void display() {
        cout << "num:" << num << "\nname:" << name << "\nscore:" << score
             << "\npay=" << pay << endl;
    }

   private:
    float pay;
};
int main() {
    Student stud1(1001, "Li", 87.5);
    Graduate grad1(2001, "Wang", 98.5, 563.5);
    Student *pt = &stud1;
    pt->display();
    pt = &grad1;
    pt->display();
    return 0;
}

不使用虚函数的情况:

 使用虚函数的情况:

(体会虚函数带来的多态的效果)

 

另一个例子:

#include <iostream>

using namespace std;

class Grandam {
   public:
    virtual void introduce_self() { cout << "I am grandam." << endl; }
};
class Mother : public Grandam {
   public:
    void introduce_self() { cout << "I am mother." << endl; }
};
class Daughter : public Mother {
   public:
    void introduce_self() { cout << "I am daughter." << endl; }
};

int main() {
    Grandam *ptr;
    Grandam g;
    Mother m;
    Daughter d;
    ptr = &g;
    ptr->introduce_self();
    ptr = &m;
    ptr->introduce_self();
    ptr = &d;
    ptr->introduce_self();
    return 0;
}

 

 

重写与重载

派生类中重新定义基类的虚函数是函数重载的另种形式,不同于普通的函数重载。

普通函数重载:函数参数或者参数类型必须有所不同,函数的返回值也可以不同

重载虚函数:要求派生类的函数名,参数个数,参数类型以及顺序,函数返回值必须相同,如果不同,则按照普通重载来对待,丢失虚函数的特性。

 

#include <iostream>

using namespace std;

class Base {
   public:
    virtual void func1() { cout << "--Base func1--\n"; }
    virtual void func2() { cout << "--Base func2--\n"; }
    void func3() { cout << "--Base func3--\n"; }
};
class Derived : public Base {
   public:
    virtual void func1() { cout << "--Derived func1--\n"; }
    void func2(int x) { cout << "--Derived func2--\n"; }
    void func3() { cout << "--Derived func3--\n"; }
};

int main() {
    Base *bp;
    Derived d2;
    bp = &d2;
    bp->func1();
    bp->func2();
    bp->func3();
    return 0;
}

 

 析构函数+virtual

基类的析构函数尽量写成虚函数

#include <iostream>

using namespace std;

class Grandam {
   public:
    Grandam() { cout << "Grandam\n"; }
    //~Grandam() { cout << "~Grandam." << endl; }
    virtual ~Grandam() { cout << "~Grandam." << endl; }
};
class Mother : public Grandam {
   public:
    Mother() { cout << "Mother\n"; }
    ~Mother() { cout << "~Mother()." << endl; }
};
int main() {
    Grandam *f;
    f = new Mother;
    delete f;
    return 0;
}

 

基类析构函数没有写成虚函数:

 

 存在内存泄漏的问题

析构函数写成虚函数:

 

 纯虚函数→抽象类

带有虚函数的类称为抽象类

对于暂时无法实现的函数,可以声明为抽象类,留给派生类去实现

注意:

  • 抽象类不能被实例化
  • 抽象类只能作为积累来使用
  • 构造函数不能是虚函数,析构函数可以是虚函数

 class 类名 {

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

};

 

#include <iostream>

using namespace std;

class Figure {
   public:
    Figure(double a, double b) {
        x = a;
        y = b;
    }
    // virtual void show_area() { cout << "No area computation defined\n"; }
    virtual void show_area() = 0;

   protected:
    double x, y;
};
class Triangle : public Figure {
   public:
    Triangle(double a, double b) : Figure(a, b) {}
    void show_area() { cout << "Triangle : " << x * y * 0.5 << endl; }
};
class Square : public Figure {
   public:
    Square(double a, double b) : Figure(a, b) {}
    void show_area() { cout << "Square :" << x * y << endl; }
};
class Circle : public Figure {
   public:
    Circle(double a) : Figure(a, a) {}
    void show_area() { cout << "Circle: " << x * x * 3.1416 << endl; }
};
int main() {
    Figure *p;  // 基类对象的指针可以指向任意的派生类,反之不然
    Triangle t(10.0, 6.0);
    Square s(10.0, 6.0);
    Circle c(10.0);
    p = &t;
    p->show_area();
    p = &s;
    p->show_area();
    p = &c;
    p->show_area();
    return 0;
}

没有使用纯虚函数:

 使用纯虚函数:

 从结构中我们可以看出,有没有使用纯虚函数的输出结果都是一样的,但是,使用纯虚函数会更加的符合逻辑,是代码结构更加的合理。

 

空类

首先,我们来观察一个程序的执行结果

#include <iostream>
using namespace std;

class Animal {};

int main() {
    cout << "This sizeof(Animal):" << sizeof(Animal) << endl;
    return 0;
}

为什么一个空类会占有1字节的空间呢?

  实际上,这是类结构体实例化的原因,空的类或结构体同样可以被实例化,如果定义对空的类或者结构体取sizeof()的值为0,那么该空的类或结构体实例化出很多实例时,在内存地址上就不能区分该类实例化出的实例。所以,为了实现每个实例在内存中都有一个独一无二的地址,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以空类所占的内存大小是1个字节。
 
再看下面这个程序
#include <iostream>
using namespace std;

class Animal {
   public:
    virtual void func() {}
};

int main() {
    cout << "This sizeof(Animal):" << sizeof(Animal) << endl;
    return 0;
}

 这里显示一个空类所占的内存空间大小为4字节,这四个字节是一个指向虚函数表的指针。

 

posted @ 2021-05-17 16:21  Veritas_des_Liberty  阅读(92)  评论(0编辑  收藏  举报