C++ 中的 多态性

一 、多态性

1.多态性概述:多态是指同样的消息被不同类型的对象接受时导致不同的行为

2.多态实现:编译时的多态:在编译的过程中确定了同名操作的具体对象。

         运行时的多态:在程序运行过程中动态地确定操作所针对地具体现象。

                这种确定操作的具体对象的过程就是绑定——指计算机程序自身彼此关联的过程。

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


3.运算符重载

  1)运算符重载概念:对已有的运算符赋予多重含义,是同一个运算符作用于不同类型的数据时导致不同的行为。

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

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

             c)重载功能应与原有功能相类似,不能改变原运算符的操作对象个数,同时至少有一个操作对象是自定义类型

  3)重载形式

    a)类的非静态成员函数:

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

    b)重载为非成员函数:

      返回类型 operator 运算符(形参表){  函数体  }  【ps:二者在声明时不同】

    注意:当运算符重载为类的成员函数时,函数的参数个数比原来的操作数个数要少一个(后置“++”,“--”除外);

          当重载为非成员函数时,参数个数与原操作数个数相同

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

a)类的非静态成员函数

oprd1 B oprd2 ,其中oprd1 为A类对象,则应把B重载为A类的成员函数,该函数只有一个形参,形参类型是oprd2 的所属类型;

oprd++ 或 oprd--,其中oprd 为A类的对象,那么运算符就应该重载为A类的成员函数,这时函数要带一个整形(int)形参——用于辨析前置还是后置

 

用代码实现:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class A {
 5 private:
 6     int x;
 7 public:
 8     A(int x = 0) :x(x) {}
 9     A operator+(A &x);    //重载+
10     A& operator++();    //前置++
11     A operator++(int);    //后置++
12     friend ostream & operator<<(ostream &out, const A &a);    //重载<<
13     void show() { cout << x << endl; }
14 };
15 A A::operator+(A &x) {
16     this->x += x.x;
17     return *this;
18 }
19 A& A::operator++() {  //前置++
20     this->x += 1;
21     return *this;
22 }
23 A A::operator++(int) {  //后置++
24     A old = *this;
25     ++(*this);    //调用前置++函数
26     return old;
27 }
28 ostream &operator<<(ostream &out, const A &a) {
29     out << a.x << endl;
30     return out;
31 }
32 int main() {
33     A a(10), b(20);
34     cout << "a = " << a << endl << "b = " << b << endl;
35     cout <<"a+b="<< a + b << endl;
36     cout <<"a++ = "<< a++ << endl;
37     cout << "after a++ ,a = " << a << endl;
38     return 0;
39 }

执行结果:

 

 

 

 

b)重载为非成员函数

对于双目运算符B,实现oprd1 B oprd2 ,其中oprd1和oprd2中只要有一个具有自定义类型,则可以把B重载为非成员函数,该函数形参为oprd1 和 oprd2;

oprd++ 或 oprd--,其中oprd 为自定义类型,那么运算符就可以重载为非成员函数,这时形参1为oprd 另有一个形参2区别前置 后置(int)

 

我们用上述成员函数的例子来代码实现:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class A {
 5 private:
 6     int x;
 7 public:
 8     A(int x = 0) :x(x) {}
 9     friend A operator+(A a,A b);
10     friend A& operator++(A &a);
11     friend A operator++(A &a, int);
12     friend ostream & operator<<(ostream &out, const A &a);    //重载<<
13     void show() { cout << x << endl; }
14 };
15 A operator+(A a, A b) {  //非成员函数的重载+
16     A x;
17     x.x = a.x + b.x;
18     return x;
19 }
20 A& operator++(A &a) {    //非成员函数的前置++
21     a.x += 1;
22     return a;
23 }
24 A operator++(A &a, int) {  //非成员函数的后置++
25     A old = a;
26     ++a;
27     return old;
28 }
29 
30 ostream &operator<<(ostream &out, const A &a) {
31     out << a.x << endl;
32     return out;
33 }
34 int main() {
35     A a(9), b(19);
36     cout << "a = " << a << endl << "b = " << b << endl;
37     cout <<"a+b="<< a + b << endl;
38     cout <<"a++ = "<< a++ << endl;
39     cout << "after a++ ,a = " << a << endl;
40     return 0;
41 }

