C++的三大特性之一继承

一、继承的相关基本概念

1、继承的定义

在C++中,可以使用继承来使新类得到已定义的一些类中的特性,这就好比与孩子从父亲母亲得到遗传类似,所以我们称原有的类为基类或父类,用原有类来生成新的类的过程称为派生,所以生成的新类称之为派生类或者子类。

2、 继承的声明

在继承中和上面所说的遗传是有区别的,孩子只能遗传其父母的一些基因,而在C++的继承中,一个新的类是可以继承多个不同的类,被称为多重继承。所以继承分为单继承和多继承。

继承的定义格式如下:

在一个类中,我们知道其成员的访问类型分为三种,公有(public),保护(protected)和私有(private)。相应的继承方式也分为这三种,他们的区别也就在于派生类的成员和类外对象在访问它从基类继承来的的成员的访问权限不同。

关于三种继承方式后的成员访问权限变换关系如下表所示:

其实这个表我自己是看着很烦,其实很容易记的,根本就不需要看这个表的。你只要记得基类的private成员在派生类中始终是不可访问的,然后记着private>protected>public,高访问权限的继承方式会改变低的访问权限成员的访问权限。说起来还是比较绕口,其实并不需要记,你看一遍肯定会牢记在心的,不信你试试看。

可以看出来,不管是哪一种继承方式,在派生类内都可以访问基类的非私有成员,基类的私有成员虽然被继承了,但是并不是可见的。而对于保护和私有继承方式,类外对象并不能访问基类的成员,公有继承方式下可以访问基类的公有成员。

二、构造函数和析构函数

在派生类中,从基类继承而来的成员初始化需要调用基类的构造函数,派生类中新增的成员初始化则调用自己的构造函数。

1.继承中的构造函数

这里,当基类没有定义自己的构造函数,则在派生类中也可以不用定义,在构造对象时自行调用默认构造函数,但一般在编译器里会认为构造函数并不做什么事情所以对代码进行优化,并不生成默认构造函数,当你在转到反汇编看汇编代码的时候是看不到调用构造函数的语句的。

当基类显式地定义了自己的构造函数,则编译器为派生类生成默认构造函数,而且当基类没有缺省的构造函数的时候,就必须为派生类定义构造函数且在初始化列表中显式的给出基类名和其参数列表,不然在派生过程中编译器就不知道如何调用基类构造函数。

上面这两点其实与在类中包含另一个类的对象的情况差不多,我们也很容易理解。那么派生类到底能不能继承基类的构造函数呢?

这一点很多人说法都不一样,有人说派生类继承了基类的构造函数,因为你在构造一个派生类对象的时候会调用基类的构造函数。其实准确的说应该是带引号的继承,之所以能够调用基类的构造函数,是因为编译器使基类的构造函数在派生类中可见,在创建派生类的对象时会先调用基类的构造函数。

构造函数的调用顺序:

因为在创建派生类对象的时候,要先给它继承基类的成员,所以当程序走到派生类的构造函数的时候会先调用基类的构造函数,所以对于构造函数的调用顺序,是按照继承列表的顺序依次调用每一个基类的构造函数,然后调用派生类自己的构造函数,再执行它的函数体。当然如果派生类中还有别的类对象,则先调用此对象类的构造函数再调用派生类自己的构造函数。

2.继承中的析构函数

析构函数是不能被继承的,同时一般会把基类的析构函数定义成虚析构函数:

 1 class Base
 2 {
 3 public:
 4     Base(){}
 5     virtual ~Base()
 6     {
 7         cout << "~Base()" << endl;
 8     }
 9 public:
10     int _pub1;
11 };
12 class Derived:public Base
13 {
14 public:
15     Derived(int k=1)
16     {
17         buf = new char[k];
18     }
19     ~Derived()
20     {
21         delete[] buf;
22         cout << "~Derived()" << endl;
23     }
24 public:
25     char* buf;
26 };
27
28 void test()
29 {
30
31     Base* b = new Derived(5);
32     delete b;
33 }
34 int main()
35 {
36     test();
37     return 0;
38 } 

如果不定义成虚函数,在delete时只会调用基类的析构函数而不会调用派生类的析构函数而导致内存泄漏。虚函数在这里不细讲了。

析构函数的调用顺序:

析构函数的调用情况,我们一般认为和压栈类似,所以析构函数的调用顺序如下:

三、继承中的同名隐藏

在C++中我们知道有重载,当在同一作用域内函数名相同且函数的参数列表不同,就会构成重载,这样就可以根据传参的不同来调用相应的函数,而不会存在二义性。

