5、继承与派生5-派生类成员的标识和访问
围绕派生类吸收基类成员,改造基类成员和添加新成员的过程,主要讨论派生类使用过程中的一些问题,在派生类中,成员可以按访问属性划分为四种:
1)不可访问的成员
这是从基类私有成员继承而来的,派生类或是建立派生类对象的模块都没有办法访问到他们,如果从派生类继续派生新类,也是无法访问的。
2)私有成员
这里可以包括从基类继承过来的成员以及新增加的成员,在派生类内部可以访问,但是建立派生类对象的模块中无法访问,继续派生,就变成了新的派生类中的不可访问成员。
3)保护成员
可能是新增也可能是从基类继承过来的,派生类内部成员可以访问,建立派生类对象的模块无法访问,进一步派生,在新的派生类中可能成为私有成员或者保护成员。
4)公有成员
派生类、建立派生类的模块都可以访问,继续派生,可能是新派生类中的私有、保护或者公有成员。
在对派生类的访问中,实际上有两个问题需要解决:第一是惟一标识问题,第二个问题是成员本身的属性问题,严格讲应该是可见性问题。我们只能访问一个能够惟一标识的可见成员。
1、作用域分辨
作用域分辨符,就是我们经常见到的"::“,它可以用来限定要访问的成员所在的类的名称,一般的使用形式是:
基类名::成员名;//数据成员
基类名::成员名(参数表);//函数成员
对于在不同的作用域声明的标识符,可见性原则是:如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层仍然可见;如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称内层变量隐藏了外层同名变量,这种现象称为隐藏规则。
在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域,二者的作用范围不同,是相互包含的两个层,派生类在内层。如果派生类声明了一个和基类成员同名的新成员,派生类的新成员就隐藏了外层同名的成员,直接使用成员名只能访问到派生类的成员。
在没有虚函数的情况下,如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏。如果要访问被隐藏的成员,就需要使用作用域分辨符和基类名来限定。
在没有虚函数的情况下,如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员。这时使用"对象名.成员名"方式可以惟一标识和访问派生类新增成员,基类的同名成员也可以使用基类名和作用分辨符访问。
eg:多继承同名隐藏举例
#include<iostream>
using namespace std;
class B1
{
public:
int nV;
void fun(){cout<<"Member of B1"<<endl;}
};
class B2
{
public:
int nV;
void fun(){cout<<"Member of B2"<<endl;}
};
class D1:public B1,public B2
{
public:
int nV;
void fun(){cout<<"Member of D1"<<endl;}
};
int main()
{
D1 d1;
d1.nV=1;
d1.fun();
d1.B1::nV=2;//使用对象名.积累名::成员名的访问方式
d1.B1::fun();
d1.B2::nV=3;
d1.B2::fun();
getchar();
}
输出结果:
Member of D1
Member of B1
Member of B2
在上面讨论的基类之间是没有继承关系,如果这个条件不满足会出现什么情况呢?如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名现象,对这种类型的同名成员也要使用作用域分辨符来惟一标识,而且必须用直接基类来进行限定。
如:有一个基类B0,声明了数据成员nV和函数fun,由B0公有派生产生了类B1和B2,再以B1、B2作为基类共同公有派生产生了新类D1,在派生类中我们不再添加新的同名成员,这时的D1类,就含有通过B1、B2继承来的基类B0中的同名成员nV和fun。
#include<iostream>
using namespace std;
class B0
{
public:
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1:public B0
{
public:
int nV1;
};
class B2:public B0
{
public:
int nV2;
};
class D1:public B1,public B2
{
public:
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main()
{
D1 d1;
d1.B1::nV=2;//使用直接基类
d1.B1::fun();
d1.B2::nV=3;//使用直接基类
d1.B2::fun();
getchar();
}
程序运行结果:
Member of B0
Member of B0
这种情况下,派生类对象在内存中就同时拥有成员nV及fun的两份同名拷贝,对于数据成员来讲,虽然两个nV可以分别通过B1和B2调用B0的构造函数进行初始化,可以存放不同的数值,也可以使用作用域分辨符通过直接基类名限定来分别进行访问。但在很多情况下,我们只需要一个这样的数据拷贝,同一成员的多份拷贝增加了内存的开销。c++提供了虚基类技术来解决这个问题。