20、继承与派生
1、类的继承与派生
继承的目的是实现代码的重用,派生的目的是当新的问题出现的时候,原有的程序不能解决时,需要对原程序进行改造。派生类的声明:class 派生类名:继承方式 基类名{成员声明;}
不同的继承方式的影响主要体现在:派生类成员对基类成员的访问权限;通过派生类对象对基类成员的访问权限
三种继承方式:公有、私有和保护。
公有继承:基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可以直接访问;
派生类的成员函数可以直接访问基类中public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象只能访问基类的public成员。
//Rectangle.h
class Point
{
public:
private:
};
class Rectangle: public Point
{
public:
private:
};//End of Rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
}
私有继承:基类的public和protected成员以private身份出现在派生类中,但是基类的private成员不可以直接访问;派生类的成员函数可以直接访问基类的public和protected成员,但不能直接访问private成员;通过派生类的对象不能直接访问基类中的任何成员。
//rectangle.h
class Point
{
public:
private:
};
class Rectangle: private Point
{
public:
private:
};
//End of rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
}
保护继承:基类的public和protected成员都已protected身份出现在派生类中,但基类的private成员不可直接访问;派生中成员函数可以直接访问类中的public和protected成员,但是不可以直接访问private成员;通过派生类的对象不能直接访问基类的任何成员。
protected成员的特点与作用:在类外通过类的对象是不可以访问的,对于派生类来说,在派生类中是与public一样的,既实现了数据隐藏,又方便继承,实现了代码重用。
class A{protected:intx;}
int main(){
类型兼容规则:一个公有派生类的对象在使用上被当做基类的对象,反之则禁止。
表现在:(轮胎和汽车的关系),派生类的对象含有的信息大于基类。
派生类的对象可以被赋值给基类对象;
派生类的对象可以初始化基类的引用;
指向基类的指针也可以指向派生类。
通过基类的对象名、指针只能使用从基类继承的成员。
#include<iostream>
using namespace std;
class B0
{
public:
};
class B1: public B0
{
public:
};
class D1: public B1
{
public:
};
void fun(B0 *ptr)
{
}
int main()
{
}//运行结果:
B0::display()
B0::display()
B0::display()
多继承时派生类的声明:class派生类名:继承方式1 基类名1,继承方式2 基类2,……{成员声明;};
每一个继承方式只用于限制对紧随其后的基类的继承。
class A{
};
class B{
private:
};
class C : public A, private B{
};
void
{
void B::setB(int x)
{
void C::setC(int x, int y, intz)
{
}
//其他函数实现略
int main()
{
// obj.setB(6);
// obj.showB(); 错误
}
继承时的构造函数:基类的构造函数是不被继承的,因为基类的构造函数不足以为派生类新增的成员初始化,派生类需要声明自己的构造函数,声明构造函数时,只需要对本类中的新增成员初始化,对继承来的基类的成员初始化,自动调用基类的构造函数完成;派生类的构造函数需要给基类的构造函数传递参数。
单继承:派生类名::派生类名(基类所需形参,本类成员所需形参)基类名(参数表){本类成员初始化;};
多继承:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数){本类成员初始化赋值语句;};
当基类中声明有默认形式的构造函数或者没有声明构造函数时,派生类构造函数可以不向基类构造函数传递参数;若基类没有声明构造函数,派生类中也可以不声明,全部采用默认构造函数;当基类中声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类的构造函数。
多继承且有内嵌对象(组合类对象成员)时的构造函数:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数),对象数据成员的初始化{本类成员初始化赋值语句;};
构造函数的调用顺序:调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);调用成员对象的构造函数,调用顺序按照它们在类中的声明顺序;派生类的构造函数体中的内容。
拷贝构造函数:若建立派生类对象时调用默认拷贝构造函数,则自动调用基类的默认构造函数;若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。C::C(C&b):B(b){};
#include<iostream>
using namespace std;
class B1
{
public:
};
class B2
{
public:
};
class B3
{
public:
};
class C: public B2, public B1, publicB3
//注意基类名的顺序
{
public:
private:
};
int main()
{
}//运行结果:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
析构函数也不被继承,需要自行声明,声明方法与一般析构函数一样,不需要显式的调用基类的析构函数,系统会自动调用,析构函数的调用顺序和构造函数相反。
#include<iostream>
using namespace std;
class B1
{
public:
};
class B2
{
public:
};
class B3
{
public:
};
class C: public B2, public B1, publicB3
{
public:
private:
};
int main()
{
}
//运行结果
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
2、同名隐藏规则
当派生类中和基类中有相同成员时,若没有强行指明,通过派生类对象使用的派生类的同名成员;如果通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。
#include<iostream>
using namespace std;
class B1
{
public:
};
class B2
{
public:
};
class D1: public B1, public B2
{
public:
};
int main()
{
}
二义性:在多继承时,基类和派生类之间,或者基类之间出现同名成员,将出现访问时的二义性(不确定性),这种情况采用虚函数或者同名隐藏规则来解决;当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性,采用虚函数解决。
class A
{
};
class B
{
};
class C: public A, piblicB
{
};
如果声明:C
则
而
class B
{
}
class B1 : publicB
}
class B2 : publicB
{
};
class C : public B1,publicB2……
{
}
虚基类:主要用于有共同基类的场合。
声明用virtual修饰基类;如class B1:virtual publicB,其作用是用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题;为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。
注意:在第一级继承时就要将共同基类设计成虚基类。
class B{ private: intb;};
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class C : public B1, public B2{ private: float d;}
下面的访问是正确的:
C
cobj.b;
虚基类的派生类对象存储结构示意图:
#include<iostream>
using namespace std;
class B0
{ public:
};
class B1: virtual public B0
{ public:
};
class B2: virtual public B0
{
};
class D1: public B1, public B2
{
};
int main()
{
}
虚基类和派生类构造函数:建立对象时所指定的类成为最(远)派生类,虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的;在整个继承结构中,直接或间接继承虚基类的所有派生类都必须在构造函数成员初始化表中给出对虚基类构造函数的调用,否则表示调用该虚基类的默认构造函数;在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略;
#include<iostream>
using namecpace std;
class B0
{ public:
};
class B1: virtual public B0
{
};
class B2: virtual public B0
{
};
class D1: public B1, public B2
{
public:
};
int main()
{
}