十五、继承和派生,兼容性原则

  1. 类和类的关系
  2. 继承
    2.1 定义
    2.2 派生类的组成
    2.3 几点说明
  3. 继承的方式
    3.1 protected 访问控制
    3.2 派生类成员的标识和访问
  4. 练习
  5. 继承中的构造和析构
    5.1 类型兼容性原则
    5.2 初始化父类成员,父类与子类的构造函数的关系
    5.3 继承中同名成员变量处理方法
    5.4 派生类中的static关键字
    5.5 多继承

在 C++中可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。 如果没有掌握继承性,就没有掌握类与对象的精华。

1.类和类之间的关系

has-A uses-A is-A

  • has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
  • uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
  • is-A 机制称为“继承”。关系具有传递性,不具有对称性。

2.继承

#include <iostream>
#include <string>
using namespace std;
class Student
{
   public:
   Student(int age,string name){
        this->age = age;
        this->name = name;
    }

    void printS(){
      cout<<name<<endl;
      cout<<age<<endl;
    }
   private:
   string name;
   int age;
};

//类Student2继承与类Student
class Student2:public Student
{
  public:
    Student2(int age,string name,char sex,float score):Student(age,name){//私有变量无法继承,只能通过初始化传递!
//子类继承过来的成员变量,通过父类的构造器来构造
       this->sex = s;
       this->score = f;
     }
  void printS(){
     Student::printS();
     cout<<this->sex<<endl;
     cout<<this->score<<endl;
    }
  private:
  char sex;
  float score;
};

2.1定义

类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。

派生与继承,是同一种意义两种称谓。isA 的关系。

1.png
2.2派生类的组成

派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。

2.png
2.3 几点说明:
  • 全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的。
  • 派生类有了自己的个性,使派生类有了意义。

3.继承的方式

class 派⽣生类名:[继承⽅方式] 基类名{
    派⽣生类成员声明;
};

一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类, 称为单继承。下面从单继承讲起。

3.1protected 访问控制

protected 对于外界访问属性来说,等同于私有,但可以派生类中可见。

#include <iostream>
using namespace std;
class Base {
 public://访问控制权限
    int pub;//对内部外部均可以
 protected:
    int pro;//对内可以,外不可以//protected访问控制权限下的成员 儿子可见
 private:
    int pri;//对内外 均不可以
};

class Drive:public Base{
  public:
     void  func(){
        pub = 10;
        pro = 100;
       //pri=1000;//error
     }
};

//三看原则
//1 看当前的成员调用的是类的外部 还是在类的内部
//2 看儿子的继承方式,是公有继承还是 私有继承
//3 看当前的额成员变量在父亲中的访问控制权限


//1 基类的私有成员 不管子类如何继承,子类都访问不了。
//2 如果是公有继承,那么基类中的访问控制权限,除了私有成员,在子类中保持不变
//3 如果是保护继承protected,那么子类中除了基类的私有成员,全部是protected权限
//4 如果是私有继承private,父类中除了私有成员,在子类都是私有成员
int main(void)
{
    Base b;
    b.pub = 10;
  //b.pro = 100;//error
  //b.pri = 1000;//error

   return 0;
}
3.2派生类成员的标识和访问
---- public protected private
公有继承(public) public protected 不可见
保护继承(protected) protected protected 不可见
私有继承(private) private private 不可见

public公有继承
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类 中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无 论派生类的成员还是派生类的对象都无法访问基类的私有成员。

private私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。。基类的公有成员和保 护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但 是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对 象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都 会成为不可访问。因此私有继承比较少用。

protect保护继承
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的 公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员 还是派生类的对象,都无法访问基类的私有成员。

private成员在子类中依然存在,但是却无法访问到。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。

如何恰当的使用public,protected和private为成员声明访问级别?

  • 需要被外界访问的成员直接设置为public
  • 只能在当前类中访问的成员设置为private
  • 只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。

4.练习

#include <iostream>
using namespace std;

class A
{
private:
    int a;
protected:
    int b;
public:
    int c;
    A(){
      a = 0;
      b = 0;
      c = 0;
     }
   void set(int a,int b,int c){
     this­‐>a = a;
     this­‐>b = b;
     this‐>c = c;
   }
};


class B:public A
{
public:
    void print()
    {
      cout<<"a = "<<a;//能否访问???  不可以
      cout<<"b = "<<b;//能否访问???  可以
      cout<<"c = "<<endl;//能否访问??? 可以
    }
};

class C:protected A
{
public:
    void print()
   {
     cout<<"a = "<<a;//能否访问???  不可以
     cout<<"b = "<<b;//能否访问???  可以
     cout<<"c = "<<endl;//能否访问??? 可以
   }
};

class D:private A
{
public:
   void print()
   {
     cout<<"a = "<<a;//能否访问???   不可以
     cout<<"b = "<<b<<endl;//能否访问???  不可以
     cout<<"c = "<<c<<endl;//能否访问??? 不可以
   }
};

int main(void)
{
   A aa;
   B bb;
   C cc;
   D dd;

   aa.c = 100;//能否访问???  可以
   bb.c = 100;//能否访问???  可以
   cc.c = 100;//能否访问???  不可以
   dd.c = 100;//能否访问???  不可以
  
   aa.set(1,2,3);//能否访问???   可以
   bb.set(10,20,30);//能否访问???  可以
   cc.set(40,50,60);//能否访问???   不可以 set是protected,在外部不能访问
   dd.set(70,80,90);//能否访问??? 不可以
 
   bb.print();//能否访问???   可以
   cc.print();//能否访问???   可以
   dd.print();//能否访问???   可以

   return 0;
}

5.继承中的构造和析构

5.1类型兼容性原则

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。

