继承的意义?

使程序的设计更符合发展规律,即事物的发展是一个从低级到高级的发展过程,类的继承也是反映由原始的简单代码到丰富的高级代码的过程。它能帮助我们描述事物的层次关系,有效而精确的理解事物,理解事物直到本质。

什么叫继承?

(1)继承使类与类之间建立起一种上下级的层次关系

(2)子类可以重复使用父类的操作和数据成员,子类可以声明新的属性和操作,还可以剔除不适合自己用途的父类操作。

为什么要使用继承?

原因:为了提高代码复用率,缩短程序开发成本

继承实现的功能:

1、继承基类的数据成员:将基类成员全盘吸收

2、增加新的数据成员、改变现有成员的属性:不同方式继承 +  声明一个同名成员,使用重写覆盖技术

3、重新定义已有成员函数

继承的分类:

针对派生类而言,根据基类的个数分:单继承 +  多继承

单继承:派生类的基类只有一个

语法格式:

  1. class 派生类名:继承方式 基类名
  2. {
  3. //成员声明:类似于普通类
  4. };

三种继承方式:公有继承(public),私有继承(private),保护继承(protected)

继承后访问属性的种类:针对派生类而言,成员可分为:不可访问成员、私有成员、受保护成员、公有成员

说明:无论是那种派生方式,派生类无法直接使用不可访问变量的,换句话说,派生类不能访问基类的私有成员。

具体如图:

继承后的法则:

派生类的成员函数访问基类成员时,

无论是公有、私有、受保护继承,基类的私有变量均不可访问,但继承后属性为公有、受保护成员均可以直接访问。

派生类的对象类外访问基类成员时,

若受保护、私有继承时,派生类对象均不可访问基类的成员(无论是哪种访问属性)。

若公有继承,派生类的对象可以访问基类的公有成员。

一句话:就派生类而言,基类私有变量不可访问,其他成员的访问情况,按具体继承后的属性而定

根据父亲物品的使用权以及继承,换一种方式理解成员权限的定义:

父亲的东西可以分为几类,只能是父母知道,子女都不可以知道。子女可以知道,但是外人不可以知道。外人可以知道。

根据分类,给出哪种物品使用哪种权限限制:

--只能是父母知道,子女都不可以知道:把这部分东西定义为Private。

--子女可以知道,但是外人不可以知道:把这部分定义为protected。

--外人可以知道:把这部分内容定义为Public

最后根据那些继承方式,对父类物品的访问类型进一步约束,形成自己的访问法则。

注意,无论是哪一种继承方式,继承方式对父亲的孩子能访问父亲哪些的物品不能访问哪些物品是没有限制的,继承方式是为了限制后来的继承。

即无论是哪一种继承方式,子类成员都不可以访问私有成员,但都可以访问父类的受保护和公有成员。当自己作为父类时,根据自己从父亲那里的继承权限,在就会限制自己的子类访问自己的父亲的权限。

调整访问控制

前提:对于在派生类中可以看见的成员(父类除私有成员外的成员),

目标:在派生类中,将基类中的私有成员调整为公有成员,将公有成员变成私有成员

  1. class BaseClass
  2. {
  3. public:
  4. int nPublicA;
  5. protected:
  6. int nProtectedA;
  7. private:
  8. int nPrivateA;
  9. };
  10. class DerivedClass : public BaseClass
  11. {
  12. public:
  13. using BaseClass::nPrivateA; /*错误,父类私有成员在子类不可见*/
  14. using BaseClass::nProtectedA;/*正确,父类受保护成员变公有成员*/
  15. private:
  16. using BaseClass::nPublicA;/*正确,父类公有成员变私有成员*/
  17. };

派生类的构造函数和析构函数