执行结果:

 

 

 

特别地:

“<<” 运算符的重载

ostream & operator << (ostream &out,const 类型说明符 &对象) 【注意着色的几个地方 是必要的】

 

4、虚函数

  1)一般虚函数成员的声明语法是:  virtual  函数类型  函数名 (形参表);

     虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定

我们用代码实现,理解虚函数的用处

代码如下:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Father {
 5 private:
 6     int x;
 7 public:
 8     Father(int x = 1) :x(x) {}
 9     virtual void show() { cout << "x of Father is " << x << endl; }
10 };
11 class Son :public Father{
12 private:
13     int x;
14 public:
15     Son(int x = 10) :x(x) {}
16     void show() { cout << "x of Son is " << x << endl; }
17 };
18 int main() {
19     Father f;
20     f.show();
21     Son s;
22     s.show();
23 
24     return 0;
25 }

执行结果:

 

可能这样没有什么说服力,因为上述代码中Father 的成员函数show()不加virtual 也可以实现上面这个结果

 

那我们接着看看下面两个代码的对比

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Father {
 5 private:
 6     int x;
 7 public:
 8     Father(int x = 1) :x(x) {}
 9     void show() { cout << "x of Father is " << x << endl; }        //不加virtual
10 };
11 class Son :public Father{
12 private:
13     int x;
14 public:
15     Son(int x = 10) :x(x) {}
16     void show() { cout << "x of Son is " << x << endl; }
17 };
18 int main() {
19     Father f;
20     f.show();
21     Son s;
22     s.show();
23 
24     Father* fs = &s;
25     fs->show();
26     return 0;
27 }
28     

主要看加粗代码,执行结果:

 

 

 

接着:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Father {
 5 private:
 6     int x;
 7 public:
 8     Father(int x = 1) :x(x) {}
 9     virtual void show() { cout << "x of Father is " << x << endl; }        //加virtual
10 };
11 class Son :public Father{
12 private:
13     int x;
14 public:
15     Son(int x = 10) :x(x) {}
16     void show() { cout << "x of Son is " << x << endl; }
17 };
18 int main() {
19     Father f;
20     f.show();
21     Son s;
22     s.show();
23 
24     Father* fs = &s;
25     fs->show();
26     return 0;
27 }
28     

依旧看加粗代码,执行结果:

 

 

 

 

这时我们发现 用基类指针指向派生类的对象时,出现了不一样的地方,使用了virtual 后派生类与基类的同名函数 在派生类中的那个函数彻底覆盖了基类原有的函数。

使用虚函数可以将一个基类函数在继承后,将这个基类函数的执行方式交给派生类掌控,这也是多态性的体现。

当然可以将基类声明为一个抽象类——即内容具有极高的可扩展性,却可以统一一些派生类所共同需要的函数(避免不同派生类取不同名的函数,却做相同的工作 or  基类指针指向它们的对象时,不能调用它们自己的函数)

例如

#include<iostream>
class Shape {
public:
    virtual double getArea()const = 0;
};
class Circle :public Shape {
private:
    double r = 1;
public:
    double getArea()const {
        return r * r*3.14;
    }
};
class Rectangle :public Shape {
private:
    double l = 1, w = 2;
public:
    double getArea()const {
        return w;
    }
};
void showArea(Shape*ptr) {
    std::cout << ptr->getArea() << std::endl;
}
int main() {
    Circle c;
    Rectangle r;
    showArea(&c);
    showArea(&r);
    return 0;
}

执行结果:

 

 正确得到结果

 

 

