C++复习(2)派生类成员的标志与访问

来源:互联网 发布:mac 显卡驱动 编辑:程序博客网 时间:2024/05/17 18:13

首先,在派生类中,成员按照访问属性可以分为以下四类:

(1)不可访问成员;这些成员是通过继承得到的,比如继承的基类中原有的私有成员。

(2)私有成员;

(3)保护成员;

(4)公有成员。

如果这些成员中,有相同的名字的话该怎么办呢?比如我们继承的基类中有个成员名叫做test,在我们的派生类中也有个成员名字叫做test,那该怎么区分它们呢?这就是本文主要探讨的问题。

1、作用域分辨符

在类的派生的层次结构中,基类和派生类的成员都有各自的类作用域,两者的关系是:基类的作用域在外层,派生类的类作用域在里层

如果派生类声明了一个和基类中名字相同的变量,那么派生类的新成员则会隐藏基类中的同名成员,直接使用对象或者指针只能访问到派生类中的新成员。这是对于成员变量来说,那么对于成员函数呢?如果派生类中声明了一个和基类中名字相同的函数,即使函数的参数表不同,也会隐藏基类中所有函数名为该名称的重载形式。这是因为只有在相同的作用域中定义的函数才可以重载,派生类和基类的同名函数在不同的作用域内,因此不能重载)

那这种情况下如何访问原有的基类中的同名变量和函数呢,可以通过作用域分辨符来访问。具体看如下代码:

#include <iostream>using namespace std;class Base1//基类1{public:int var;void fun(){cout<<"Base1 var = "<<var<<endl;}};class Base2//基类2{public:int var;void fun(){cout<<"Base2 var = "<<var<<endl;}};class Derived:public Base1,public Base2//派生类{public:int var;void fun(){cout<<"Derived var = "<<var<<endl;}};int main(){Derived d;d.var = 3;d.fun();d.Base1::var = 1;d.Base1::fun();d.Base2::var = 2;d.Base2::fun();return 0;}

可以看出我们上面说的是对的。当然了,其实这样做的目的就是为了不产生语义的二义性,用其他方法也可以代替,例如

using Base1::var;using Base2::fun;
这种情况下默认的使用的就是Base1中的var变量,Base2中的fun()函数了。

但是还有一个问题,我们上面的两个基类Base1和Base2都是没有基类的,那如果他们有共同的基类,派生类该如何访问他们基类的基类的成员呢?

看如下代码:

#include <iostream>using namespace std;class Base0{public:int var0;void fun(){cout<<"Base0 var = "<<var0<<endl;}};class Base1:public Base0//基类1{public:int var;};class Base2:public Base0//基类2{public:int var;};class Derived:public Base1,public Base2//派生类{public:int var;};int main(){Derived d;d.Base1::var0 = 1;d.Base1::fun();d.Base2::var0 = 2;d.Base2::fun();return 0;}

运行结果如下:

其实这个时候,派生类对象d的内存中存了5个数据,分别为Base1::var0,Base1::var,Base2::var0,Base2::var,Derived::var,存放了两份成员函数Base1::fun(),Base2::fun()。

这是因为如果派生类的部分或者全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也有产生同名的现象,对于这种类型的同名成员也要使用作用域分辨符作为唯一标识,而且必须使用直接基类来进行限定。

这时如果我们使用

d.Base0::var = 1;d.Base0::fun();
就会出错,原因是语义不明,也就是编译器不知道现在用的这一份是从Base1中来的,还是从Base2中来的。

就像我们上面说的,如果派生类的若干直接基类有共同基类的话,派生类对象中会存放多份直接基类的共同基类中的成员副本,这样会比较占用内存,那么如何解决这个问题呢,就是我们接下来要讲得虚基类了

2、虚基类

虚基类的声明是在派生类的定义过程中进行的,如下:
class 派生类:virtual 继承方式 基类
这种情况下,基类就变成虚基类了。这样在基类的多个派生类的同一派生类对象中,对于该基类的数据成员副本只有一份,同时函数也就只有一个映射。
这里需要注意,虚基类的派生类定义的成员名称不能与虚基类中名称相同,否则还是会按照上面我们提到的方法,保存多份副本,因为不知道到底是哪个类中的变量。
使用代码如下:
#include <iostream>using namespace std;class Base0{public:int var;void fun(){cout << "Base0 var = " << var << endl;}};class Base1 :virtual public Base0{public:int var1;void fun1(){cout << "Base1 var1 = " << var1 << endl;}};class Base2 :virtual public Base0{public:int var2;void fun2(){cout << "Base2 var2 = " << var2 << endl;}};class Derived :public Base1, public Base2{int vard;};int main(){Derived test;test.var = 5;test.fun();}
可以看到这种代码编译是不会有问题的,运行结果如下:
这时在test对象的内存占用中,对于var变量只保存了一份,这就是虚基类的作用。

实际上,虚基类和我们上面提到的通过作用域标识符的方法是两种很常见的方法,不能说这种方法好那种方法差,因为他们适用于不同的情境下。使用虚基类确实可以减少内存的占用,但是如果Base1和Base2中的方法用到了Base0中的变量呢?那只有一份副本的话是不是会出现问题呢?

如上面所说的,虚基类的使用时比较方便的,这时的虚基类的构造函数是默认的构造函数,但是当虚基类的构造函数不是默认的,而且虚基类没有声明默认的构造函数,这样就比较麻烦了。在派生类的构造函数的初始化列表中要列出对其所有基类和虚基类的初始化。
现在总结一下类的构造函数的流程:
(1)如果该类中有直接或间接的虚基类,那么首先执行该虚基类的构造函数;
(2)如果该类中有其他基类,那么按照继承的声明列表顺序执行他们的构造函数;
(3)执行派生类构造函数中的初始化列表中的初始化,按照在类中的声明顺序来,与初始化列表中的顺序没有关系;
(4)执行构造函数体中的内容。

参考文献:
《C++语言程序设计(第四版)》.郑莉等. 清华大学出版社

0 0
原创粉丝点击