类一旦被创建,C++编译器会为其产生四个缺省函数,默认的无参构造函数,默认的拷贝构造函数,默认的析构函数,默认的赋值函数。
对于构造函数:
若父类的构造函数无参,则子类的构造函数可以无参,若父类的构造函数有参,则子类的构造函数必有参,可以使用参数列表为基类构造函数传参。
对于拷贝构造函数和赋值函数:
由于这两类函数的函数名和参数都是确定的,所以不涉及由子类为父类传参的情况。
一句话:无论是父类还是基类,只要有自定义的拷贝构造函数,则在调用拷贝构造函数时,就调用自定义的,否则调用默认的。
若父类没有自定义拷贝构造函数,则子类的拷贝构造函数将调用父类的默认拷贝构造函数。
若父类有自定义的拷贝构造函数,则子类的拷贝构造函数将调用父类的自定义的拷贝构造函数。
注意,对于父类和子类之间的拷贝和赋值时,由于子类中包含父类中的成员,可以按照参数对应赋值

对象构造顺序:

创建对象时,先基类,再对象类成员的构造函数,后派生类

撤销对象时,先派生类,再对象类成员的析构函数,后基类

如果,在类中有多个对象成员,则其调用构造函数的顺序和类中对象声明的顺序有关,而和在参数列表中的顺序无关。析构函数的调用顺序和构造函数的顺序相反。

语法:

  1. 派生类名(派生类构造函数参数表):基类名(参数),子对象名(参数)
  2. {
  3. //派生类的数据成员初始化
  4. }

为基类传参:使用参数列表的形式初始化对象

举例:

  1. #include <iostream>
  2. using namespace std;
  3. class Base
  4. {
  5. private:
  6. int i;
  7. public:
  8. Base(int )
  9. {
  10. cout<<"调用基类构造函数"<<endl;
  11. }
  12. };
  13. class Derived:public Base
  14. {
  15. private:
  16. int j;
  17. Base B;//基类对象
  18. public:
  19. Derived(int i,int j):Base(i),B(i)
  20. {
  21. this->j=j;
  22. cout<<"调用派生类的构造函数"<<endl;
  23. }
  24. };
  25. void main()
  26. {
  27. Derived d(1,2);
  28. system("pause");
  29. }

运行结果:

调用基类构造函数

调用基类构造函数

调用派生类的构造函数

同名覆盖与重写:

产生原因:派生类定义的成员与基类中的成员同名

如果是函数的话,函数名和参数完全相同

结果:派生类的成员覆盖了基类的同名成员

具体来数,直接使用派生类对象调用成员时,只能调到派生类自己的,而无法访问基类成员

如果要访问基类被覆盖的成员,则需要加上 类名::成员 访问

例子:

  1. #include <iostream>
  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6. void a()
  7. {
  8. cout<<"调用基类的成员函数"<<endl;
  9. }
  10. };
  11. class Derived:public Base
  12. {
  13. public:
  14. void a()
  15. {
  16. cout<<"调用派生类的成员函数"<<endl;
  17. }
  18. };
  19. void main()
  20. {
  21. Derived d;
  22. d.a(); //调用的是派生类的成员函数,这时会覆盖基类的成员函数
  23. d.Base::a();//调用基类的成员函数
  24. d.Derived::a();//调用派生类的成员函数
  25. system("pause");
  26. }

调用结果:

调用派生类的成员函数

调用基类的成员函数

调用派生类的成员函数

派生类为基类赋值:

定义:在公有派生情况下,一个派生类的对象可用于基类对象可使用的任何地方

换句话说:公有派生情况下,一个派生类对象可以直接作为基类的对象使用

具体分3仲情况:

1)  派生类的对象 给 基类对象 赋值

结果:基类对象能够访问派生类中从基类继承的成员

2)  派生类的对象 给 基类对象的引用 赋值

结果:基类对象只可以访问派生类中基类的成员,看不到派生类之外的成员,也就不可访问派生类自己定义的成员

3)  派生类的对象 给 基类对象的指针 赋值

结果:基类指针只可以访问派生类中基类的成员,派生类之外成员不可访问

若使基类指针访问派生类自己的成员,则必须使用显式类型转换,把基类指针显示转换为派生类指针,这时可以访问所有派生类成员。

