继承
来源:互联网 发布:中文编程语言 编辑:程序博客网 时间:2024/04/30 00:23
继承(inheritance)和派生(derived):
类提供了说明一组对象结构的机制。借助于继承这一重要机制,已存在的类具有建立子类的能力,进而建立类的层次,扩充类的定义。
继承提供了创建新类的一种方法,一个新类可以通过对已有类进行修改和扩充来定义。从一个类继承定义的新类,将继承已有类的方法和属性,并且可添加不包含在父类中的新方法和属性。新类被称为已有类的子类,又称为派生类,已有类称为新类的父类,又称为基类。(在java中,只有父类,子类之称,没有基类,派生类这样的说法).
一. 派生类的三种继承方式:
1.公有继承(public):
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。
2.私有继承(private)
私有继承的特点是基类的公有成员和保护成员作为派生类的私有成员,并且不能被这个派生类的子类访问。
3.保护继承(protected)
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
二. 派生与构造函数、析构函数
1. 构造函数和析构函数不能够被继承(why?);
2.派生类构造函数的调用顺序:
(1)调用基类的构造函数,调用顺序按照它们继承时说明的顺序。
(2)调用子对象类的构造函数,调用顺序按照它们在类中说明的顺序。
(3)派生类构造函数体中的内容。
3.派生类析构函数的调用顺序:
和构造函数的顺序完全相反.
4. 派生类构造函数使用中应注意的问题:
(1)派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有默认的构造函数或者根本没有定义构造函数.
(2)当基类的构造函数使用一个或多个参数时,则派生类必须定义构造函数,提供将参数传递给基类构造函数的途径.
三. 多继承:
1. 可以为一个派生类指定多个基类,这样的继承结构称为多继承。多继承可以看作是单继承的扩展。
(在java中,不允许进行多继承,类似多继承的功能是通过接口(interface)实现的。
例子程序:
/* file: inheritanceTest.cpp */
/* IDE环境: Dev-C++ 4.9.9.2 */
/*本例中类的继承关系如下:
*
* public
* A <--------- B
*
* private public
* A <--------- C <--------- D
*
***********************/
#include <stdio.h>
class A
{
public:
A()
{
printf("A::constructing ! /n");
init();
}
~A()
{
printf("A::destructing ! /n");
}
void init() { i = j= k = 0;}
int get_i(void) { return i; }
int get_j(void) { return j; }
int get_k(void) { return k; }
public: int i;
private: int j; //private成员不能被派生类直接访问
protected : int k;
};
class B : public A
{
public:
B()
{
printf("B::constructing ! /n");
init(); // 调用本类的init()函数。
}
~B()
{
printf("B::destructing ! /n");
}
void init(void) // 方法覆盖,也称方法重写,注意与重载是不同的。
{
A::init(); //调用父类的init()函数,这里必须显式地用A::init(), "A::"不能省略
h = 0;
}
void set_h(int x) { h = x; }
int get_h(void) { return h; }
void display(void)
{
{ printf(" B::disp_i i = %d /n", get_i()); }
{ printf(" B::disp_j j = %d /n", get_j()); }
{ printf(" B::disp_k k = %d /n", get_k()); }
{ printf(" B::disp_h h = %d /n", get_h()); }
}
void print(void)
{
printf(" B::print i = %d /n", i );
// printf(" B::print j = %d /n", j ); //compiler error: 103 F:/devcpptest/devcpptest/derivedTest.cpp `int A::j' is private
printf(" B::print k = %d /n", k );
printf(" B::print h = %d /n", h );
}
private:
int h;
};
class C: private A
{
public:
C()
{
printf("C::constructing ! /n");
}
~C()
{
printf("C::destructing ! /n");
}
void Cprint_i(void)
{
printf(" C::Cprint_i i = %d /n", i ); //OK
}
//A::j; //compile error: F:/devcpptest/devcpptest/derivedTest.cpp `int A::j' is private
A::k; //调整对A::k的访问控制, 使得 k 可以被C的派生类访问。
};
class D: public C
{
public:
D()
{
printf("D::constructing ! /n");
}
~D()
{
printf("D::destructing ! /n");
}
void Dprint_i(void)
{
//printf(" D::Dprint_i i = %d /n", i ); //compile error: F:/devcpptest/devcpptest/derivedTest.cpp `int A::i' is inaccessible
// 对于类C, i是它的私有数据成员(因为C是从父类A以private的方式继承的), 因此,不能被
//它的派生类D访问。
printf(" D::Dprint_i i = %d /n", k); //OK
}
};
void TestFunc(void)
{
B b; // 构造函数调用顺序:A ---> B
b.display();
b.print();
C c; // 构造函数调用顺序:A ---> C
//int x = c.get_i(); //compile error: F:/devcpptest/devcpptest/derivedTest.cpp `int A::get_i()' is inaccessible
//因为C是以private的方式继承自A的,A的所有public,proteced成员函数都不能被C访问。
D d; // 构造函数调用顺序:A ---> C ---> D
d.Dprint_i();
}
int main(void)
{
TestFunc(); //调用完此函数后,所有在 TestFunc()中定义的变量都将销毁,对于对象来说,在此函数执行完成后,系统自动调用析构函数,
//而调用的顺序正好与构造函数的调用顺序相反。
while(1);
return 0;
}
四. 同一基类多次拷贝引起的二义性:
在多基派生中,如果在多条继承路径上有一个公共的基类,则在这些路径的汇合点,便会产生来自不同路径的公共基类的多个拷贝,这样,
便会引起二义性,具体地,有下面两种情况:
1. 同名成员的二义性,例如,
/* IDE环境: Dev-C++ 4.9.9.2 */
/* file: derivedTest2.cpp */
#include <stdio.h>
class A
{
public:
int a()
{
return 1;
}
};
class B :public A
{
public:
float a()
{
return float(1.2345);
}
};
class C : public A
{
};
class D : public B, public C
{
};
int main()
{
D d;
printf(" %f ",d.a()); //compiler error: F:/devcpptest/devcpptest/derivedTest2.cpp request for member `a' is ambiguous;
// F:/devcpptest/devcpptest/derivedTest2.cpp: candidates are: int A::a() float B::a()
// d.a() 不知道是要调用int a()还是float a(),
while(1);
}
2. 同一基类被多次继承产生的二义性(用虚函数来解决) ,例如,
/* IDE环境: Dev-C++ 4.9.9.2 */
/* file: derivedTest3.cpp */
#include <stdio.h>
class B
{ public : int b ;} ;
class B1 : public B
{ private : int b1 ; } ;
class B2 : public B
{ private : int b2 ; } ;
class C : public B1 , public B2
{
public : int f ( ) ;
private : int d ;
} ;
int main()
{
C c;
//c.b; // error
//c.B::b; // error,从哪里继承的?
c.B1::b; // ok,从B1继承的
c.B2::b; // ok ,从B2继承的
while(1);
return 0;
}
五. 虚基类
1. 虚基类的引入:
正如四.2的例子derivedTest3.cpp一样,如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
为了保证基类成员在派生类中只继承一次,就必须将在其直接派生类都说明为按虚拟方式派生,即使用关键字virtual 把它们定义为虚基类,格式如下:
class 派生类名: virtual 派生方式 基类名 {
...
};
这里,virtual 与派生方式(public, private, protected)间的先后顺序无关。
例如,
/* IDE环境: Dev-C++ 4.9.9.2 */
/* file: virtualBaseClassTest1.cpp */
#include <stdio.h>
class A
{ public : A ( )
{ printf("class A/n"); } } ;
class B : virtual public A
{ public : B ( )
{ printf("class B/n"); } } ;
class C : virtual public A
{ public : C ( )
{printf("class C/n"); } } ;
class D : public B , public C
{ public : D ( )
{ printf("class D/n"); } } ;
int main ( )
{ D dd ; while(1); return 0; }
输出结果:
class A
class B
class C
class D
2. 虚基类的初始化:
(1) 从虚基类直接或间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数的调用(例virtualBaseClassTest2)。
(2) 虚基类的构造函数要在其派生类的构造函数之前调用(例virtualBaseClassTest2)。
(3) 在虚基类的类层次结构中,系统将自左向右按深度优先遍历算法对公共派生类进行初始化(例virtualBaseClassTest3)。
/* 例子程序1 */
/* IDE环境: Dev-C++ 4.9.9.2 */
/* file: virtualBaseClassTest2.cpp */
#include <stdio.h>
class A
{
public:
A( ) { printf("A() /n"); }; //这里,即使不显示定义A的构造函数,在定义派生类D的一个实例时,就会调用A的默认构造函数
};
class B
{
public :
B( ) { printf("B() /n"); }; //显示默认构造函数
};
class C
{
public :
C(int i) { printf("C(int i), i =%d /n", i); };
};
class D: virtual public A, virtual public B, virtual public C
{
public:
//D(){ }; // F:/devcpptest/devcpptest/virtualBaseClassTest2.cpp no matching function for call to `C::C()'
// candidates are: C::C(const C&)
D(int i):C(i) { printf("D(int i), i =%d /n", i);}; // 必须用初始化列表的形式,使得在C(i)在派生类D的构造函数执行之前被调用。
};
int main ( )
{
D d(3);
while(1);
return 0;
}
输出结果:
A()
B()
C(int i), i =3
D(int i), i =3
/* 例子程序2 */
/* IDE环境: Dev-C++ 4.9.9.2 */
/* file: virtualBaseClassTest3.cpp */
#include <stdio.h>
class A
{
public:
A( ) { printf("A() /n"); }; //这里,即使不显示定义A的构造函数,在定义派生类D的一个实例时,就会调用A的默认构造函数
};
class B
{
public :
B( ) { printf("B() /n"); }; //显示默认构造函数
};
class C: virtual public A, virtual public B
{
public :
C(int i) { printf("C(int i), i =%d /n", i); };
};
class D: virtual public A, virtual public B
{
public:
//D(){ }; // F:/devcpptest/devcpptest/virtualBaseClassTest3.cpp no matching function for call to `C::C()'
// candidates are: C::C(const C&)
D(int i){ printf("D(int i), i =%d /n", i);}; // 必须用初始化列表的形式,使得C(i)先被调用。
};
class E: virtual public C, virtual public D
{
public:
//E(){ }; // 原理同 virtualBaseClassTest1.cpp
//E(int i):{ printf("E(int i), i =%d /n", i);}; // 原理同 virtualBaseClassTest2.cpp,没有进行 C(i),D(i)的初始化
//E(int i):C(i) { printf("E(int i), i =%d /n", i);}; // 原理同 virtualBaseClassTest2.cpp,没有进行 D(i)的初始化
E(int i):C(i), D(i){ printf("E(int i), i =%d /n", i);}; // 必须用初始化列表的形式,使得C(i),D(i)先被调用。
};
int main ( )
{
E ee(3);
while(1);
return 0;
}
输出结果:
A()
B()
C(int i), i =3
D(int i), i =3
E(int i), i = 3
初始化过程(如下),这也正体现了自左至右深度优先遍历算法:
1. 初始化C的A;
2. 初始化C的B;
3. 初始化C;
4. D的A与B均已初始化,初始化D;
5. 初始化E;