2.类的作用域运算符

shadow

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

  1. void func()
  2. {
  3. cout<<"B::void func()"<<endl;
  4. func();
  5. }

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

  1. #include <iostream>
  2.  
  3. using namespace std;
  4. class A
  5. {
  6. public:
  7. void foo()
  8. {
  9. cout<<"A::void foo()"<<endl;
  10. }
  11. };
  12. class B:public A
  13. {
  14. public:
  15. void foo()
  16. {
  17. cout<<"B::void foo()"<<endl;
  18. foo();//实际上这里是有一个this指针指向foo的
  19. }
  20. };
  21. int main()
  22. {
  23. B b;
  24. b.foo();
  25. return ;
  26. }

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

  1. void foo()
  2. {
  3. cout<<"B::void foo()"<<endl;
  4. A::foo();
  5. }

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(),Y()
  32. {
  33.  
  34. }
  35. void dis()
  36. {
  37. cout<<Y_data<<endl; }
  38. };
  39.  
  40. int main()
  41. {
  42. Z z;
  43. z.dis();
  44. return ;
  45. }

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

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

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

  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. _data=d;
  12. }
  13. void setData(int d)
  14. {
  15. _data=d;
  16. }
  17. protected:
  18. int _data;
  19. };
  20.  
  21. class Y
  22. {
  23. public:
  24. Y(int d)
  25. {
  26. cout<<"Y()"<<endl;
  27. _data=d;
  28. }
  29. int getData()
  30. {
  31. return _data;
  32. }
  33. protected:
  34. int _data;
  35. };
  36.  
  37. class Z:public X,public Y
  38. {
  39. public:
  40. Z(int i,int j)
  41. :X(i),Y(j)
  42. {
  43.  
  44. }
  45. void dis()
  46. {
  47. cout<<X::_data<<endl;
  48. cout<<Y::_data<<endl;
  49. }
  50. };
  51.  
  52. int main()
  53. {
  54. Z z(,);
  55. z.dis();
  56. cout<<"================="<<endl;
  57. z.setData();
  58. cout<<z.getData()<<endl;
  59. cout<<"================="<<endl;
  60. z.dis();
  61. return ;
  62. }

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

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

当其继承在类Z中后

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

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

需要解决的问题:

数据冗余

访问方便

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

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

首先解决初始化问题,

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

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. //祖父类
  6. class A
  7. {
  8. protected:
  9. int _data;
  10. };
  11. //父类继承祖父类
  12. class X:virtual public A
  13. {
  14. public:
  15. X(int d)
  16. {
  17. cout<<"X()"<<endl;
  18. _data=d;
  19. }
  20. void setData(int d)
  21. {
  22. _data=d;
  23. }
  24.  
  25. };
  26. //各父类继承祖父类
  27. class Y:virtual public A
  28. //虚继承
  29. {
  30. public:
  31. Y(int d)
  32. {
  33. cout<<"Y()"<<endl;
  34. _data=d;
  35. }
  36. int getData()
  37. {
  38. return _data;
  39. }
  40. };
  41.  
  42. class Z:public X,public Y
  43. {
  44. public:
  45. Z(int i,int j)
  46. :X(i),Y(j)
  47. {
  48.  
  49. }
  50. void dis()
  51. {
  52. cout<<_data<<endl;
  53. }
  54. };
  55.  
  56. int main()
  57. {
  58. Z z(,);
  59. z.dis();
  60. cout<<"================="<<endl;
  61. z.setData();
  62. cout<<z.getData()<<endl;
  63. cout<<"================="<<endl;
  64. z.dis();
  65. return ;
  66. }

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

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

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

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

  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(),B(),A()
  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 ;
  56. }

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

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

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

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

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. class Furniture
  6. {
  7. public:
  8. void descript()
  9. {
  10. cout<<"_weight:"<<_weight<<endl;
  11. cout<<"_color :"<<_color<<endl;
  12. }
  13. protected:
  14. float _weight;
  15. int _color;
  16. };
  17. class Sofa:virtual public Furniture
  18. {
  19. public:
  20. Sofa(float w=,int c=)
  21. {
  22. _weight=w;
  23. _color=c;
  24. }
  25. void sit()
  26. {
  27. cout<<"take a sit and have a rest"<<endl;
  28. }
  29. };
  30.  
  31. class Bed:virtual public Furniture
  32. {
  33. public:
  34. Bed(float w=,int c=)
  35. {
  36. _weight=w;
  37. _color=c;
  38. }
  39. void sleep()
  40. {
  41. cout<<"have a sleep ......."<<endl;
  42. }
  43.  
  44. };
  45.  
  46. class SofaBed:public Sofa,public Bed
  47. {
  48. public:
  49. SofaBed(float w,int c)
  50. {
  51. _weight=w;
  52. _color=c;
  53. }
  54. };
  55.  
  56. int main()
  57. {
  58. SofaBed sb(,);
  59. sb.sit();
  60. sb.sleep();
  61. sb.descript();
  62. return ;
  63. }
  64.  
  65. int main1()
  66. {
  67. Sofa sf;
  68. sf.sit();
  69. Bed bd;
  70. bd.sleep();
  71. return ;
  72. }

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=,int y=)
  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=,int y=,int r=)
  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(,);
  33. s.draw();
  34. Circle c(,,);
  35. c.draw();
  36. s=c; //派生类对象可以赋值给基类对象
  37. s.draw();
  38. return ;
  39. }

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

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

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

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