举例:

  1. #include <iostream>
  2. using namespace std;
  3. class Base
  4. {
  5. public:
  6. void a()
  7. {
  8. cout<<"调用基类的成员函数"<<endl;
  9. }
  10. };
  11. class Derived:public Base
  12. {
  13. public:
  14. void a()
  15. {
  16. cout<<"调用派生类的成员函数"<<endl;
  17. }
  18. };
  19. void main()
  20. {
  21. Base b;
  22. Base *B;
  23. Derived d;
  24. B=&b;
  25. B->a();        //基类指针指向基类,调用的都是基类函数
  26. B=&d;
  27. B->a();        //基类指针指向派生类,调用的是派生类中从基类继承的函数
  28. b=d;
  29. b.a();         //使用派生类为基类赋值,调用的是派生类中从基类继承的函数
  30. //  Derived *D=&b; //错误,不能用基类为派生类指针赋值
  31. //  d=b;           //错误,不能用基类为派生类赋值
  32. Base *E;
  33. E=&d;          //必须要先赋值啊,这时派生类指针E才不会乱指。
  34. ((Derived*)E)->a();//对基类指针强制转换为指向派生类,这时可以指向派生类自己的成员
  35. system("pause");
  36. }

运行结果:

调用基类的成员函数

调用基类的成员函数

调用基类的成员函数

注意:

1、在公有继承下,为什么一个指向基类的指针可以指向其公有派生类的对象,但是指向派生类的指针不能指向一个基类的对象?

因为:派生类指向基类时,派生类能力减弱,不行的。

基类指向派生类时,不是能力扩大,实际上还是指向派生类中包含的基类。

2、为什么前提必须是公有派生?

因为:公有派生:派生后类外还是类可以直接访问,私有或受保护派生:类不可以直接访问基类成员,就没什么意思了

3、引入protected成员的原因:

若一个类,将外界能够访问的操作都公有化了,所有不能被外界访问的成员都私有化,这样一来,在后继的类中,就没有对基类任何可以悄悄改进的余地了,即无法对类中某些功能可扩展的变量扩展功能。所以,继承也需要这样的成员,它们对外界是私有的,对派生的子女是允许访问的。这种设计需求就是访问控制符protected。

此时,类中private成员是一种隐私,子女以及外来人员谁都不能动的

类中protected成员是基类想后代开放的成员,便于继承者基于此而改进,基类为了长远考虑,可以留下保护成员,但派生类简单而清晰的设计应该是不使用任何保护成员,即只使用公有成员。

4、使用派生类对象对基类对象、基类对象引用、基类对象指针赋值时,是否调用拷贝构造函数?

由于派生类对象包含的对象实体拥有基类对象包含的实体对象,因此可以使用派生类对象对基类对象、基类对象引用、基类对象指针赋值。

(1)在创建对象时,使用派生类对象为基类对象赋值,需要调用拷贝构造函数或赋值函数

(2)由于基类对象引用、基类对象指针只是获得了派生类中基类部分的地址,即取了个别名,此时没有调用拷贝构造函数。

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. class BaseClass
  5. {
  6. public:
  7. BaseClass(string baseA)
  8. {
  9. m_strBaseA = baseA;
  10. }
  11. BaseClass(const BaseClass& other)
  12. {
  13. m_strBaseA = other.m_strBaseA;
  14. }
  15. void show()
  16. {cout<<"Base: "<<m_strBaseA<<endl;}
  17. private:
  18. string m_strBaseA;
  19. };
  20. class DerivedClass : public BaseClass
  21. {
  22. public:
  23. DerivedClass(string strBaseA,string strDerivedA):BaseClass(strBaseA)
  24. {
  25. m_strDerivedA = strDerivedA;
  26. }
  27. void show()
  28. {cout<<"Derived: "<<m_strDerivedA<<endl;}
  29. private:
  30. string m_strDerivedA;
  31. };
  32. /*可以使用派生类对基类进行直接复制*/
  33. int main()
  34. {
  35. /*
  36. 使用拷贝构造函数,用派生类对基类赋值
  37. 由于derive的对象实体包含baseOne的对象实体,
  38. 使用derive对baseOne赋值,就是将derive中的Base对象实体复制给baseOne
  39. **注意:这个过程调用了拷贝构造函数
  40. */
  41. DerivedClass derive("Base","Derived");
  42. BaseClass baseOne(derive);
  43. derive.show();
  44. baseOne.show();
  45. /*
  46. 使用派生类对象初始化基类对象的引用
  47. 等价于 baseTwo 是 derive中的Base对象实体的别名
  48. **注意:这个过程没有调用了拷贝构造函数
  49. */
  50. BaseClass& baseTwo = derive;
  51. derive.show();
  52. baseTwo.show();
  53. /*
  54. 使用派生类对象初始化基类对象的指针
  55. 等价于 baseThree 是 derive中的Base对象实体的别名
  56. **注意:这个过程没有调用了拷贝构造函数
  57. */
  58. BaseClass* baseThree = &derive;
  59. derive.show();
  60. baseThree->show();
  61. system("pause");
  62. return 1;
  63. }



