zoukankan      html  css  js  c++  java
  • C++基础知识-Day8

    2.类的作用域运算符

    shadow

    在我们之前讲的内容中,我们会发现一种情况,就是在我们在不同类中的打印函数我们都是尽量让其名字不同,那么为什么会有这种情况呢?首先我们来看一个函数

    void func()
        {
            cout<<"B::void func()"<<endl;
            func();
        }

    运行程序会发现这是一个死循环,因为其存在自己调用自己的情况,那么放在类中会是什么样子的呢

    #include <iostream>
    
    using namespace std;
    class A
    {
    public:
        void foo()
        {
            cout<<"A::void foo()"<<endl;
        }
    };
    class B:public A
    {
    public:
        void foo()
        {
            cout<<"B::void foo()"<<endl;
            foo();//实际上这里是有一个this指针指向foo的
        }
    };
    int main()
    {
        B b;
        b.foo();
        return 0;
    }

     这样调用还是会出现死循环的情况,虽然其本意是在类B中的foo调用类A中的foo,但是由于this指针指向foo并且由于类中的两个函数重名,因此会出现死循环,为了解决这个问题,引入类的作用域运算符,将类B中的foo函数写成如下形式

    void foo()
        {
            cout<<"B::void foo()"<<endl;
            A::foo();
        }

     shadow产生机理

    (1)  在父子类中出现重名的标识符(函数成员和数据成员),就会构成shadow,如果想访问被shadow的成员,加上父类的命名空间

    (2)  shadow在父子类中的标识符只有一个,就是重名,不论返回值,参数不同什么

     3. 继承的方式详解

    继承的方式有三种:public,protected和private,但是我们一般都用public

    所有的继承必须是public的,如果想私有继承的话,应该采用将基类实例作为成员的方式作为替代

    一般情况下,在一个类中,public常用于接口,protected常用于数据,private常用于隐私

    那么为什么public是用的最多的呢

     如果多级派生中,均采用public,直到最后一级,派生类中均可访问基类的public,protected,很好的做到了接口的传承,保护数据以及隐私的保护

     protected:封杀了对外的接口,保护数据成员,隐私保护

    public:传承接口,间接地传承了数据(protected)

    protected:传承数据,间接封杀了对外接口(public)

    private:统杀了数据和接口

    4. 类的作用域运算符

    shadow产生机理

    (1)  在父子类中出现重名的标识符(函数成员和数据成员),就会构成shadow,如果想访问被shadow的成员,加上父类的命名空间

    (2)  shadow在父子类中的标识符只有一个,就是重名,不论返回值,参数不同什么

    5. 多重继承

    从继承类别来说,继承可以分为单继承和多继承

    多继承的意义:

    俗话讲,鱼和熊掌不可兼得,而在计算机中可以实现,生成一种新的对象,叫熊掌鱼,多继承自鱼和熊掌即可

    继承语法:

    派生类名:public 基类名1,public 基类名2,…,protected 基类名n

    构造器格式

    派生类名:派生类名(总参列表)

           :基类名1(参数表1),基类名2(参数名2),…基类名n(参数名n),

           内嵌子对象1(参数表1),内嵌子对象2(参数表2)…内嵌子对象n(参数表n)

    {

           派生类新增成员的初始化语句

    }

    多继承可能存在的问题

    (1)  三角问题

    多个父类中重名的成员,继承到子类中后,为了避免冲突,携带了各父类的作用域信息,子类中要访问继承下来的重名成员,则会产生二义性,为了避免冲突,访问时需要提供父类的作用域信息

    构造器问题

    下面我们用一个实际的例子来对其进行讲解

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class X
     6 {
     7 public:
     8     X(int d)
     9     {
    10         cout<<"X()"<<endl;
    11     }
    12 protected:
    13     int _data;
    14 };
    15 
    16 class Y
    17 {
    18 public:
    19     Y(int d)
    20     {
    21         cout<<"Y()"<<endl;
    22     }
    23 protected:
    24     int _data;
    25 };
    26 
    27 class Z:public X,public Y
    28 {
    29 public:
    30     Z()
    31         :X(1),Y(2)
    32     {
    33 
    34     }
    35     void dis()
    36     {
    37         cout<<Y_data<<endl;39     }
    40 };
    41 
    42 int main()
    43 {
    44     Z z;
    45     z.dis();
    46     return 0;
    47 }

    直接这样的话会报错,因为_data会产生二义性,为了解决这个问题,我们可以在数据之前加上其父类作用域

    1 void dis()
    2     {
    3         cout<<Y::_data<<endl;
    4         cout<<X::_data<<endl;
    5     }

    下面我们看一个有趣的情况

    #include <iostream>
    
    using namespace std;
    
    class X
    {
    public:
        X(int d)
        {
            cout<<"X()"<<endl;
            _data=d;
        }
        void setData(int d)
        {
            _data=d;
        }
    protected:
        int _data;
    };
    
    class Y
    {
    public:
        Y(int d)
        {
            cout<<"Y()"<<endl;
            _data=d;
        }
        int getData()
        {
            return _data;
        }
    protected:
        int _data;
    };
    
    class Z:public X,public Y
    {
    public:
        Z(int i,int j)
            :X(i),Y(j)
        {
    
        }
        void dis()
        {
            cout<<X::_data<<endl;
            cout<<Y::_data<<endl;
        }
    };
    
    int main()
    {
        Z z(100,200);
        z.dis();
        cout<<"================="<<endl;
        z.setData(1000000);
        cout<<z.getData()<<endl;
        cout<<"================="<<endl;
        z.dis();
        return 0;
    }

    在这里我们getData得到的数据仍然是200,并不是setData的1000000,原因如下

    刚开始的时候,在类X和类Y中,都有一个_data,

     当其继承在类Z中后

    由于是重名的问题,setData设置的是类X中的数据,但是getData得到的是类Y中的数据,所以说会出现问题

    那么我们应该怎么来解决这个问题呢

    需要解决的问题:

    数据冗余

    访问方便

    由此引发了一个三角转四角的问题

    1. 提取各父类中相同的成员,包括数据成员和函数成员,构成祖父类
    2. 让各父类,继承祖父类
    3. 虚继承是一种继承的扩展,virtual

    首先解决初始化问题,

    祖父类的好处是,祖父类是默认的构造器,因此在父类中,并不需要显示地调用,按道理说,Z中有类X,Y,只需要管X,Y的初始化就可以了

    #include <iostream>
    
    using namespace std;
    
    //祖父类
    class A
    {
    protected:
        int _data;
    };
    //父类继承祖父类
    class X:virtual public A
    {
    public:
        X(int d)
        {
            cout<<"X()"<<endl;
            _data=d;
        }
        void setData(int d)
        {
            _data=d;
        }
    
    };
    //各父类继承祖父类
    class Y:virtual public A
            //虚继承
    {
    public:
        Y(int d)
        {
            cout<<"Y()"<<endl;
            _data=d;
        }
        int getData()
        {
            return _data;
        }
    };
    
    class Z:public X,public Y
    {
    public:
        Z(int i,int j)
            :X(i),Y(j)
        {
    
        }
        void dis()
        {
            cout<<_data<<endl;
        }
    };
    
    int main()
    {
        Z z(100,200);
        z.dis();
        cout<<"================="<<endl;
        z.setData(1000000);
        cout<<z.getData()<<endl;
        cout<<"================="<<endl;
        z.dis();
        return 0;
    }

    这样就带来了两个好处,解决了数据冗余的问题,并且为访问带来了便利,虚继承也是一种设计的结果,被抽象上来的类叫做虚基类。也可以说成:被虚继承的类称为虚基类

    虚基类:被抽象上来的类叫做虚基类

    虚继承:是一种对继承的扩展

    那么虚继承就有几个问题需要我们来注意了,首先是初始化的顺序问题,为了测试初始化的顺序问题,因为上述都是构造器的默认情况,但是实际情况中,可能都会带参数,甚至是虚继承的祖父类也会带参数,那么构造器顺序又将是如何的呢?我们利用如下代码进行测试

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class A
     6 {
     7 public:
     8     A(int i)
     9     {
    10         _data=i;
    11         cout<<"A(int i)"<<endl;
    12     }
    13 protected:
    14     int _data;
    15 };
    16 class B:virtual public A
    17 {
    18 public:
    19     B(int i)
    20         :A(i)
    21     {
    22         _data=i;
    23         cout<<"B(int i)"<<endl;
    24     }
    25 };
    26 
    27 class C:virtual public A
    28 {
    29 public:
    30     C(int i)
    31         :A(i)
    32     {
    33         _data=i;
    34         cout<<"C(int i)"<<endl;
    35     }
    36 };
    37 
    38 class D:public C,B
    39 {
    40 public:
    41     D()
    42         :C(1),B(1),A(1)
    43     {
    44         cout<<"D(int i)"<<endl;
    45     }
    46     void dis()
    47     {
    48         cout<<_data<<endl;
    49     }
    50 };
    51 int main()
    52 {
    53     D d;
    54     d.dis();
    55     return 0;
    56 }

    运行代码后我们可以得知,构造的顺序是从祖父类的构造器开始,按照顺序执行下来,最后到孙子类的构造器为止的

    当然,上述只是一个测试,因为在实际过程中,祖父类是由父类抽象起来的,因此一般不会用祖父类生成对象 

    在实际过程中,在父类的构造器中我们常带默认参数,这样我们就可以不使得派生类的构造器如此复杂

    实际例子,沙发床,除了上述之外,我们还需要增加颜色和重量,除此之外,我们还需要用descript函数来对其进行描述

    #include <iostream>
    
    using namespace std;
    
    
    class Furniture
    {
    public:
        void descript()
        {
            cout<<"_weight:"<<_weight<<endl;
            cout<<"_color :"<<_color<<endl;
        }
    protected:
        float _weight;
        int _color;
    };
    class Sofa:virtual public Furniture
    {
    public:
        Sofa(float w=0,int c=1)
        {
            _weight=w;
            _color=c;
        }
        void sit()
        {
            cout<<"take a sit and have a rest"<<endl;
        }
    };
    
    class Bed:virtual public Furniture
    {
    public:
        Bed(float w=0,int c=1)
        {
            _weight=w;
            _color=c;
        }
        void sleep()
        {
            cout<<"have a sleep ......."<<endl;
        }
    
    };
    
    class SofaBed:public Sofa,public Bed
    {
    public:
        SofaBed(float w,int c)
        {
            _weight=w;
            _color=c;
        }
    };
    
    int main()
    {
        SofaBed sb(1000,2);
        sb.sit();
        sb.sleep();
        sb.descript();
        return 0;
    }
    
    int main1()
    {
        Sofa sf;
        sf.sit();
        Bed bd;
        bd.sleep();
        return 0;
    }

     6. 多态

    (1)  生活中的多态

    如果有几个相似而不完全相同的对象,有时人们要求在向他们发出同一个消息时,他们的反应各不相同,分别执行不同的操作,这种情况就是多态现象

    (2)  C++ 中的多态

    C++ 中的多态是指,由继承而产生的相关的不同的类,其对同一消息会做出不同的响应

    比如,Mspaint中的单击不同图形,执行同一拖动动作而绘制不同的图形,就是典型的多态应用

    多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性,可以减轻系统的升级,维护,调试的工作量和复杂度

    (3)  赋值兼容

    赋值兼容是指,在需要基类对象的任何地方,都可以使用共有派生的对象来替代

    只有在共有派生类中才有赋值兼容,赋值兼容是一种默认行为,不需要任何的显示的转化步骤

    赋值兼容总结起来有以下三种特点

    派生类的对象可以赋值给基类对象

    派生类的对象可以初始化基类的引用

    派生类对象的地址可以赋给指向基类的指针

    下面我们将分别对其进行说明

    • 派生类的对象可以赋值给基类对象

    观察下面代码

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class Shape
     6 {
     7 public:
     8     Shape(int x=0,int y=0)
     9         :_x(x),_y(y){}
    10     void draw()
    11     {
    12         cout<<"draw shape from"<<"("<<_x<<","<<_y<<")"<<endl;
    13     }
    14 protected:
    15     int _x;
    16     int _y;
    17 };
    18 class Circle:public Shape
    19 {
    20 public:
    21     Circle(int x=0,int y=0,int r=1)
    22         :Shape(x,y),_radius(r){}
    23     void draw()
    24     {
    25         cout<<"draw shape from"<<"("<<_x<<","<<_y<<")"<<"radius:"<<_radius<<endl;
    26     }
    27 protected:
    28     int _radius;
    29 };
    30 int main()
    31 {
    32     Shape s(1,2);
    33     s.draw();
    34     Circle c(4,5,6);
    35     c.draw();
    36     s=c; //派生类对象可以赋值给基类对象
    37     s.draw();
    38     return 0;
    39 }

     有上述例子可以看出,派生类的对象是可以复制给基类对象的

    • 派生类的对象可以初始化基类的引用
    1 int main()
    2 {
    3     Shape s(1,2);
    4     s.draw();
    5     Circle c(4,5,6);
    6     Shape &rs=c;
    7     rs.draw();
    8     return 0;
    9 }
    • 派生类的对象的地址可以赋给指向基类的指针
    1 int main()
    2 {
    3     Shape s(1,2);
    4     s.draw();
    5     Circle c(4,5,6);
    6     Shape *ps=&c;
    7     ps->draw();
    8     return 0;
    9 }

     在这三种情况中,使用的最多的是第三种,即派生类对象的地址可以赋给指向基类的指针

    就如图示一样,假设左边的类是父类,右边的类是子类,,左边的指针是派生类的对象的地址赋给指向派生类的指针,那么其可访问的范围就是整个派生类,右边的指针是派生类的对象的地址赋给指向基类的指针,那么其访问范围就只有基类的那一部分

    7. 多态

    多态分为静多态和动多态

    静多态,就是我们说的函数重载,表面上,是由重载规则来限定的,内部实现却是Namemangling,此种行为,发生在编译期,故称为静多态

    (动)多态,不是在编译阶段决定,而是在运行阶段决定,故称动多态,动多态的形成条件如下

    多态实现的条件

    父类中有虚函数(加virtual,是一个声明型关键字,即只能在声明中有,在实现中没有),即公用接口

     子类override(覆写)父类中的虚函数

    通过已被子类对象赋值的父类指针,调用共有接口

     

    下面分别对这些条件进行讲解

    • 父类中有虚函数(加virtual,是一个声明型关键字,即只能在声明中有,在实现中没有),即公用接口

    virtual函数是一个声明型关键字,只能在声明中有,在实现中没有

    class A
    {
    public:
        A(){};
        virtual void draw();
    private:
        int _x;
    }
    void A::draw()
    {
        cout<<_x<<endl;
    }

    假设在实现的过程中也加入virtual关键字,即

    virtual void A::draw()
    {
        cout<<_x<<endl;
    }

    系统即会开始报错

    •  子类覆写父类中的虚函数,子类中同名同参同函数,才能构成覆写
    • 通过已被子类对象赋值的父类指针,调用虚函数,形成多态
     1 #include <iostream>
     2 #include <typeinfo>
     3 using namespace std;
     4 
     5 class Shape
     6 {
     7 public:
     8     Shape(int x=0,int y=0)
     9         :_x(x),_y(y)
    10     {
    11         cout<<"shape->this"<<this<<endl;
    12         cout<<typeid(this).name()<<endl;
    13     }
    14     virtual void draw()
    15     {
    16         cout<<"draw shape from"<<"("<<_x<<","<<_y<<")"<<endl;
    17     }
    18 protected:
    19     int _x;
    20     int _y;
    21 };
    22 class Circle:public Shape
    23 {
    24 public:
    25     Circle(int x=0,int y=0,int r=1)
    26         :Shape(x,y),_radius(r)
    27     {
    28         cout<<"shape->this"<<this<<endl;
    29         cout<<typeid(this).name()<<endl;
    30     }
    31     void draw()
    32     {
    33         cout<<"draw shape from"<<"("<<_x<<","<<_y<<")"<<"radius:"<<_radius<<endl;
    34     }
    35 protected:
    36     int _radius;
    37 };
    38 
    39 
    40 class Rect:public Shape
    41 {
    42 public:
    43     Rect(int x=0,int y=0,int w=0,int l=0)
    44         :Shape(x,y),_width(w),_lenth(l){}
    45     virtual void draw()
    46     {
    47         cout<<"draw Circle from"<<"("<<_x<<","<<_y<<")"
    48            <<""<<_width<<"lenth:"<<_lenth<<endl;
    49     }
    50 protected:
    51 
    52     int _width;
    53     int _lenth;
    54 };
    55 
    56 
    57 int main()
    58 {
    59     Circle c(3,4,5);
    60     Shape *ps=&c;//父类指针指向子类的对象
    61     ps->draw();
    62 
    63     Rect r(6,7,8,9);
    64     ps=&r;
    65     ps->draw();
    66     return 0;
    67 }

    可以看出,利用virtual,可以实现多态

    通过父类的指针调用父类的接口指向其本来应该指向的内容

     1 int main()
     2 {
     3     Circle c(3,4,5);
     4     Shape *ps=&c;//父类指针指向子类的对象
     5     ps->draw();
     6 
     7     Rect r(6,7,8,9);
     8     ps=&r;
     9     ps->draw();
    10     while(1)
    11     {
    12         int choice;
    13         cin>>choice;
    14         switch(choice)
    15         {
    16             case 1:
    17                 ps=&c;
    18                 break;
    19             case 2:
    20                 ps=&r;
    21                 break;
    22         }
    23         ps->draw();
    24     }
    25     return 0;
    26 }

    一个接口呈现出不同的行为,其中virtual是一个声明型关键字,用来声明一个虚函数,子类覆写了的函数,也是virtual 

    虚函数在子函数中的访问属性并不影响多态,要看子类

    虚函数和多态总结

    (1)virtual是声明函数的关键字,他是一个声明型关键字

    (2)override构成的条件,发生在父子类的继承关系中,同名,同参,同返回

    (3)虚函数在派生类中仍然为虚函数,若发生覆写,最好显示的标注virtual

    (4)子类中覆写的函数,可以为任意的访问类型,依子类需求决定

    8. pure virtual function

    纯虚函数,指的是virtual修饰的函数,没有实现体,被初始化为0,被高度抽象化的具有纯接口类才配有纯虚函数,含有纯虚函数的类称为抽象基类

    抽象基类不能实例化(不能生成对象),纯粹用来提供接口用的

    子类中若无覆写,则依然为纯虚,依然不能实例化

    9. 总结

    (1)纯虚函数只有声明,没有实现,被“初始化”为0

    (2)含有纯虚函数的类,称为Abstract Base Class(抽象基类),不能实例化,即不能创造对象,存在的意义就是被继承,而在派生类中没有该函数的意义

    (3)如果一个中声明了纯虚函数,而在派生类中没有该函数的定义,则该虚函数在派生类中仍然为虚函数,派生类仍然为纯虚基类

    10. 析构函数

    含有虚函数的类,析构函数也应该声明为虚函数

    这是为了保证对象析构的完整性,具体的情况就是父类的指针指向子类的堆对象,此时通过父类指针去析构子类堆对象时就会虚构不完整,为了保证析构的完整性,含有虚函数的类将其析构函数也声明为虚函数(virtual)

    对比栈对象和对对象在多态中销毁的不同

    首先我们来看位于栈上的对象

    在这里,我们生成了几个类,一个是抽象基类,一个是Dog类,一个是Cat类,我们分别在class中去构造这几个类

    首先生成Animal类

    其.h文件的内容如下

     1 #ifndef ANIMAL_H
     2 #define ANIMAL_H
     3 class Animal
     4 {
     5 public:
     6     Animal();
     7     ~Animal();
     8     virtual void voice()=0;
     9 };
    10 #endif // ANIMAL_H

    其.cpp文件中的内容如下

     1 #include "animal.h"
     2 #include <iostream>
     3 using namespace std;
     4 Animal::Animal()
     5 {
     6     cout<<"Animal::Animal()"<<endl;
     7 }
     8 
     9 Animal::~Animal()
    10 {
    11     cout<<"Animal::~Animal()"<<endl;
    12 }

    然后我们再生成Dog的.h文件

     1 #ifndef DOG_H
     2 #define DOG_H
     3 #include "animal.h"
     4 class Animal;
     5 class Dog : public Animal
     6 {
     7 public:
     8     Dog();
     9     ~Dog();
    10 
    11     virtual void voice();
    12 };
    13 #endif // DOG_H

    然后我们再生成Dog的.cpp文件

     1 #include "dog.h"
     2 #include "animal.h"
     3 #include <iostream>
     4 using namespace std;
     5 Dog::Dog()
     6 {
     7     cout<<"Dog::Dog()"<<endl;
     8 }
     9 
    10 Dog::~Dog()
    11 {
    12     cout<<"Dog::~Dog()"<<endl;
    13 }
    14 
    15 void Dog::voice()
    16 {
    17     cout<<"wang wang wang"<<endl;
    18 }

    然后我们生成Cat类

    首先生成Cat的.h文件

     1 #ifndef CAT_H
     2 #define CAT_H
     3 #include "animal.h"
     4 class Cat : public Animal
     5 {
     6 public:
     7     Cat();
     8     ~Cat();
     9 
    10     virtual void voice();
    11 };
    12 #endif // CAT_H

    然后再生成cat的.cpp文件

     1 #include "cat.h"
     2 #include "animal.h"
     3 #include <iostream>
     4 using namespace std;
     5 Cat::Cat()
     6 {
     7     cout<<"Cat::Cat()"<<endl;
     8 }
     9 Cat::~Cat()
    10 {
    11     cout<<"Cat::~Cat()"<<endl;
    12 }
    13 void Cat::voice()
    14 {
    15     cout<<"miao miao miao"<<endl;
    16 }

    最后,main函数如下

     1 #include <iostream>
     2 #include "animal.h"
     3 #include "cat.h"
     4 #include "dog.h"
     5 using namespace std;
     6 
     7 int main()
     8 {
     9     Cat c;
    10     Dog d;
    11     Animal *pa=&c;
    12     pa->voice();
    13     return 0;
    14 }

    生成的结果为

    可以看出其是析构完全了的

    但是若为栈上的对象,即主函数改写为如下

     1 #include <iostream>
     2 #include "animal.h"
     3 #include "cat.h"
     4 #include "dog.h"
     5 using namespace std;
     6 
     7 int main()
     8 {
     9     Animal *pa=new Dog;
    10     pa->voice();
    11     delete pa;
    12     return 0;
    13 }

    得出的结果为

    可以看出其是没有析构完全的,生成的Dog是没有析构的,因此对于堆上的对象,其是析构器有问题的

    我们只需要解决如下

    但凡类中含有虚函数(包括纯虚函数),将其虚构函数置为virtual ,这样即可以实现完整虚构

    12.设计模式的原则:依赖倒置原则-核心思想:面向接口编程

    传统的过程式设计倾向于使高层次的模块依赖于低层次的模块(自顶向下,逐步细化),而依据DIP的设计原则,将中间层抽象为抽象层,让高层模块和底层模块依赖于中间层

    以一个例子来进行举例,用母亲给给孩子讲故事来进行举例

    原本母亲给孩子讲故事是依赖于故事书上的内容,因此对于母亲给孩子讲故事我们可以写成如下代码

     1 //Mother 依赖于 Book  依赖->耦合    -->低耦合
     2 class Book
     3 {
     4 public:
     5     string getContents()
     6     {
     7         return "从前有座山,山里有座庙,庙里有个小和尚."
     8                 "听老和尚讲故事,从前有座山";
     9     }
    10 };
    11 class Mother
    12 {
    13 public:
    14     void tellStory(Book &b)
    15     {
    16         cout<<b.getContents()<<endl;
    17     }
    18 };

    在这里,母亲和书的关系是一种强耦合关系

    即只要书的内容发生改变,Book,Mother等都需要发生改变,这样是很麻烦的

    但是实际上,这种强耦合关系是我们所不希望的,为了解决这种强耦合关系,我们引入一个中间层

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 //Mother 依赖于 Book  依赖->耦合    -->低耦合
     6 
     7 class IReader
     8 {
     9 public:
    10     virtual string getContents()=0;
    11 };
    12 
    13 class Book:public IReader
    14 {
    15 public:
    16     string getContents()
    17     {
    18         return "从前有座山,山里有座庙,庙里有个小和尚."
    19                 "听老和尚讲故事,从前有座山";
    20     }
    21 };
    22 
    23 class NewsPaper:public IReader
    24 {
    25 public:
    26     string getContents()
    27     {
    28         return "Trump 要在黑西哥边境建一座墙";
    29     }
    30 };
    31 class Mother
    32 {
    33 public:
    34     void tellStory(IReader *pi)
    35     {
    36         cout<<pi->getContents()<<endl;
    37     }
    38 };
    39 int main()
    40 {
    41     Mother m;
    42     Book b;
    43     NewsPaper n;
    44     m.tellStory(&b);
    45     m.tellStory(&n);
    46     return 0;
    47 }

    这样的话,书改变时,Mother是不会发生改变的,只需要加一个新类就是可以的了,用户端接口不会发生改变

    虚继承和虚函数总结

    虚继承解决了多个父类中重名冗余的成员(包括数据成员和函数成员)

    虚函数解决了多态的问题

    被虚继承的类称为虚基类,含有纯虚函数的类称为抽象基类

  • 相关阅读:
    Java 对象的序列化和反序列化
    Java 数组元素倒序的三种方式
    Java实现字符串倒序输出的几种方法
    sql ,内连接,外连接,自然连接等各种连接
    linux上 安装软件
    打乱数组
    java集合类
    我换了新博客啦
    代理模式
    抽象工厂模式
  • 原文地址:https://www.cnblogs.com/Cucucudeblog/p/10148671.html
Copyright © 2011-2022 走看看