7. 多态

多态分为静多态和动多态

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

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

多态实现的条件

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

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

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

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

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

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

  1. class A
  2. {
  3. public:
  4. A(){};
  5. virtual void draw();
  6. private:
  7. int _x;
  8. }
  9. void A::draw()
  10. {
  11. cout<<_x<<endl;
  12. }

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

  1. virtual void A::draw()
  2. {
  3. cout<<_x<<endl;
  4. }

系统即会开始报错

  • 子类覆写父类中的虚函数,子类中同名同参同函数,才能构成覆写
  • 通过已被子类对象赋值的父类指针,调用虚函数,形成多态
  1. #include <iostream>
  2. #include <typeinfo>
  3. using namespace std;
  4.  
  5. class Shape
  6. {
  7. public:
  8. Shape(int x=,int y=)
  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=,int y=,int r=)
  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. class Rect:public Shape
  40. {
  41. public:
  42. Rect(int x=,int y=,int w=,int l=)
  43. :Shape(x,y),_width(w),_lenth(l){}
  44. virtual void draw()
  45. {
  46. cout<<"draw Circle from"<<"("<<_x<<","<<_y<<")"
  47. <<"width:"<<_width<<"lenth:"<<_lenth<<endl;
  48. }
  49. protected:
  50.  
  51. int _width;
  52. int _lenth;
  53. };
  54.  
  55. int main()
  56. {
  57. Circle c(,,);
  58. Shape *ps=&c;//父类指针指向子类的对象
  59. ps->draw();
  60.  
  61. Rect r(,,,);
  62. ps=&r;
  63. ps->draw();
  64. return ;
  65. }

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

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

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

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

虚继承和虚函数总结

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

虚函数解决了多态的问题

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