5 父类和子类相互转换时的内存状况

  1. Student:父类
  2. GraduateStudent:子类
  3. GraduateStudent gs;
  4. (1)Student s = gs;    // 正确
  5. (2)Student& t = gs;   //正确
  6. (3)Student* p = &gs;  //正确
  7. (4)GraduateStudent gs = s;    // 错误
  8. (5)Graduatestudent* pGS = &s;   //错误
  9. (6)Student* pS = reinterpret_cast<Student*>(&gs);  // 正确
  10. (7)GraduateStudent* pGS = reinterpret_cast<GraduateStudent*>(&s);  // 错误

分析一:(1)正确,但(4)不正确。这是因为在子类对象中包含有父类对象的实体,可以拿出来父类部分为父类对象赋值。但是基类中数据不充分,不含有子类中的全部信息,所以拒绝执行父类对子类的赋值。对于指针,也是同样原因。(3)正确,(5)错误。原因同上。

分析二:(1)(2)(3)正确,表示子类对象可以为父类对象、父类对象引用、父类对象指针赋值。其中(1)需要调用拷贝函数,而(2)(3)不需要调用拷贝构造函数。

分析三:(6)和(3)一样,而且都正确。原因同分析一。即可以使用子类指针为父类指针赋值。

分析四:(7)和(5)一样,都错误。原因同分析一。即不可以使用父类指针为子类指针赋值。

内存图:

从内存图可以看出,

1、父类对象s复制了子类对象gs中的Student部分,构成了名副其实的Student对象实体。

2、父类对象的引用 t 只是引用了子类对象中的Student部分。

3、父类对象的指针 p 指向子类对象gs的首地址,它恰好是子类对象中的Student部分的首地址。

4、子类指针可以强制类型转化给父类指针。这是由于在父类GraduateStudent中,学生对象的地址和研究生对象的地址是重合的,研究生对象指针(子类指针)转化为学生指针(父类指针)时,只是把指针类型换了换而已,而指针的地址即指针指向没有改变。所以,上例中(7)也是错的,因为学生对象和研究生对象的地址不重合。

