C++类的继承与派生 ##1、继承与派生
一、继承的概念
1、继承与派生
类的继承,即一个新类从已有的类那里获得其已有特性。通过继承,一个新建子类从已有的父类那里获得父类的特性,或者说,从已有的类(父类)产生一个新的子类,称为类的派生。派生类继承了基类的所有数据成员和成员函数,并可以对成员作必要的增加或调整。
2、派生类的声明方式
class 派生类名:[继承方式] 基类名
{
派生类新增加的成员
} ;
例:先声明一个基类Base,在此基础上通过单继承建立一个派生类Derived
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
class Base
{
public:
void Print(int a)
{
_pub=0;
_pro=1;
_pri=2;
cout<<"_pub"<<_pub<<endl;
cout<<"_pro"<<_pro<<endl;
cout<<"_pri"<<_pri<<endl;
}
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
//派生类
class Derived:public Base
{
public:
void Print()
{
_pub=0;
_pro=1;
//_pri=2;
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
int main()
{
Derived d;
cout<<sizeof(Derived)<<endl;
system("pause");
return 0;
}
3、派生类的构成
4、派生类成员的访问属性
*注:若继承方式省略,使用关键字class时默认的继承方式是private,使用struct时默认继承方式public.
—派生类的默认成员函数在继承关系里面,在派生类中如果没有显示定义6个成员函数(构造函数、拷贝构造函数、析构函数、赋值运算符重载、取地址操作符重载、const修饰的取地址操作符重载),编译系统则会默认合成这六个默认的成员函数
5、派生类的构造函数和析构函数
1)、继承关系中构造函数调用顺序
构造函数调用的代码:
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
//基类
class Base
{
public:
Base()
{
cout<<"Base::Base()"<<endl;
}
void Print(int a)
{
_pub=0;
_pro=1;
_pri=2;
cout<<"_pub"<<_pub<<endl;
cout<<"_pro"<<_pro<<endl;
cout<<"_pri"<<_pri<<endl;
}
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
//派生类
class Derived:public Base
{
public:
Derived()
{
cout<<"Derived::Derived()"<<endl;
}
void Print()
{
_pub=0;
_pro=1;
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
int main()
{
Derived d;
d.Print();
cout<<sizeof(Derived)<<endl;
system("pause");
return 0;
}
构造函数,函数体执行顺序,基类在前,派生类在后
构造函数的调用顺序,如下图所示:
1、
2、
3、
4、
5、
6、
2)、继承关系中析构函数调用
在派生时,派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。在派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。
6、继承体系中的作用域
1)、在继承体系中基类和派生类是俩个不同作用域
2)、子类和父类中有同名的成员或成员函数,子类成员将屏蔽父类对成员的直接访问—隐藏—重定义
*实际中在继承体系里最好不要定义同名的成员
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{}
void Print()
{
_pub=0;
_pro=1;
_pri=2;
cout<<"_pub"<<_pub<<endl;
cout<<"_pro"<<_pro<<endl;
cout<<"_pri"<<_pri<<endl;
}
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
//派生类
class Derived:public Base
{
public:
Derived()//派生类构造函数--在函数体中只对派生类新增的数据成员初始化
{}
void Print()
{
_pubD=4;
_proD=5;
cout<<"_pubD"<<_pubD<<endl;
cout<<"_proD"<<_proD<<endl;
}
public:
int _pubD;
protected:
int _proD;
private:
int _priD;
};
int main()
{
Derived d;
d.Print();
cout<<sizeof(Derived)<<endl;
system("pause");
return 0;
}
运行结果如图所示:
7、继承与转换—赋值兼容规则—public继承
1)、子类对象可以赋值给父类对象(切割、换片)—>赋值的部分是子类继承父类的部分
2)、父类对象不能赋值给子类对象—>程序会崩溃
3)、父类的指针/引用可以指向子类对象
4)、子类的指针/引用可以指向父类对象——-可以通过强制类型转换完成
程序运行代码:
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{}
void Print()
{
_pub=0;
_pro=1;
_pri=2;
cout<<"_pub"<<_pub<<endl;
cout<<"_pro"<<_pro<<endl;
cout<<"_pri"<<_pri<<endl;
}
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
//派生类
class Derived:public Base
{
public:
Derived()//派生类构造函数--在函数体中只对派生类新增的数据成员初始化
{}
void Show()
{
d1=6;
}
public:
int d1;
int d2;
};
void FunTest()
{
Base b; //定义基类Base对象b
Derived d1; //定义公用派生类Derived对象d1
Base &r=b; //定义基类Base对象的引用变量r,并用b对其初始化
r=d1; //用派生类对象d1对Base的引用变量r赋值
b=d1; //用派生类Derived对象d1对基类对象b赋值
d1.d2=7; //d1中包含派生类中增加的成员
}
void fun(Base &r) //形参是类Base的对象的引用变量
{
cout<<r._pub<<endl; //输出该引用变量的数据成员_pri;
}
int main()
{
Derived d;
FunTest();
d.Print();
d.Show();
fun(d);
cout<<sizeof(Derived)<<endl;
system("pause");
return 0;
}
程序运行结果和分析:
8、继承与静态成员
基类定义了static成员(可以被继承),则整个继承体系里只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
二、单继承&多继承&菱形继承
1、单继承:一个子类只有一个直接父类的继承关系为单继承
2、多继承:一个子类有两个或以上直接父类时的继承关系 为多继承
如果已经声明了类B1和B2,则可以声明多重继承的派生类D
代码如下:
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
class D:public B1,public B2
{
public:
int _d;
};
int main()
{
cout<<sizeof(D)<<endl;
D d;
d._b1 =0;
d._b2 =1;
d._d =2;
system("pause");
return 0;
}
程序运行结果:
多重继承的先后:
子类对象赋值给父类对象:
3、菱形继承—派生类对象访问基类—程序代码
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class C1:public B
{
public:
int _c1;
};
class C2:public B
{
public:
int _c2;
};
class D:public C1,public C2
{
public:
int _d;
};
int main()
{
cout<<sizeof(D)<<endl;
D d;
d.C1::_b =0;
d._c1 =1;
d.C2::_b =2;
d._c2 =3;
d._d =4;
system("pause");
return 0;
}
程序运行结果:
菱形继承体系中子类对象包含多份父类对象,发生数据冗余&浪费空间的问题,派生类D的 大小中公有继承俩个B类,引发程序的二义性问题:分析结果如图所示:
4、虚拟继承—–虚基类—菱形虚拟继承
虚基类并不是在声明基类是声明的,而是在声明派生类是,指定继承方式时声明的。
*为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,
#include<stdlib.h>
#include<string>
#include<iostream>
using namespace std;
class B
{
public:
int _b;
};
class D:virtual public B
{
public:
int _d;
};
int main()
{
cout<<sizeof(D)<<endl;
D d;
d._b =0;
d._d =1;
system("pause");
return 0;
}
程序运行结果:
结果分析:
在内存中查看d的地址,会发现它的内存地址指向的也是一个地址,因此在内存c2中查看这个地址,发现它是一个指向偏移量表格的指针,编译器合成的派生类构造函数将偏移量表格指向前4个字节