一、派生类的概念
1.1、为什么要使用继承
1.2、派生类的说明
1.3、保护成员的作用(protected)
二、派生类的构造函数与析构函数
2.1、派生类构造函数和析构函数的执行顺序
通常情况下,当创建派生类对象时, 首先执行基类的构造函数, 随后再执行派生类的构造函数; 当撤消派生类对象时, 则先执行派生类的析构函数, 随后再执行基类的析构函数。
下列程序的运行结果,反映了基类和派生类的构造函数及析构函数的执行顺序。
2.2、派生类构造函数和析构函数的构造规则
当基类的构造函数没有参数,或没有显式定义构造函数时, 派生类可以不向基类传递参数,甚至可以不定义构造函数。派生类不能继承基类中的构造函数和析构函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
在 C + + 中,派生类构造函数的一般格式为:
派生类构造函数名(参数表) :基类构造函数名( 参数表)
{
/ / …
}
其中基类构造函数的参数,通常来源于派生类构造函数的参数表, 也可以用常数值。
下面的程序说明如何传递一个参数给派生类的构造函数和传递一个参数给基类的构造函数。
说明:
- 当基类构造函数不带参数时, 派生类不一定需要定义构造函数, 然而当基类的构造函数哪怕只带有一个参数,它所有的派生类都必须定义构造函数,
甚至所定义的派生类构造函数的函数体可能为空,仅仅起参数的传递作用。 - 若基类使用缺省构造函数或不带参数的构造函数, 则在派生类中定义构造函数时可略去“∶基类构造函数名(参数表)”;此时若派生类也不需要构造函数, 则可不定义构造函数。
- 如果派生类的基类也是一个派生类, 则每个派生类只需负责其直接基类的构造,依次上溯。 由于析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的基类无关,故基类的析构函数不会因为派生类没有析构函数而得不到执行, 它们各 自是独立的。
三、多重继承
3.1、多重继承的声明
一般形式如下:
class 派生类名: 派生方式 1 基类名 1, …,派生方式 n 基类名 n
{
// 派生类新增的数据成员和成员函数
} ;
冒号后面的部分称基类表,各基类之间用逗号分隔, 其中“派生方式 i”( i = 1, 2, …, n )规定了派生类从基类中按什么方式继承: private 或 public,缺省的派生方式是 private
class z: public x, y
{
// 类 z 公有继承了类 x,私有继承了类 y
// …
};
class z: x, public y
{
// 类 z 私有继承了类 x,公有继承了类 y
// …
};
class z: public x, public y
{
// 类 z 公有继承了类 x 和类 y
// …
};
如下:对基类成员的访问必须是无二义的, 例如下列程序段对基类成员的访问是二义的,必须想法消除二义性。
class X
{
public:
int f();
};
class Y
{
public:
int f();
int g();
};
class Z∶public X, public Y
{
public:
int g();
int h();
};
假如定义类 Z 的对象 obj: Z obj;则以下对函数 f( )的访问是二义的:
-
obj .f( ) ;
/ / 二义性错误,不知调用的是类 X 的 f( ) ,还是类 Y 的 f( )
使用成员名限定可以消除二义性,例如: -
obj .X∷f( ) ;
/ / 调用类 X 的 f( ) -
obj .Y∷f( ) ;
/ / 调用类 Y 的 f( )
3.2、多重继承的构造函数与析构函数
多重继承构造函数的定义形式与单继承构造函数的定义形式相似, 只是 n 个基类的构造函数之间用“,”分隔。多重继承构造函数定义的一般形式如下:
派生类构造函数名(参数表) :基类 1 构造函数名 ( 参数表), 基类 2 构造函数名 (参数表), …,基类 n 构造函数名(参数表)
{
// …
}
例如,由一个硬件类 Hard 和一个软件类 Soft ,它们共同派生出系统类 System, 声明如下:
class Hard
{
protected:
char bodyname[20];
public:
Hard(char * bdnm ); // 基类 Hard 的构造函数
// …
};
class Soft
{
protected:
char os[10];
char Lang[15];
public:
Soft( char * o, char * lg);// 基类 Soft 的构造函数
// …
} ;
class System: public Hard, public Soft
{
private:
char owner[10] ;
public:
System( char * ow, char * bn, char * o, char * lg) // 派生类 System 的构造函数
∶Hard( bn), Soft(o, lg);
// 缀上了基类 Hard 和 Soft 的构造函数
// …
};
注意:在定义派生类 System 的构造函数时,缀上了 Hard 和 Soft 的构造函数。
再如,现有一个窗口类 window 和一个滚动条类 scrollbar, 它们可以共同派生出一个带有滚动条的窗口,声明如下:
class window
{
// 定义窗口类 window
// …
public:
window(int top, int left, int bottom, int right);
~window();
// …
} ;
class scrollbar
{
// 定义滚动条类 scrollbar
// …
public:
scrollbar(int top, int left, int bottom, int right);
~scrollbar();
// …
};
class scrollbarwind∶window,scrollbar
{
/ / 定义派生类
/ / …
public:
scrollbarwind(int top, int left, int bottom, int right);
~scrollbarwind();
// …
};
scrollbarwind∷scrollbarwind(int top, int left, int bottom, int right)∶window( top, left,bot tom, right),scrollbar(top, right - 20, bottom, right)
{
// …
}
在这个例子中, 定义派生类 scrollbarwind 的构造函数时, 也缀上了基类 window 和scrollbar 的构造函数。
下面我们再看一个程序,其中类 X 和类 Y 是基类, 类 Z 是类 X 和类 Y 共同派生出来的,请注意类 Z 的构造函数的定义方法。
#include<iostream>
using namespace std;
class X
{
int a;
public:
X(int sa ) // 基类 X 的构造函数
{
a = sa;
}
int getX()
{
return a ;
}
};
class Y
{
int b;
public:
Y(int sb) // 基类 Y 的构造函数
{
b = sb;
}
int getY()
{
return b;
}
} ;
class Z: public X, private Y
{
// 类 Z 为基类 X 和基类 Y 共同的派生类
int c;
public:
Z(int sa, int sb, int sc ) :X(sa), Y(sb) // 派生类 Z 的构造函数,缀上
{
c = sc ;
} // 基类 X 和 Y 的构造函数
int getZ()
{
return c;
}
int getY()
{
return Y::getY();
}
};
int main()
{
Z obj( 2, 4, 6) ;
int ma = obj.getX();
cout << "a = "<< ma << endl;
int mb = obj .getY();
cout << "b = "<< mb << endl;
int mc = obj .getZ();
cout << "c = "<< mc << endl;
return 0 ;
}
上述程序运行的结果如下:
a = 2
b = 4
c = 6
由于派生类 Z 是 X 公有派生出来的, 所以类 X 中的公有成员函数 getX( )在类 Z 中仍是公有的, 在 main ( ) 中可以直接引用, 把成员 a 的值赋给 main ( ) 中的变量 ma ,并显示在屏幕上。类 Z 是从类 Y 私有派生出来的, 所以类 Y 中的公有成员函数 getY( ) 在类 Z 中成为私有的,在 main( ) 中不能直接引用。为了能取出 b 的值,在 Z 中另外定义了一个公有成员函数 Z∷getY( ) ,它通过调用 Y∷getY( ) 取出 b 的值。主函数 main( )中的语句:int mb = obj .getY( ) ;调用的是派生类 Z 的成员函数 getY( ) , 而不是基类 Y 的成员函数 getY( )。由于类 Z 中的成员函数 getZ( ) 是公有成员,所以在 main( ) 中可以直接调用取出 c 的值。
总结:
多重继承的构造函数的执行顺序与单继承构造函数的执行顺序相同, 也是遵循先执行基类的构造函数,再执行对象成员的构造函数, 最后执行派生类构造函数的原则。在多个基类之间, 则严格按照派生类声明时从左到右的顺序来排列先后。而析构函数的执行顺序则刚好与构造函数的执行顺序相反。
3.3、虚基类
不难理解,如果在上例中类 base 只存在一个拷贝, 那么对 a的引用就不会产生二义性。在 C + + 中,如果想使这个公共的基类只产生一个拷贝,则可以将这个基类说明为虚基类。这就要求从类 base 派生新类时, 使用关键字 virtual 将类base 说明为虚基类。
#include <iostream>
using namespace std;
class base
{
protected:
int a;
public:
base()
{
a = 5;
}
};
class base1:virtual public base
{
public:
base1()
{
cout << "base1 a = "<< a << endl;
}
};
class base2:virtual public base
{
public:
base2()
{
cout << "base2 a = "<< a << endl;
}
};
class derived:public base1, public base2
{
public:
derived()
{
cout << "derived a = "<< a << endl;
}
};
int main ()
{
derived obj;
return 0 ;
}
在上述程序中,从类 base 派生出类 base1 和类 base2 时,使用了关键字 virtual ,把类base 声明为 base1 和 base2 的虚基类。这样, 从 base1 和base2 派生出的类 derived 只有一个基类 base, 从而可以消除二义性。
虚基类的初始化
虚基类的初始化与一般的多重继承的初始化在语法上是一样的,但构造函数的调用顺序不同。虚基类构造函数的调用顺序是这样规定的:
- 若同一层次中包含多个虚基类, 这些虚基类的构造函数按对它们说明的先后次序调用。
- 若虚基类由非虚基类派生而来, 则仍然先调用基类构造函数, 再调用派生类的构造函数。
- 若同一层次中同时包含虚基类和非虚基类, 应先调用虚基类的构造函数, 再调用非虚基类的构造函数,最后调用派生类构造函数, 例如:
class X∶public Y, virtual public Z
{
// …
};
X one;
定义类 X 的对象 one 时,将产生如下的调用次序:
Z( ) ;
Y( ) ;
X( ) ;
#include<iostream>
using namespace std;
class base
{
int a ;
public:
base (int sa)
{
a = sa;
cout << "Constructing base"<< endl;
}
};
class base1: virtual public base
{
int b;
public:
base1 (int sa, int sb) : base(sa)
{
b = sb;
cout << "Constructing baes1"<< endl;
}
};
class base2: virtual public base
{
int c;
public:
base2 (int sa, int sc) : base (sa)
{
c = sc ;
cout << "Constructing baes2"<< endl;
}
};
class derived: public base1, public base2
{
int d;
public:
derived(int sa, int sb, int sc, int sd ) :
base(sa ), base1 (sa,sb), base2(sa, sc )
{
d = sd;
cout << "Constructing derived"<< endl;
}
};
int main()
{
derived obj(2, 4, 6, 8 ) ;
return 0 ;
}
在上述程序中, base 是一个虚基类,它只有一个带参数的构造函数, 因此要求在派生类 base1、base2 和 derived 的构造函数的初始化表中,都必须带有对 base 构造函数的调用。
如果 base 不是虚基类,在派生类 derived 的构造函数的初始化表中调用 base 的构造函数是错误的,但是当 base 是虚基类且只有带参数的构造函数时, 就必须在类 derived 构造函数的初始化表中调用类 base 的构造函数。因此, 在 derived 构造函数的初始化表中,不仅含有对 base1 和 base2 构造函数的调用, 还有对虚基类 base 构造函数的调用。上述程序运行的结果为:
Constructing base
Constructing base1
Constructing base2
Constructing derived
不难看出,上述程序中虚基类 base 的构造函数只执行了一次。显然, 当 derived 的构造函数调用了虚基类 base 的构造函数之后, 类 base1 和类 base2 对 base 构造函数的调用被忽略了。这也是初始化虚基类和初始化非虚基类不同的地方。
说明:
- 关键字 virtual 与派生方式关键字 ( public 或 private ) 的先后顺序无关紧要, 它只说明是“虚拟派生”。 例如以下两个虚拟派生的声明是等价的。
class derived: virtual public base{
// …
} ;
class derived: public virtual base{
// …
} ;
- 一个基类在作为某些派生类虚基类的同时, 又作为另一些派生类的非虚基类, 这种情况是允许存在的,例如:
class B{
// …
} ;
class X∶virtual public B{
// …
} ;
class Y∶virtual public B{
// …
} ;
class Z∶public B{
// …
} ;
class AA∶public X, public Y , public Z{
// …
} ;
此例中,派生类 AA 由类 X、类 Y 和类 Z 派生而来。AA 与它的间接基类 B 之间的对应关系是:类 B 既是 X、Y 继承路径上的一个虚基类, 也是 Z 继承路径上的一个非虚基类。