第二章 继承和派生
2.1 派生
派生类或派生类的使用者均不能访问基类的私有数据成员;对于基类的公有成员的访问,如果派生的方式不同,访问的权限也不同。派生时,不指明派生类型的,按私有派生进行派生。
<1>私有派生
由私有派生得到的派生类,对基类的公有成员只能是私有继承,也就是基类的所有公有成员只能成为派生类的私有成员,这些私有成员只能被派生类的成员函数访问,派生类的使用者无权访问。此时,基类中的保护段成员也成为派生类的私有成员。
如果希望基类中的某些公有成员、保护成员在私有派生类中也是公有、保护的,使得派生类和派生类的使用者可以访问它,则可以在派生类中的公有段或保护段中说明这些成员,并在成员名前缀上“类名::”。
class A {
int count;
public:
A(int i = 0){
count = i; }
int getCount(){
return count; }
};
class B :A{ //私有派生
public:
A::getCount; //声明基类的公有成员为派生类的公有成员
/*protected:
A::getCount; */ //声明基类的公有成员为派生类的保护成员
};
void main()
{
B b;
int num = b.getCount();
}
声明基类的公有成员为派生类的公有成员,只需要注明类名和函数名,不需要声明函数参数和返回值,因为该函数在基类中已经声明过。
私有段中不能进行声明。派生类中有与基类相同的函数时,这种声明是无效的,派生对象调用的函数是派生类中的,而不是基类的。在派生类公有段中声明基类的构造函数,如果基类构造函数(重载情况下)同在公有段中,则基类的构造函数成为派生类中的公有成员;如果基类构造函数处于不同的段中,系统将根据派生类构造函数调用基类构造函数的使用,决定这种声明的有效性。
Class A { protected: int count; void print1(); public: int num; void print(); A(); A(int i); };
class B: A{ protected: A::count; A::print1; public: B(); void print(); //A::count; //error不能同时声明 A::A; A::num; A::print; //声明无效 }; | class A { protected: A(int i); public: A(); };
class B: A{ public: B():A(){ } A::A; //public段A()有效 /* B():A(2){ } A::A; */ //protected段A()有效 };
|
<2>公有派生
通过公有派生得到派生类,基类中的公有成员在派生类中仍然是公有成员,基类中的保护段成员在派生类中仍然是保护成员。
<3>保护派生
通过公有派生得到派生类,基类中的公有成员和保护段成员,在派生类中是保护成员。
2.2 派生类的构造函数和析构函数
2.2.1派生类构造函数和析构函数的定义原则
在以下情况下必须定义派生类的构造函数
<1>派生类本身需要构造函数
<2>在定义派生类对象时,其相应的基类需要调用带参数的构造函数。
构造函数格式:
派生类构造函数(参数):基类构造函数(参数),对象成员1构造函数(参数),。。。对象成员n构造函数(参数) { //基类和对象成员构造函数的排放顺序不分先后
}
如果基类使用缺省的构造函数或不带参数的构造函数,则在派生类中定义构造函数时可略去“:基类构造函数(参数)”;如果派生类对象不需要初始化,也不必定义构造函数。
派生类的析构函数的定义与基类没有关系。
如果基类、成员类和派生类都有构造函数,执行顺序分别是先基类、再对象成员、后派生;析构函数时的执行顺序正好相反,先派生、再对象成员、后基类。
class A {
int count;
public:
A(int i = 0){
count = i;
}
~A(){
}
};
class B{
public:
B(char *str){
memset(s, 0, 20);
strcpy(s, str);
}
~B(){
}
private:
char s[20];
};
class C :A{
public:
C(int num);
~C(){
}
private:
int number;
B b;
};
C::C(int num):b("CHINA"),A(num){
number = num;
}
void main(void )
{
C c(100); //先A(num),再b("CHINA"),最后C::C(int num)
} //析构时,先~C(),再~B(),最后~A()
2.2.2虚析构函数:
c++语言标准关于虚析构函数的阐述:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。实际运行时经常发生的是,派生类的析构函数永远不会被调用。声明析构函数为虚就会调用基类和派生类的析构函数。
#include <iostream.h>
class Base
{
public:
virtual ~Base() { cout<< "~Base" << endl ; }
};
class Derived : public Base
{
public:
virtual ~Derived() { cout<< "~Derived" << endl ; }
};
void main(void)
{
Base * pB = new Derived; // upcast
delete pB; //对象内存释放时,Derived和Base的析构函数都会被调用。
}
输出结果为:
~Derived
~Base
如果析构函数不为虚,那么输出结果为
~Base
2.3 派生类对基类私有成员的访问
在公有派生和私有派生中,继承类都不能直接访问基类的私有成员。继承类访问基类的私有成员的方法有以下两种方法:
<1>在基类中增加保护类成员
将基类私有成员中需要提供给派生类访问的部分定义为保护段成员。基类的保护类成员可以被派生类(无论公有派生、保护派生还是私有派生)访问,但外界对象不能访问。
class A {
protected:
int count;
public:
A(int i = 0){
count = i;
}
void print(){
cout << count;
}
};
class B:public A{
int num;
public:
B(int i){ num = i;}
void print(){
cout << count << " " << num; //公有继承派生类访问基类的保护成员
}
};
class C :B{
public:
C(): B(10){ }
void print(){
cout << count; //私有继承的派生类访问基类的保护成员
//cout << num; //私有继承的派生类不能访问基类的私有成员
}
};
void main()
{
B b(4);
C c;
b.print();
c.print();
}
<2>将需要访问基类私有成员的派生类成员函数声明为基类的友元
在基类中增加保护类成员的方法,存在一个弊端,多重公有继承或保护继承得到的派生类,仍然可以访问原基类的保护段成员。可以通过将需要访问基类私有成员的派生类成员函数声明为基类的友元的方法,使得派生类中的指定成员函数函数可以访问基类的私有成员,其他的成员函数则不能访问基类的私有成员。
通过友员访问基类的私有成员,这种访问不是经常的,但是必要的。否则,需要类提供足够的成员函数。友元的使用使得C++中的数据封装性受到削弱,导致程序的维护性变差(慎用)。
声明为某个类的友元的外界对象既可以是另一个类的成员函数,也可以是不属于任何类的一个函数,还可以是某个类(此时,该类中的所有成员函数都成为友元)。设置友元的首要目的是可以使友元函数访问(使用或改变)某类的私有成员。
友元的声明可以放在类的公有部分,也可以放在类的私有部分。
class circle;
class point {
int x, y;
public:
point(int x, int y);
friend void print(const point p); //声明不属于任何类的函数为友元函数
friend class circle; //声明某类.为友元类
friend void circle::getCircleCenter(int &x, int &y); //声明某类的函数为友元函数,该函数可以访问point的私有成员x和y
}
说明:友元函数虽然可以访问类对象的私有成员,但是它没有this指针,在某一时刻究竟是应该访问哪一个对象的私有成员,很难确定。可以通过向友元函数传递对象的办法,显式地确定对象。
友元关系是“给予”的,而不是“索取”的。也就是说,如果类B是类A的友元类,类A中必须声明类B是类A的友元类。友元关系是不对称且不传递的。
2.4 虚基类
P part(of A)
P part(of B)
C part |
C |
A |
B |
P |
P |
C |
A |
B |
P |
在如下左图所示的多层继承类中, 类P两次成为类C的间接基类,此时,C类对象中有两个类P的对象:由A继承的P和由B继承的P,C类对象的内存排列如中图所示:
虚基类:当在多条继承路径上有一个公共的基类,在这些路径中的某几条路径汇合处,这个公共基类会产生多个实例,如果需要只保存基类的一个实例,可以将这个公共基类声明为虚基类。虚基类的声明如下表右侧所示:
非虚基类的多重继承 class P { public: int next; }; class A: public P { }; class B: public P { }; class C: public A, public B { public: void set( int i); }; void C::set(int i) { next = i; //具有二义性 }
| 虚基类的多重继承 class P { public: int next; }; class A: virtual public P { }; class B: virtual public P { }; class C: public A, public B { public: void set( int i); }; void C::set(int i) { next = i; //无二义性 }
|
虚基类构造函数的调用规则:
<1>同一层次中虚基类的构造函数在非虚基类之前调用
<2>同一层次中包含多个虚基类,虚基类构造函数按照他们的说明顺序调用
<3>若虚基类由非虚基类派生,则遵守先调用基类构造函数,再调用派生类构造函数。
例:
base2类 |
base2类 |
base虚基类 |
level1类 |
level2虚基类 |
toplevel类 |
如下所示的类继承关系:
建立toplevel类对象top时,构造函数的调用顺序是:
base类
base2类
level2类
base2类
level1类
toplevel类