c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。

派生类中的成员,包含两大部分:

1> 一类是从基类继承过来的,一类是自己增加的成员。

2>  从基类继承过过来的表现其共性,而新增的成员体现了其个性。

派生类定义格式:

Class 派生类名 :  继承方式 基类名{

//派生类新增的数据成员和成员函数

}

三种继承方式:

1>  public :    公有继承

2>  private :   私有继承

3> protected : 保护继承

从继承源上分:

1> 单继承:指每个派生类只直接继承了一个基类的特征

2> 多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征

派生类访问控制

派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之外的成员方法),但是在派生类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。

派生类的访问权限规则如下:


相应的代码:

//基类
class A{
public:int mA;
protected:int mB;
private:int mC;
};//1. 公有(public)继承
class B : public A{
public:void PrintB(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
class SubB : public B{void PrintSubB(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
void test01(){B b;cout << b.mA << endl; //可访问基类public属性//cout << b.mB << endl; //不可访问基类protected属性//cout << b.mC << endl; //不可访问基类private属性
}//2. 私有(private)继承
class C : private A{
public:void PrintC(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
class SubC : public C{void PrintSubC(){//cout << mA << endl; //不可访问基类public属性//cout << mB << endl; //不可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
void test02(){C c;//cout << c.mA << endl; //不可访问基类public属性//cout << c.mB << endl; //不可访问基类protected属性//cout << c.mC << endl; //不可访问基类private属性
}
//3. 保护(protected)继承
class D : protected A{
public:void PrintD(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
class SubD : public D{void PrintD(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
void test03(){D d;//cout << d.mA << endl; //不可访问基类public属性//cout << d.mB << endl; //不可访问基类protected属性//cout << d.mC << endl; //不可访问基类private属性
}

继承中的对象模型

在C++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成:

class Aclass{
public:int mA;int mB;
};
class Bclass : public Aclass{
public:int mC;
};
class Cclass : public Bclass{
public:int mD;
};
void test(){cout << "A size:" << sizeof(Aclass) << endl;cout << "B size:" << sizeof(Bclass) << endl;cout << "C size:" << sizeof(Cclass) << endl;
}

对象构造和析构的调用原则

继承中的构造和析构

1>  子类对象在创建时会首先调用父类的构造函数

2>  父类构造函数执行完毕后,才会调用子类的构造函数

3>  当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数

4>  析构函数调用顺序和构造函数相反

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;class Base{
public:Base(int a){cout << "Base默认构造函数!" << endl;mA = a;}~Base(){cout << "Base析构函数!" << endl;}
public:int mA;
};class Other{
public:Other(){cout << "Other 构造函数!" << endl;}~Other(){cout << "Other 析构函数!" << endl;}
};class Derived : public Base{
public:Derived(int a) : Base(a){cout << "Derived 构造函数!" << endl;mNew = 0;}~Derived(){cout << "Derived 析构函数!" << endl;}
public:Other o;int mNew;
};void test(){Derived d(10);cout << d.mA << endl;}int main(){test();system("pause");return EXIT_SUCCESS;
}

继承中同名成员的处理方法

1>  当子类成员和父类成员同名时,子类依然从父类继承同名成员

2>  如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)

3>  在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;//1. 变量重名的情况#if 0class Base{public:Base(){mA = 0;}public:int mA;};class Derived1 : public Base{public:Derived1(){mA = 100;}void showD(){cout << Base::mA << endl;cout << mA << endl;}public://基类的mA只是隐藏,并不是覆盖int mA;};//如果子类的成员变量和基类的变量重名,基类的自动隐藏,但可以通过类作用域运算符来访问基类的变量void test(){Derived1 d;cout << d.mA << endl;cout << d.Base::mA << endl;d.showD();}#elseclass Base{
public:void func(){cout << "Base:func" << endl;}//以下重载函数void MyFunc(){}void MyFunc(int a){}void MyFunc(int a,int b){}
};class Derived : public Base{
public:void func(){cout << "Derived:func" << endl;}//和基类重载函数重名//void MyFunc(int a , int b ,int c){}int MyFunc(int a, int b){}
};void test(){//1. 和普通函数重名Derived d;d.func();//基类的func函数也是隐藏d.Base::func();//2. 和重载函数重名//d.Base::MyFunc();//d.MyFunc(10);//d.MyFunc(10,20);//d.MyFunc(10, 20, 30);//如果和基类中重载的函数同名了,基类中所有版本的此函数都将会被隐藏}#endifint main(){test();system("pause");return EXIT_SUCCESS;
}

1>  Derive1重定义了Base类的myfunc函数,derive1可访问func1及其重载版本的函数。

2>  Derive2通过改变函数参数列表的方式重新定义了基类的func1函数,则从基类中继承来的其他重载版本被隐藏,不可访问

3>  Derive3通过改变函数返回类型的方式重新定义了基类的func1函数,则从基类继承来的没有重新定义的重载版本的函数将被隐藏。

任何时候重新定义基类中的一个重载函数,在新类中所有的其他版本将被自动隐藏.

非自动继承的函数

不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。

另外operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。

在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。

class Base{
public:static int getNum(){ return sNum; }static int getNum(int param){return sNum + param;}
public:static int sNum;
//const static int sNum = 10; //const静态成员建议在类内初始化
};
int Base::sNum = 10;class Derived : public Base{
public:static int sNum; //基类静态成员属性将被隐藏
#if 0//重定义一个函数,基类中重载的函数被隐藏static int getNum(int param1, int param2){return sNum + param1 + param2;}
#else//改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数static void getNum(int param1, int param2){cout <<  sNum + param1 + param2 << endl;}
#endif
};
int Derived::sNum = 20;

多继承概念

我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;struct Base1 {int mA;
};struct Base2 {int mA;
};struct Derived : public Base1, public Base2{};void test(){Derived d;//d.mA; //报错,不明确,到底是调用Base1的还是Base2的mA?d.Base1::mA;d.Base2::mA;
}void f(){}
void f(int a){}int main(){f(10);system("pause");return EXIT_SUCCESS;
}

多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类1继承的版本还是从基类2继承的版本

解决方法就是显示指定调用那个基类的版本。

菱形继承和虚继承

两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;#if 0
//动物
class Animal{
public:int mAge;
};//羊
class Sheep : public Animal{};
//鸵
class Camel : public Animal{};//羊驼
class SheepCamel : public Sheep, public Camel{};void test(){SheepCamel sc;//sc.mAge;sc.Sheep::mAge;sc.Camel::mAge;//菱形继承带来的问题://1. 二义性问题//2. 数据继承了多份,实际上只需要一份,浪费空间//解决方案://通过虚继承
}#else//动物
class Animal{
public:Animal(int age){mAge = age;cout << "Animal 构造函数!" << endl;}
public:int mAge;
};//只需要继承公共类的地方写上virtual
//羊
class Sheep : virtual public Animal{
public:Sheep() : Animal(10){cout << "Sheep 构造函数!" << endl;}//vbptr -> vbtable(记录公共数据相对当前这个vbptr指针的偏移量)
}; //虚继承
//鸵
class Camel : virtual public Animal{
public:Camel() : Animal(20){cout << "Camel 构造函数!" << endl;}//int tuo;
}; //虚继承//羊驼
class SheepCamel : public Sheep, public Camel{
public:SheepCamel() : Animal(10){cout << "SheepCamel 构造函数!" << endl;}
};void test(){SheepCamel sc;
#if 0sc.Sheep::mAge = 10;cout << sc.Camel::mAge << endl;cout << sc.mAge << endl;//虚继承了,那么羊和鸵共享一份基类数据cout << sizeof(sc) << endl;
#endif
}#endifint main(){test();system("pause");return EXIT_SUCCESS;
}

class BigBase{
public:BigBase(){ mParam = 0; }void func(){ cout << "BigBase::func" << endl; }
public:int mParam;
};class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};int main(){Derived derived;//二义性问题解决derived.func();cout << derived.mParam << endl;//输出结果:12cout << "Derived size:" << sizeof(Derived) << endl;return EXIT_SUCCESS;
}

以上程序Base1 ,Base2采用虚继承方式继承BigBase,那么BigBase被称为虚基类。

通过虚继承解决了菱形继承所带来的二义性问题。

但是虚基类是如何解决二义性的呢?并且derived大小为12字节,这是怎么回事?

通过内存图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜测到编译器肯定对我们编写的程序做了一些手脚。

ü BigBase 菱形最顶层的类,内存布局图没有发生改变。

ü Base1和Base2通过虚继承的方式派生自BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。

ü Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。

由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承一份数据,并且也解决了二义性的问题。现在模型就变成了Base1和 Base2 Derived三个类对象共享了一份BigBase数据。

当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。

class BigBase{
public:BigBase(int x){mParam = x;}void func(){cout << "BigBase::func" << endl;}
public:int mParam;
};
class Base1 : virtual public BigBase{
public:Base1() :BigBase(10){} //不调用BigBase构造
};
class Base2 : virtual public BigBase{
public:Base2() :BigBase(10){} //不调用BigBase构造
};class Derived : public Base1, public Base2{
public:Derived() :BigBase(10){} //调用BigBase构造
};
//每一次继承子类中都必须书写初始化语句
int main(){Derived derived;return EXIT_SUCCESS;
}

注意:

虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的.

Jerry Schwarz,输入输出流(iostream)的作者,曾在个别场合表示如何他重新设计iostream的话,很可能从iostream中去除多重继承。

Day06(上)C++继承和派生相关推荐

  1. Python基础20-面向对象:静态、组合、继承与派生、多态、封装、反射、内置attr方法等

    目录 静态 静态属性@property 类方法@classmethod 静态方法@staticmethod 组合 继承与派生 继承与派生 继承顺序 在子类中调用父类方法与super 多态 封装 反射 ...

  2. C++学习之路—继承与派生(四)拓展与总结

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 1    拓展部分 本节主要由两部分内容组成,分 ...

  3. C++复习笔记--继承和派生

    -继承和派生 一继承(继承就是从先辈处得到属性和行为特征) 1 派生类的声明 class  派生类名∶[继承方式] 基类名 { 派生类新增的数据成员和成员函数 }: 2 基类成员在派生类中的访问属性 ...

  4. 类的继承和派生java_类的继承和派生

    一.类的继承和派生定义. 继承:保持已有类的特性而构造新类的过程称为继承. 派生:在已有类的基础上新增自己的特性而产生新类的过程称为派生. 被继承的已有类称为基类. 派生出的新类称为派生类. 二.继承 ...

  5. C++(22)--继承和派生

    继承和派生 1.基本概念 2.实现公有继承 3.私有继承的例子 4. 继承和组合 <老九学堂C++课程><C++ primer>学习笔记.<老九学堂C++课程>详情 ...

  6. C/C++学习----第二章 继承和派生

    第二章 继承和派生 2.1 派生 派生类或派生类的使用者均不能访问基类的私有数据成员:对于基类的公有成员的访问,如果派生的方式不同,访问的权限也不同.派生时,不指明派生类型的,按私有派生进行派生. & ...

  7. 模块的封装之C语言类的继承和派生

    [交流][微知识]模块的封装(二):C语言的继承和派生 在模块的封装(一):C语言的封装中,我们介绍了如何使用C语言的结构体来实现一个类的封装,并通过掩码结构体的方式实 现了类成员的保护.这一部分,我 ...

  8. 继承类 基类的赋值_Chapter10:继承与派生(四)

    之前的文章把继承和派生的基本概念和内部原理都大致理清了.但是类可以视作是一种特殊的数据类型--它也具有转换的功能,称为转型,这一节展开讨论它,以及如何通过指针跨越权限访问不同成员的技巧. 1.类的转型 ...

  9. c++学习---继承与派生类

    1.继承 继承性是面向对象程序设计当中最重要的机制,这种机制可以无限度的重复利用程序的 资源.通过C++语言中的继承机制,我们可以扩充和完善旧的程序设计以适应新的需求. 这样不仅可以节省程序开发的时间 ...

  10. 【C++入门】C++ 继承和派生

    C++继承和派生 文章目录 C++继承和派生 一.继承和派生的概念 1.继承的概念 2.派生类 3.继承实例程序 二.类之间的两种关系 1.继承关系 2.复合关系 三.派生类覆盖基类成员&类的 ...

最新文章

  1. trl meaning genearlly we find 6
  2. MPI学习存在的一些问题
  3. 动态规划 POJ 1088 滑雪
  4. 关于aspx.designer.cs
  5. 【计算机网络】复习荟萃(四)
  6. 特斯拉线圈怎么用_中二科技_场管自激特斯拉线圈的制作
  7. (转载)--SG函数和SG定理【详解】
  8. webpack官方文档分析(一):安装
  9. 矢量图形和位图的不同
  10. web网页对话框的一些设置
  11. 所谓的飞扬档案管理软件
  12. 一招解决 Mac JD-JUI 打不开问题
  13. 怎么把图片做成pdf文件?
  14. 广发股票交易接口做什么的?
  15. mysql断开连接_MYSQL,使用什么命令可以断开所有用户的表连接?
  16. 《当程序员的那些狗日日子》四
  17. 深入浅出Flask PIN
  18. 键盘录入 写入文件 quit时 结束
  19. 编程 - 变量的命名方法
  20. 中国全屋智能行业市场前瞻与投资战略规划分析报告

热门文章

  1. 设计模式--代理模式--深入理解动态代理
  2. 利用高德地图 API 显示地图信息
  3. 通过easyx窗口实现贪吃蛇
  4. PMP考试必备-常见翻译问题(三)
  5. 华硕路由器修改 Hosts 以达到局域网内自定义解析
  6. 《预训练周刊》第52期:屏蔽视觉预训练、目标导向对话
  7. 书香小说APP界面设计
  8. APP界面设计规范:如何定义视觉规范
  9. 解决图片闪烁问题(雪碧图)
  10. 易捷行云亮相北京卫视,战略性新兴产业引发持续关注