类型兼容规则中所指的替代包括以下情况:

  • 子类对象可以当作父类对象使用
  • 子类对象可以直接赋值给父类对象
  • 子类对象可以直接初始化父类对象
  • 父类指针可以直接指向子类对象
  • 父类引用可以直接引用子类对象

在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。

子类就是特殊的父类 (base *p = &child;)

/*
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
*/

class Parent
{
public:
   void printP(){
      cout<<"Parent::printP()..."<<endl;
   }
   int a;
};


class Child:public Parent
{
public:
   void printC(){
     cout<<"Child::printC()..."<<endl;
   }

   int b;
};

void print(Parent *p)
{
   p->printP();
}

int main(void)
{
    Child c;//子类对象
  
    c.printP(); //子类对象可以当作父类对象使用(方法继承)

   //子类对象可以直接赋值给父类对象
    Parent p = c;
  //Child c2 = p;报错!
  //由于子类用于父类的全部内存空间,子类能够保障父类初始化完成
  //子类对象可以直接初始化父类对象,因为子类内存空间包含的父类的内存空间,能够保证完全赋值
 

 //父类指针可以直接指向子类对象
 //子类对象能够完全满足父类指针的需求,所以可以
Parent *pp = &c;//pp->printP,pp->printC不存在这个方法

//不能用子类指针指向父类对象
//父类对象满足不了子类指针的所有需求,所以不能够
Child *cp = &p;//报错!cp->printP,cp->printC
//cp指向父类对象,父类是没有printC方法的!所以报错


//父类引用可以直接引用子类对象
  Parent &pr = c;
  
//子类引用不可以引用父类的对象
  Child &cr = p;//报错,和父类指针一样

//利用兼容性原则传参!
  print(&p);//Parent *p = &p;
  print(&c);//Parent *p = &c;

   return 0;
}
5.2初始化父类成员,父类与子类的构造函数有什么关系?
3.jpg
4.png
  • 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化.
  • 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理.
//子类在进行初始化成员变量的时候,如果此成员变量是继承过来的,那么需要调用父类的构造器来初始化
class Parent
{
public:
  Parent(int a){
     this->a = a;
     cout<<"Parent(int a)..."<<endl;
   }
 ~Parent(){
     cout<<"~Parent()..."<<endl;
   }

  void printA(){
     cout<<"a = "<<a<<endl;
   }

private:
   int a;
}

class Son:public Parent
{
public:
   //子类继承于父类,父类中的成员变量 应该用 父类的构造函数来初始化
   Son(int a,int b):Parent(a){
     this->b = b;
     cout<<"Son(int a,int b)...."<<endl;
   }
 
   ~Son(){
     cout<<"~Son()..."<<endl;
    }

  void printB(){
     cout<<"b = "<<b<<endl;
  }

  void printAB(){
    Parent::printA();
    this->printB();
 }
private:
    int b;
}

void test1()
{
   Son s(10,20);//先构造父类,再构造子类
   s.printAB();
}//子类先析构,父类后析构,和栈一样!


int main(void)
{
 
   test1();
   return 0;
}

1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反

5.3继承中同名成员变量处理方法
  • 当子类成员变量与父类成员变量同名时
  • 子类依然从父类继承同名成员
  • 在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
  • 同名成员存储在内存中的不同位置

同名成员变量和成员函数通过作用域分辨符进行区分

5.4派生类中的static关键字
  • 基类定义的静态成员,将被所有派生类共享
  • 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质(遵守派生类的访问控制)
  • 派生类中访问静态成员,用以下形式显式说明:
    类名::成员
    或通过对象访问
    对象名.成员
class A
{
public:
   static int s;
private:

}


int A::s = 0;//静态成员变量要在类的外部初始化

class B:public A
{
public:
private:

}


int main(void)
{
   B b;
   cout<<b.s<<endl;//0
   b.s = 100;
   cout<<b.s<<endl;//100

   cout<<A::s<<endl;//也是100


   return 0;
}
  • static函数也遵守3个访问原则
  • static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)
5.5多继承

一个类有多个直接基类的继承关系称为多继承

5.png
class Funiture
{
public:
   int m;//材质
};//如果子类虚继承本类,编译器会将父类中的成员,只拷贝一份


//沙发床
//床类
class Bed:virtual public Funiture
{
public:
  void sleep(){
      cout<<"在床上睡觉..."<<endl;
   }
};

//沙发
class Sofa:virtual public Funiture
{
public :
  void sit(){
     cout<<"在沙发上睡觉..."<<endl;
    }
};


class SofaBed:public Bed,public Sofa
{
public:
   void sitAndSleep(){
       sit();//sofa
       sleep();//bed;
   }

};


int main(void){

 Bed b;
 b.sleep();
 cout<<"----------------------"<<endl;

Sofa s;
s.sit();
cout<<"----------------------"<<endl;

SofaBed sb;
sb.sitAndSleep();

sb.m;//报错!!!因为不知道用哪个父类的m,要用虚继承
//改虚继承后不报错!

return 0;
}
6.png
7.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359

推荐阅读更多精彩内容

  • 继承和多态 1. 继承的优缺点 优点:(1)子类可以灵活地改变父类中的已有方法;(2)能够最大限度的实现代码重用。...
    MinoyJet阅读 629评论 0 0
  • C++类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心...
    863cda997e42阅读 662评论 0 4
  • 转自大神博客凡程子 一、基本概念 类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派...
    leon4ever阅读 485评论 0 0
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,113评论 1 32
  • 1 赛场对于客场选手很残酷,客场选手不要听负能量的东西 2 赛场的气氛要搞好,而且很重要,场内和外国友人说very...
    harrytc阅读 298评论 0 0