这里我们还是用了纯虚函数即在虚函数后加“=0”可以彻底把这一函数交给派生类去掌控,纯虚函数在基类中可以不实现。

还有在重写继承来的虚函数时,如果函数有默认的形参值,不要重新定义不同的值,举个‘栗子’

代码:

#include<iostream>
using namespace std;

class A {
public:
    virtual void show(int x = 1) { 
        cout << "x = " << x << endl; 
    }
};
class B :public A {
public:
    void show(int x = 2) {   //更改形参,原本应该 x = 1
        cout <<"x = "<< xx << endl; 
    }
};
int main() {
    B b;
    A *ptr = &b;
    ptr->show();
    return 0;
}

更改前:

输出 x = 1;

 

 更改后:

输出 x = 1;

 

这是因为,虚函数是动态绑定的,但默认形参是静态绑定的,也就是说通过一个指向派生类的基类指针,可以访问到派生类的虚函数,但是默认形参却只能来自基类定义

 

 

还有一点,将基类的析构函数申明为虚函数的优点(值得变为习惯)!!!

用代码说明:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class Base {
 5 public:
 6     Base() { cout << "Constructing of Base" << endl; }
 7     virtual void fun1() { cout << "fun1 of Base" << endl; }
 8     void fun2() { cout << "fun2 of Base" << endl; }
 9     ~Base() { cout << "Destructing of Base" << endl; }    //加virtual 派生类可以被析构,不加,则可能造成内存泄漏
10 };
11 class Derived:public Base {
12 public:
13     Derived() { cout << "Constructing of Derived" << endl; }
14     void fun1() { cout << "fun1 of Derived" << endl; }
15     void fun2() { cout << "fun2 of Derived" << endl; }
16     ~Derived() { cout << "Destructing of Derived" << endl; }
17 };
18 int main() {
19     Base*ptr = new Derived;
20 
21     delete ptr;
22 
23 
24     return 0;
25 }

执行结果:

 

 

 

如果基类的析构函数为虚函数:

执行结果:

 

 

很明显这样将动态分配的派生类对象的内存也释放了,而如果是第一种情况就容易造成内存泄漏

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

为什么使用虚函数?

如果派生类需要修改基类的行为(即重写与基类函数同名的函数)就应该在基类中将相应的函数声明为虚函数;

相反的,在基类中不希望被派生类更改的就应该声明为非虚函数。

 

 

补充知识:

dynamic_cast 的使用

目的:将执行基类向派生类转换

   理解:基类指针本来只能指向基类的成员,哪怕是指向派生类对象,这个指针仍旧只能指向基类的成员(先除去virtual),

                     现在用dynamic_cast可以转换基类指针

使用条件:1.积累的指针或引用确实绑定到派生类的对象上;2.只有当基类至少含有一个虚函数即基类是多态类

代码实现:

#include<iostream>

class base{
public:
    int x = 99;
    virtual ~base() {}    //这里定义为虚函数很重要,必须是多态类才可以dynamic_cast
};
class derived :public base {
public:
    int y = 21;
    ~derived() {}
};
int main() {
    base *ptr = new derived;
    if (dynamic_cast<derived*>(ptr) != NULL) {        //因为如果转换失败会返回NULL
        std::cout << dynamic_cast<derived*>(ptr)->y << std::endl;
    }
    if (typeid(*ptr) == typeid(derived)) {
        std::cout << "1" << std::endl;
    }
    return 0;
}

使用typeid 只能判断一个对象是否为某个具体类型,而不会把它的子类型也包括在内。如果要达到判断一个对象是否为某个类型或其子类型的目的,还是用dynamic_cast更方便。

 

此处知识还推荐:

https://blog.csdn.net/qq_31073871/article/details/79910328

===============================

以上为现阶段的学习,如果有误望指正:)

 

posted @ 2019-10-23 17:19  果冻小布丁  阅读(610)  评论(0编辑  收藏  举报