继承的意义?

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

什么叫继承?

(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. mysql系列四、mySQL四舍五入函数用法总结

    一.MySQL四舍五入函数ROUND(x) ROUND(x)函数返回最接近于参数x的整数,对x值进行四舍五入. 实例: 使用ROUND(x)函数对操作数进行四舍五入操作.SQL语句如下: mysql& ...

  2. Centos6.5使用yum安装mysql——快速上手必备

    第1步.yum安装mysql [root@stonex ~]#  yum -y install mysql-server 安装结果: Installed:     mysql-server.x86_6 ...

  3. oem 重建

    OracleDBControl启动失败to local from URL=http://your-url.co     方法: emca -deconfig dbcontrol db -repos d ...

  4. 使用Let's Encrypted HPPTS你的网站

    1.前言 最近,有同事咨询我,怎么样使用Let's Encrypted部署数字证书,于是,结合自己之前的实践,简单总结下. 2.HTTPS的优势 什么加密,防篡改,防广告植入什么的,这个就不多说了.这 ...

  5. 560. Subarray Sum Equals K

    Given an array of integers and an integer k, you need to find the total number of continuous subarra ...

  6. Python-垃圾回收机制

    引子: 我们定义变量会申请内存空间来存放变量的值,而内存的容量是有限的,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,而变量名是访问到变量值的唯一方式,所以当一个变量值没有关联任何变量 ...

  7. 关于z-index的那些事儿

    关于z-index的真正问题是,很少有人理解它到底是怎么用.其实它并不复杂,但是如果你从来没有花一定时间去看具体的z-index相关文档,那么你很可能会忽略一些重要的信息. 不相信我吗?好吧,看看你能 ...

  8. php实现概率性随机抽奖代码

    1.初始数据: 权重越大,抽取的几率越高 [奖品1, 权重 5], [ 奖品2, 权重6], [ 奖品3, 权重 7], [ 奖品4, 权重2] 2.处理步骤: 1)N = 5 + 6 + 7 + 2 ...

  9. ECMAscript5 新增数组内函数

    indexOf() 格式:数组.indexOf(item, start) 功能:从start这个下标开始,查找item在数组中的第一次出现的下标. 参数:item 我们要去查找的元素 start从哪个 ...

  10. ORA-12638: 身份证明检索失败 的解决办法

    今天在使用应用程序连接Oracle时碰到了 “ORA-12638: 身份证明检索失败” 错误, 解决方法:这是因为Oracle-client端的高级安全性验证导致,解决办法如下: 开始 -> 程 ...