1、类的继承与派生 保持已有类的特性而构造新类的过程成为继承;
在已有类的基础上新增自己的特性而产生新类的过程称为派生;
被继承的已有类为基类;派生出的新类成为派生类。继承和派生其实是一回事。
继承的目的是实现代码的重用,派生的目的是当新的问题出现的时候,原有的程序不能解决时,需要对原程序进行改造。派生类的声明: class 派生类名:继承方式 基类名{成员声明;}
不同的继承方式的影响主要体现在:派生类成员对基类成员的访问权限;通过派生类对象对基类成员的访问权限
三种继承方式:公有、私有和保护。
公有继承:基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可以直接访问;
派生类的成员函数可以直接访问基类中public和protected成员,但不能直接访问基类的private成员;
通过派生类的对象只能访问基类的public成员。
//Rectangle.h
class Point //基类Point类的声明
{
public: //公有函数成员
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private: //私有数据成员
float X,Y;
};
class Rectangle: public Point //派生类声明部分
{
public: //新增公有函数成员
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //调用基类公有成员函数
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有数据成员
float W,H;
};//End of Rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
Rectangle rect; //声明Rectangle类的对象
rect.InitR(2,3,20,10); //设置矩形的数据
rect.Move(3,2); //移动矩形位置
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //输出矩形的特征参数
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
私有继承:基类的public和protected成员以private身份出现在派生类中,但是基类的private成员不可以直接访问;派生类的成员函数可以直接访问基类的public和protected成员,但不能直接访问private成员;通过派生类的对象不能直接访问基类中的任何成员。
//rectangle.h
class Point //基类声明
{
public:
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private:
float X,Y;
};
class Rectangle: private Point //派生类声明
{
public: //新增外部接口
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //派生类访问基类公有成员
void Move(float xOff, float yOff) {Point::Move(xOff,yOff);}//自己重新定义一个move函数,其实是间接调用基类函数,
//为的是方便在类外(主函数中)使用move函数。
float GetX() {return Point::GetX();}
float GetY() {return Point::GetY();}
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有数据
float W,H;
};
//End of rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
Rectangle rect; //声明Rectangle类的对象
rect.InitR(2,3,20,10); //设置矩形的数据
rect.Move(3,2); //移动矩形位置,使用的是派生类的函数。
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //输出矩形的特征参数
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
保护继承:基类的public和protected成员都已protected身份出现在派生类中,但基类的private成员不可直接访问;派生中成员函数可以直接访问类中的public和protected成员,但是不可以直接访问private成员;通过派生类的对象不能直接访问基类的任何成员。
protected成员的特点与作用:在类外通过类的对象是不可以访问的,对于派生类来说,在派生类中是与public一样的,既实现了数据隐藏,又方便继承,实现了代码重用。
class A{protected:int x;} …… class A{protected:int x;} class B:public A{public: void f();}
int main(){ A a; a.x=5;//错误} …… void B:f(){x=5;}//正确
类型兼容规则:一个公有派生类的对象在使用上被当做基类的对象,反之则禁止。
表现在:(轮胎和汽车的关系),派生类的对象含有的信息大于基类。
派生类的对象可以被赋值给基类对象;
派生类的对象可以初始化基类的引用;
指向基类的指针也可以指向派生类。
通过基类的对象名、指针只能使用从基类继承的成员。
#include <iostream>
using namespace std;
class B0 //基类B0声明
{
public:
void display(){cout<<"B0::display()"<<endl;} //公有成员函数
};
class B1: public B0 //公有派生类B1声明
{
public:
void display(){cout<<"B1::display()"<<endl;} //公有成员函数
};
class D1: public B1 //公有派生类D1声明
{
public:
void display(){cout<<"D1::display()"<<endl;} //公有成员函数
};
void fun(B0 *ptr) //普通函数
{ //参数为指向基类对象的指针
ptr->display(); //"对象指针->成员名"
}
int main() //主函数
{
B0 b0; //声明B0类对象
B1 b1; //声明B1类对象
D1 d1; //声明D1类对象
B0 *p; //声明B0类指针
p=&b0; //B0类指针指向B0类对象
fun(p);
p=&b1; //B0类指针指向B1类对象
fun(p);
p=&d1; //B0类指针指向D1类对象
fun(p);
}//运行结果:
B0::display()
B0::display()
B0::display()
多继承时派生类的声明:class 派生类名:继承方式1 基类名1,继承方式2 基类2,……{成员声明;};
每一个继承方式只用于限制对紧随其后的基类的继承。
class A{
public:
void setA(int);
void showA();
private:
int a;
};
class B{
public:
void setB(int);
void showB();
private:
int b;
};
class C : public A, private B{
public:
void setC(int, int, int);
void showC();
private:
int c;
};
void A::setA(int x)
{ a=x; }
void B::setB(int x)
{ b=x; }
void C::setC(int x, int y, int z)
{ //派生类成员直接访问基类的
//公有成员
setA(x);
setB(y);
c=z;
}
//其他函数实现略
int main()
{
C obj;
obj.setA(5);
obj.showA();
obj.setC(6,7,9);
obj.showC();
// obj.setB(6); 错误,私有继承的B,在类外不可以直接访问。
// obj.showB(); 错误
return 0;
}
继承时的构造函数:基类的构造函数是不被继承的,因为基类的构造函数不足以为派生类新增的成员初始化,派生类需要声明自己的构造函数,声明构造函数时,只需要对本类中的新增成员初始化,对继承来的基类的成员初始化,自动调用基类的构造函数完成;派生类的构造函数需要给基类的构造函数传递参数。
单继承:派生类名::派生类名(基类所需形参,本类成员所需形参)基类名(参数表){本类成员初始化;};
多继承:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数){本类成员初始化赋值语句;};
当基类中声明有默认形式的构造函数或者没有声明构造函数时,派生类构造函数可以不向基类构造函数传递参数;若基类没有声明构造函数,派生类中也可以不声明,全部采用默认构造函数;当基类中声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类的构造函数。
多继承且有内嵌对象(组合类对象成员)时的构造函数:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数),对象数据成员的初始化{本类成员初始化赋值语句;};
构造函数的调用顺序:调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);调用成员对象的构造函数,调用顺序按照它们在类中的声明顺序;派生类的构造函数体中的内容。
拷贝构造函数:若建立派生类对象时调用默认拷贝构造函数,则自动调用基类的默认构造函数;若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。C::C(C&b):B(b){};
#include <iostream>
using namespace std;
class B1 //基类B1,构造函数有参数
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;}
};
class B2 //基类B2,构造函数有参数
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;}
};
class B3 //基类B3,构造函数无参数
{
public:
B3(){cout<<"constructing B3 *"<<endl;}
};
class C: public B2, public B1, public B3 //派生新类C
//注意基类名的顺序
{
public: //派生类的公有成员
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//注意基类名的个数与顺序
//注意成员对象名的个数与顺序
private: //派生类的私有对象成员
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
C obj(1,2,3,4);//传递参数顺序,首先基类:B2(b),B1(a),B3不要参数,接着是内嵌成员memberB1,memberB2,memberB3
}//运行结果:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
析构函数也不被继承,需要自行声明,声明方法与一般析构函数一样,不需要显式的调用基类的析构函数,系统会自动调用,析构函数的调用顺序和构造函数相反。
#include <iostream>
using namespace std;
class B1 //基类B1声明
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;} //B1的构造函数
~B1() {cout<<"destructing B1 "<<endl;} //B1的析构函数
};
class B2 //基类B2声明
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;} //B2的构造函数
~B2() {cout<<"destructing B2 "<<endl;} //B2的析构函数
};
class B3 //基类B3声明
{
public:
B3(){cout<<"constructing B3 *"<<endl;} //B3的构造函数
~B3() {cout<<"destructing B3 "<<endl;} //B3的析构函数
};
class C: public B2, public B1, public B3 //派生类C声明
{
public:
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//派生类构造函数定义
private:
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{ C obj(1,2,3,4);
}
//运行结果
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 //声明基类B1
{
public: //外部接口
int nV;
void fun(){cout<<"Member of B1"<<endl;}
};
class B2 //声明基类B2
{
public: //外部接口
int nV;
void fun(){cout<<"Member of B2"<<endl;}
};
class D1: public B1, public B2 //声明派生类D1
{
public:
int nV; //同名数据成员
void fun(){cout<<"Member of D1"<<endl;} //同名函数成员
};
int main()
{
D1 d1;
d1.nV=1; //对象名.成员名标识
d1.fun(); //访问D1类成员
d1.B1::nV=2; //作用域分辨符标识
d1.B1::fun(); //访问B1基类成员
d1.B2::nV=3; //作用域分辨符标识
d1.B2::fun(); //访问B2基类成员
}
二义性:在多继承时,基类和派生类之间,或者基类之间出现同名成员,将出现访问时的二义性(不确定性),这种情况采用虚函数或者同名隐藏规则来解决;当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性,采用虚函数解决。
class A
{
public:
void f();
};
class B
{
public:
void f();
void g()
};
class C: public A, piblic B
{ public:
void g();
void h();
};
如果声明:C c1;
则 c1.f(); 具有二义性,解决方法1:用类名限定c1.A::f(); or c1.B::f();解决方法2:同名覆盖在c中声明一个同名函数f(),在根据需要调用A::f or B::f.
而 c1.g(); 无二义性(同名覆盖)
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(); …… C c; c.B1::b;
private: ……c.b; c.B2::b;
int d; ……c.B::b;
} //不光产生二义性,还将基类成员b产生了多次拷贝。 ……
虚基类:主要用于有共同基类的场合。
声明用virtual修饰基类;如class B1:virtual public B,其作用是用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题;为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。
注意:在第一级继承时就要将共同基类设计成虚基类。
class B{ private: int b;};
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;
cobj.b;
虚基类的派生类对象存储结构示意图:
#include <iostream>
using namespace std;
class B0 //声明基类B0
{ public: //外部接口
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0 //B0为虚基类,派生B1类
{ public: //新增外部接口
int nV1;
};
class B2: virtual public B0 //B0为虚基类,派生B2类
{ public: //新增外部接口
int nV2;
};
class D1: public B1, public B2 //派生类D1声明
{ public: //新增外部接口
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main() //程序主函数
{ D1 d1; //声明D1类对象d1
d1.nV=2; //使用最远基类成员的唯一一份拷贝
d1.fun();
}
虚基类和派生类构造函数:建立对象时所指定的类成为最(远)派生类,虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的;在整个继承结构中,直接或间接继承虚基类的所有派生类都必须在构造函数成员初始化表中给出对虚基类构造函数的调用,否则表示调用该虚基类的默认构造函数;在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略;
#include <iostream>
using namecpace std;
class B0 //声明基类B0
{ public: //外部接口
B0(int n){ nV=n;}
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0
{ public:
B1(int a) : B0(a) {}
int nV1;
};
class B2: virtual public B0
{ public:
B2(int a) : B0(a) {}
int nV2;
};
class D1: public B1, public B2
{
public:
D1(int a) : B0(a), B1(a), B2(a){}
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main()
{
D1 d1(1);
d1.nV=2;
d1.fun();
}