什么是继承?
1.语法
1.1例子
通过这种方法,Student和Teacher这两个类就继承了Person的成员变量和成员函数,可以直接调用它们。
如图,如果成员变量和成员函数在基类中是公有的话就可以直接访问!但如果是私有和保护的话就无法访问。
1.2继承基类成员访问方式的变化
1.先看这个表格的最后一行,只要在基类中是私有成员,不论哪种继承方式,在派生类中均不可见。这里的不可见不是说真的不可见,而是在派生类外边和里边不可以直接访问,但是派生类可以通过基类中公有成员函数访问。(注意,不论是成员函数还是成员变量都是成员,如果它们是私有,都不能直接在类外面访问,只能通过其他公有的成员函数访问)。
2.另外两行需要记住一个规则(权限:public>protected>private)。记住这个规则后,在继承过程中,最终结果去权限小的那一个。
比如,基类的protected成员被派生类public继承后,由于protected权限小于private,所以基类的protected成员在派生类中依然是保护成员。
再比如,基类的protected成员被派生类private继承后,由于protected权限大于private权限,所以基类的protected成员在派生类中变成private成员。
1.2.1protected和private的区别
解释一下,基类中的private成员不光在基类和派生类外面不能访问,而且派生类里面也不能访问。
而基类中的protected成员,在基类和派生类外面不能访问,但在派生类里面可以被访问。
熟悉继承的语法后就可以理解,其实实践中用得最多的直接上面的一小部分。因为继承的意义在于派生类能够顺畅的使用基类的成员,所以protected继承和private继承就极少用到。所以我们在日常的学习甚至工作中记住上面这个小表格就足够了。
再补充一下,struct也可以继承,但要注意与class的区别。
1.class的默认访问限定符是private,在没写继承方式时,继承方式同样默认private继承。‘
2.struct的默认访问限定符是public,在没写继承方式时,继承方式同样默认public继承。
虽然它们有默认的访问限定符和默认的继承方式,但还是建议写上,一是格式更加标准,二是提高代码的可读性。
2.基类和派生类对象赋值转换
在派生类对象对基类对象赋值时,不会产生临时对象,所以在传引用时不用加const(可以理解为编译器的特殊处理)。但是基类对象不可以赋值给派生类对象。
赋值兼容,编译器进行特殊处理。
这是is -a的关系,就把派生类和基类当成一个类型,在赋值过程中没有类型的转换就不产生临时对象。所以传引用和传指针时,通过派生类改变成员变量,基类中的也会随之改变。
因为基类的对象的成员变量派生类中都有,所以在赋值时,相当于在把派生类中含有基类成员变量的部分进行切片然后赋值给基类。传指针时,基类的指针同样指向该切片的空间。所以在传引用和指针后,对基类的成员变量修改,会让派生类对应的成员变量发生改变。
派生类中的内置类型成员直接赋值给基类,自定义类型成员调拷贝构造给基类。
3.继承中的作用域
学到现在,一共有几个域的概念:
1.局部域 2.全局域 3.命名空间域 4.类域
局部域和全局域会影响访问也会影响声明周期(同一个域不能有同名变量和同名函数,不同的域可以同名),类域和命名空间域只影响访问不影响声明周期。
在类域中又可以细分为基类的类域和派生类的类域。
基类和派生类可以有同名的成员变量,在访问时遵循“就近原则”。在派生类访问时,默认访问派生类的,如果想访问基类的,需要域作用限定符。
3.1练习一下
这个题目的正确答案是D。
对于C:两个函数构成重载的前提是:1.在同一个域中。2.函数命相同。3.参数不同。所以C不对。
公有继承后,在派生类里面,同名函数这样访问:
在派生类外面,同名函数这样访问:
把基类成员当成一个整体,把它们看成一个自定义类型。派生类成员还是和以前一样。
在派生类对象实例化时,会调用父类的默认构造函数,如果没有,编译报错。
回顾一下类和对象中的构造函数:
在对象实例化时,编译器默认生成的构造函数对于成员变量中的内置类型(int char等)不做处理,对于自定义类型(vector string等),去调用它们自己的默认构造。
关于构造函数
如果基类没有默认构造函数,编译报错:
除非在派生类的构造函数中显式的调用基类的构造函数:
当然如果基类中有默认构造函数,就不需要在派生类的构造函数中显式的调用基类的构造函数了:
类比:
如果不用继承,就把Person当成BB的成员,在Person没有默认构造时,同样需要在BB的构造函数中显式的调用Person的构造函数,区别就是继承类似于匿名对象的初始化,这个是有名的。
因此,继承,你就可以想象成有一个基类对象变成了派生类的成员,所以它是一个整体,它的初始化只能去调它的构造函数,不可以在派生类的构造函数中逐一初始化它的成员。
关于拷贝构造:
对于派生类调用编译器默认生成拷贝构造时,对于内置类型进行值拷贝。对于自定义类型(vector,string)调用它们的拷贝构造,对于基类(也是自定义类型),也是调用基类的拷贝构造。(所以一般派生类中编译器默认生成的拷贝构造是够用的)。
假设需要显示的写拷贝构造:
这是基类的拷贝构造。
这是派生类的拷贝构造(借用了基类的拷贝构造,利用了切割/切片)。
关于赋值:
派生类调用赋值时(编译器默认生成的),对于内置类型直接赋值,对于自定义类型(包括基类)回去调用它们的赋值。
如果显示的写,如下:
对于析构函数:
派生类的析构会隐藏基类的析构,需要基类的访问限定符才能访问到基类的析构。
构造要求先父后子,析构要求先子后父。
因此:
所以:
在派生类的析构函数中不要手动调用基类的析构函数,否则基类会析构两次!