C++ 继承与派生的更多相关文章

  1. c++学习--继承与派生

    继承和派生 1 含有对象成员(子对象)的派生类的构造函数,定义派生类对象成员时,构造函数的执行顺序如下: 1 调用基类的构造函数,对基类数据成员初始化: 2 调用对象成员的构造函数,对对象成员的数据成 ...

  2. 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)

    [源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...

  3. [C++]类的继承与派生

    继承性是面向对象程序设计的第二大特性,它允许在既有类的基础上创建新类,新类可以继承既有类的数据成员和成员函数,可以添加自己特有的数据成员和成员函数,还可以对既有类中的成员函数重新定义.利用类的继承和派 ...

  4. O-c中类的继承与派生的概念

    什么是继承 众所周知,面向对象的编程语言具有: 抽象性, 封装性, 继承性, 以及多态性 的特征. 那么什么是继承呢? 传统意义上是指从父辈那里获得父辈留下的东西 在开发中, 继承就是"复用 ...

  5. 程序设计实习MOOC / 继承和派生——编程作业 第五周程序填空题1

    描述 写一个MyString 类,使得下面程序的输出结果是: 1. abcd-efgh-abcd- 2. abcd- 3. 4. abcd-efgh- 5. efgh- 6. c 7. abcd- 8 ...

  6. 走进C++程序世界------继承和派生

    继承和派生 继承是面向对象编程语言的最重要方面之一,正确的使用继承可编写出设计良好,容易于维护和扩展的应用程序.下面是在其他博客中的总结: ****************************** ...

  7. C++学习之路—继承与派生(一):基本概念与基类成员的访问属性

    (本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1   基本思想与概念 在传统的程序设计 ...

  8. C/C++基础知识总结——继承与派生

    1. 类的继承与派生 1.1 派生类的定义 (1) 定义规范 class 派生类名: 继承方式 基类1名, 继承方式 基类2名... { ...派生类成员声明; }; (2) 从以上形式上看可以多继承 ...

  9. python基础——继承与派生、组合

    python基础--继承与派生 1 什么是继承: 继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类成为基类或超累,新建的类成为派生类或子类 1.1 继承分为:单 ...

  10. 【C++ 实验六 继承与派生】

    实验内容 1. 某计算机硬件系统,为了实现特定的功能,在某个子模块设计了 ABC 三款芯片用于 数字计算.各个芯片的计算功能如下: A 芯片:计算两位整数的加法(m+n).计算两位整数的减法(m-n) ...

随机推荐

  1. ARMV8 Procedure Call Standard

    1.前言 2.  术语说明 Term Note ABI Application Binary Interface 应用程序二进制接口 EABI Embedded ABI  嵌入式ABI PCS Pro ...

  2. 015_NGINX作为WebSocket Proxy的设置

    产研那边有通过nginx代理进行长连接的需求,咱们都知道默认nginx只支持短连接的,使用长连接需要单独配置 一. websocket协议提供创建一种支持在server和client之前双向通信的we ...

  3. Oracle12c 性能优化攻略:攻略1-1:创建具有最优性能的数据库

    一:章节前言 本章着眼于影响表中数据存储性能的数据库特性. 表的性能部分取决于在创建之前所应用的数据库特性.例如:在最初创建数据库时采用的物理存储特性以及相关的表空间都会在后来影响表的性能.类似地,表 ...

  4. [工具/PC]计算机中丢失libiconv-2.dll,丢失libintl-8.dll,无法定位程序输入点libiconv于动态链接库libiconv-2.dll上问题解决方法

    CodeBlocks 1. 背景,为了学习C语言,在win系统上下载了codeBlock,先简单介绍下:Code::Blocks 是一个开放源码的全功能的跨平台C/C++集成开发环境. Code::B ...

  5. js字符串截取为数组

    var str="hello,word,java,eclipse,jsp"; //字符串截取为数组 var strArr=str.split(","); for ...

  6. Twitter开源的Heron快速安装部署教程

    什么是Heron? Twitter使用Storm实时分析海量数据已经有好几年了,并在2011年将其开源.该项目稍后开始在Apache基金会孵化,并在2015年秋天成为顶级项目.Storm以季度为发布周 ...

  7. 《JavaScript 高级程序设计》第四章:变量、作用域和内存问题

    目录 变量的引用 执行环境及作用域 作用域链延长 块级作用域 垃圾回收机制 变量的引用 当一个变量保存了基本数据类型时,此时对于变量的操作(赋值,运算)就是操作这个基本数据的本身,就算是赋值操作,赋值 ...

  8. 交叉编译和安装ARM板(RK3288)和Linux 3.10上的RTL8188无线网卡驱动

    插入无线网卡,输入ifconfig,发现没有检测到网卡. 输入lsusb,查看无线网卡型号. 我用的无线网卡是EDUP的网卡,包装盒里有一张驱动光盘,把光盘里linux下的驱动目录复制下来.如果没有驱 ...

  9. windows下的python环境搭建(python2和python3不兼容,python2用的多)

    Windows平台下搭建python开发环境 以下为在 Window 平台上安装 Python 的简单步骤: 打开WEB浏览器访问http://www.python.org/download/ 在下载 ...

  10. php 前一天或后一天的日期

    php 判断今天的前一天,或前后多少天的代码 <?php date_default_timezone_set('PRC'); //默认时区 echo "今天:",date(& ...