但是在派生类中如果有一个和基类中同名的函数,那么在派生类中基类的这个函数就是被屏蔽的,当你用派生类对象调用这个函数一定是调用派生类中的函数,即使两个函数的参数列表不同。但其实基类的这个函数还是被继承了的,要想调用可以使用作用域解析符进行调用。(对于一个同名的成员也是同样的道理)。例子如下:

 1 class A
 2 {
 3 public:
 4     void test(int)
 5     {
 6         cout << "test1()" << endl;
 7     }
 8
 9 public:
10     int _pub1;
11 protected:
12     int _pro1;
13 private:
14     int _pri1;
15 };
16
17 class B :public A
18 {
19 public:
20     void test()
21     {
22         cout << "test2()" << endl;
23     }
24 public:
25     int _pub2;
26 protected:
27     int _pro2;
28 private:
29     int _pri2;
30 };
31 int main()
32 {
33     B b;
34     b.test();         //编译错误
35     b.test(3);      //编译错误
36     b.A::test(3);   //正确
37     return 0;
38 }

四、继承中的赋值兼容规则

在此之前,我们看看基类和派生类的对象模型:

 1 class Base
 2 {
 3 public:
 4     Base() { cout << "Base()" << endl; }
 5     ~Base() { cout << "~Base()" << endl; }
 6 public:
 7     int _pub1;
 8 protected:
 9     int _pro1;
10
11 };
12 class Derived :public Base
13 {
14 public:
15     Derived()   { cout << "Derived()" << endl; }
16     ~Derived()  { cout << "~Derived()" << endl; }
17 public:
18     int _pub2;
19 protected:
20      int _pro2;
21 };
22 int main()
23 {
24     return 0;
25 }

假如就这样定义基类和派生类,那么派生类继承了基类的_pub1和pro1成员

赋值兼容规则如下:

1、派生类对象可以直接赋值给基类对象

基类对象不能赋值给派生类对象

2、基类类型指针可以指向派生类对象(派生类对象可以初始化基类的引用)

派生类类型指针不可以指向基类对象

这里也比较容易理解(当然是在public继承下),一个派生类对象本来就是从它的基类继承而来的,向上图中的对象模型,在派生类中有与基类对应的一个模块是继承来的成员,那么在赋值过程中编译器是可以把相应的基类部分赋值给基类对象。而对于把一个基类对象赋值给派生类对象的话,

五、理解“is a”和“has a”

我一开始就很不理解为啥要总结这么样的关系,还这么抽象,什么东西啊。后来写写代码也确实慢慢领会了一点,还是有那吗一丝丝韵味在其中的。

is a:

is a就是有一个,对于public继承,就有着is a特性。在上面的赋值兼容规则中也说到了,一个派生类对象时可以赋值给基类对象的,所以派生类可以代替任何需要直接基类的地方。is a就是代表了这种继承关系。对于多继承或者在派生类对象中有新增加的东西,这种关系相当于is like。

has a:

has a一般用来描述是组合这种关系的,就是在某一个类中有另一个类。那么在这个类中就可以用它包含的类的成员及成员函数,有时候我们可以把保护和私有继承也看成是一种has a关系,因为只有在类内才可以访问基类的成员。

is a相当于父亲在儿子家干活,而has a相当于雇别人在家里干活。大概就是这么个意思,大家能理解就行了,对于组合和继承这里就不展开讲了,它们各有优缺点和用武之地。在我们利用继承的时候,并不是说我们需要在这个类中使用另一个类的某些东西就继承它,要保证这个类是和基类是有is a关系的,比如说老虎是动物的一种,这才叫继承。

时间: 2024-11-08 12:01:49

C++的三大特性之一继承的相关文章

【转】java提高篇(二)-----理解java的三大特性之继承

[转]java提高篇(二)-----理解java的三大特性之继承 原文地址:http://www.cnblogs.com/chenssy/p/3354884.html 在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句话中最引人注目的是"复用代码",尽可能的复用代码使我们程序员一直在追求的,现在我来介绍一种复用代码的方式,也是java三大

java随笔:三大特性之继承

Java三大特性之继承 一.介绍 笔记重点:构造器.protected关键字(这个自行查阅).向上转型.private关键字(继承非常重要的要点)  复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句话中最引人注目的是"复用代码",尽可能的复用代码使我们程序员一直在追求的. 继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继

(1) 深入理解Java面向对象三大特性 封装 继承 多态