C++基础知识-Day8的更多相关文章

  1. python基础知识-day8(动态参数)

    1.动态参数 函数的形式参数个数不确定.函数的形式数据类型不确定,使用动态参数,*代表元组,**代表字典. 2.代码案例演示 1 def func(*args,**kwargs): 2 print(a ...

  2. python基础知识-day8(函数实战)

    1 def out(): 2 username=input("请输入用户名:\n") 3 password=input("请输入密码:\n") 4 return ...

  3. python基础知识-day8(模块与包、random、os)

    1.模块与包 package:相同的模块代码存储在一个目录下(即包里边会包含多个模块).   包不能存储在文件夹的目录下,模块名称不能使用关键字.(不包含工程文件夹) 2.模块与包的实例 1)在工程文 ...

  4. .NET面试题系列[1] - .NET框架基础知识(1)

    很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...

  5. RabbitMQ基础知识

    RabbitMQ基础知识 一.背景 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然 ...

  6. Java基础知识(壹)

    写在前面的话 这篇博客,是很早之前自己的学习Java基础知识的,所记录的内容,仅仅是当时学习的一个总结随笔.现在分享出来,希望能帮助大家,如有不足的,希望大家支出. 后续会继续分享基础知识手记.希望能 ...

  7. selenium自动化基础知识

    什么是自动化测试? 自动化测试分为:功能自动化和性能自动化 功能自动化即使用计算机通过编码的方式来替代手工测试,完成一些重复性比较高的测试,解放测试人员的测试压力.同时,如果系统有不份模块更改后,只要 ...

  8. [SQL] SQL 基础知识梳理(一)- 数据库与 SQL

    SQL 基础知识梳理(一)- 数据库与 SQL [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902856.html 目录 What's 数据库 ...

  9. [SQL] SQL 基础知识梳理(二) - 查询基础

    SQL 基础知识梳理(二) - 查询基础 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5904824.html 序 这是<SQL 基础知识梳理( ...

  10. [SQL] SQL 基础知识梳理(三) - 聚合和排序

    SQL 基础知识梳理(三) - 聚合和排序 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5926689.html 序 这是<SQL 基础知识梳理 ...

随机推荐

  1. C#中as运算符

    as运算符用于执行引用类型的显式类型转换.如果要转换的类型与指定的类型兼容,转换就会成功进行:如果类型不兼容,as运算符就会返回null值.如下面的代码所示,如果object引用实际上不引用strin ...

  2. RabbitMQ在Ubuntu 16.04下的安装与配置

    安装执行如下命令: echo 'deb http://www.rabbitmq.com/debian/ testing main' | sudo tee /etc/apt/sources.list.d ...

  3. mvc 按钮权限控制

    需要开发一个按钮权限的控制,思路:拦截所有按钮路径,和用户拥有的3级按钮权限对比, 所有验证都一个方法解决,只需要修改js后的参数,参数就是按钮对应的权限码 如果有什么问题请提醒,谢谢! xml: & ...

  4. C#使用MemoryStream类读写内存

    MemoryStream和BufferedStream都派生自基类Stream,因此它们有很多共同的属性和方法,但是每一个类都有自己独特的用法.这两个类都是实现对内存进行数据读写的功能,而不是对持久性 ...

  5. servlet篇 之 访问形式

    get方式访问和post方式访问: get/post区别? 1) 参数传递 查询字符串(的形式)! get  url?key1=value&key2=value 2) http协议 请求报文包 ...

  6. 使用aapt查看当前apk的属性

    android:versioncode——整数值,代表应用程序代码的相对版本,也就是版本更新过多少次. android:versionname——字符串值,代表应用程序的版本信息,需要显示给用户. e ...

  7. Mail.Ru Cup 2018 Round 3

    A:签到 #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> # ...

  8. Number Sequence POJ - 1019 递推 数学

    题意 1 12 123 1234 12345 ....这样的序列 问第n位数字是几   是数字! 1-9! 思路:递推关系 主要是位数的计算   用a[i]=a[i-1]+(int)log10((do ...

  9. Hdoj 4540.威威猫系列故事——打地鼠 题解

    Problem Description 威威猫最近不务正业,每天沉迷于游戏"打地鼠". 每当朋友们劝他别太着迷游戏,应该好好工作的时候,他总是说,我是威威猫,猫打老鼠就是我的工作! ...

  10. 【BZOJ5289】[HNOI2018]排列(贪心)

    [BZOJ5289][HNOI2018]排列(贪心) 题面 BZOJ 洛谷 题解 这个限制看起来不知道在干什么,其实就是找到所有排列\(p\)中,\(p_k=x\),那么\(k<j\),其中\( ...