转眼已经工作快6年了,最开始做了2年J2EE:然后整了2年的数据仓库,主要是Cognos的报表开发:现在从事4G LTE核心网的开发,用的语言任然是Java,但写代码的机会不多,基本都是看代码找BUG,偶尔做点new feature也是在比较成熟的框架上复制.粘贴.修改,大部分时间还是在理解业务,钱多.事少.离家近,当时来这家公司图的是后面2点,2年过去了,英文水平有所提升,对敏捷开放也有一定的了解,但技术方面明显退步了或者说没有进步吧,本来以前也不怎么好,因为工作上用不到,自己也没怎么学习,所

面向对象的三大特性之继承-基础知识总结------彭记(05)

面向对象的三大特性之继承: js中的某些对象没有一些需要的属性和方法,但是另外的对象有,那么拿过使用,就是继承. js中继承的实现方式: 1.混入式继承:通过循环将一个对象中的所有属性和方法混入到另外一个对象中: var me={ work:function(){ console.log('敲代码'): } }: var mayun={ money:9999999, car:'000', manager:function(){ console.log('管理巴巴...'); } } /*遍历,让

[转载]CSS三大特性(继承、优先级、层叠)之个人见解

首先声明一下CSS三大特性——继承.优 先级和层叠.继承即子类元素继承父类的样式,比如font-size,font-weight等f开头的css样式以及text-align,text- indent等t开头的样式以及我们常用的color.简单的就不演示了,强调一下font-size这个东东(虽然也有继承,但是标签不同继承的效果也 不一样),比如下面的代码: <!DOCTYPE html> <html lang="en"> <head> <met

面向对象编程三大特性之继承

面向对象编程有三大特性:继承,封装,多态~ 从object基类开始,逐步向下开枝散叶,组成了整个对象体系. 为什么要用这种形式? 解答这个问题之前,我们可以先回顾一下生物的进化史~ 从单细胞开始到现在的哺乳动物,在不断地进化中,物种的身体结构愈加复杂,这种进化所花费的时间非常漫长,但是一旦进化成功,后代就可以直接拥有父母的结构,而不用再次花费漫长的时间用来进化. 现在我们再次回到继承上来,object基类支持 .NET Framework 类层次结构中的所有类,并为派生类提供低级别服务.这是 .

java基础篇(二) ----- java面向对象的三大特性之继承

java面向对象的三大特性之继承: 复用代码是java众多引人注目的功能之一,但是想要成为极具革命性的语言,仅仅是复制代码并对其加以改变是不够的.它必须能够做更多的事.引自<Think in java>    而代码复用也是程序员一直不断追求的.由此来说下代码复用的一种方式 java面向对象的三大特性--继承!! 在面向对象程序设计中,继承机制可以有效地组织类的结构.确定类之间的关系,在已有类的基础上开发新的类.继承机制在程序代码复用.提高软件开发效率.降低软件系统维护成本等方面具有重要作用.

面向对象的三大特性之继承

# 面向对象的三大特性之继承 # 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好 (如机器人) # 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好 (动物--猫) # 继承同时有两种含义:1,继承基类的方法,并且做出自己的改变或扩展(代码重用),这种意义并不大,因为它使子类和基类出现强耦合 # 2,声明某个子类兼容于某基类.定义一个接口类,子类继承接口类,并实现接口中定义的方法 # 接口继承只是接口类规定了子类必须实现的方法,但是接口类里不去实现,子类

面向对象的三大特性---封装继承多态

面向对象的三大特性---封装继承多态 首先我们来创建一个控制台程序 新建一个父类Person 创建一个子类继承父类Person 子类: main函数: 输出结果: 根据结果显示创建子对象,首先调用父类的构造函数在调用子类的构造函数. 继承的传递性 例如: A:B,B:C,则A:C ⅰ.继承过来之后被隐藏,有意隐藏加一个关键字"new",如果加了关键字"new",就告诉子类,父类里面的sayHi只能被隐藏,不能被覆写  ⅱ.继承过来之后被覆写/重写,覆写需要在父类里面

Python面向对象之:三大特性:继承,封装,多态。

前言: python面向对象的三大特性:继承,封装,多态. 1. 封装: 把很多数据封装到?个对象中. 把固定功能的代码封装到?个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了?个很?B的函数. 那这个也可以被称为封装. 在?向对象思想中. 是把?些看似?关紧要的内容组合到?起统?进?存储和使?. 这就是封装. 2. 继承: ?类可以?动拥有?类中除了私有属性外的其他所有内容. 说?了, ??可以随便?爹的东?. 但是朋友们, ?定要认清楚?个