在原有类特性的基础上,进行更具体的类的定义;
新类继承了原有类的特性,也可以说原有类派生出新类;称为基类与派生类
7.1基类与派生类
7.1.1继承关系举例
类的继承,是新的类从已有类那里得到已有的特性,原有类称为基类或父类,产生的新类称为派生类或子类。
7.1.2派生类的定义
class 派生类名 : 继承方式 基类名1 , 继承方式 基类名2 …{
}
class Derived : public Base1 , public Base2{
public:
Derived();
~Derived();
}
一个派生类继承多个基类称为多继承,若只有一个直接基类的情况称为单继承。
在类的层次中,直接参与派生出某类的基类称为直接基类,基类的基类称为间接基类。
继承方式规定了如何访问从基类继承的成员;
类的继承方式指定了派生类成员以及类外对象对于从基类继承来的访问权限。
7.1.3派生类生成过程
经历过程:吸收基类成员、改造基类成员、添加基类成员。
主要目的就是实现代码的重用和扩充
class Account{ //账户类
private:
std::string id;
double balance;
static double total;//静态变量,实现类的共享
};
class CreditAccount : public Account { //信用账户类
};
1、吸收基类成员
派生类包含了它的全部基类中除构造和析构函数外的所有成员。
2、改造基类成员
(1)访问控制
(2)对基类数据或函数成员的覆盖和隐藏,
当在派生类中声明一个和某基类成员同名的新成员,则派生类的新成员就隐藏了外层同名成员,这时通过派生类的对象就只能访问到新声明的值了。
3、添加新成员
由于不能够继承构造函数和析构函数,因而需要在派生类中加入新的构造和析构函数。
7.2访问控制
从基类继承的成员,其访问属性由继承方式控制。
1.基类中成员有三种访问属性,public , protected , private
在基类中自身成员可以访问基类中任何一个其他成员,但通过基类的对象,只能访问到基类的共有成员。
2.类的继承方式有三种public , protected , private
不同的继承方式,导致原来具有不同访问属性的基类成员在派生类中的访问属性也有所不同。
这里的访问指:
一是派生类中的新增成员访问从基类继承的成员;
二是在派生类外部,通过派生类的对象访问从基类继承的成员。
总之 就是,不同的继承方式,通过上面的两种访问派生类方式,将会改变基类中不同访问属性的成员的上面两种访问权限,
7.2.1公有继承
当类的继承方式是公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问。
也就是说,
(1)基类中的public and protected 仍作为派生类的公有成员和保护成员,类中随便访问,类外只能通过对象访问公有,
(2)无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员。
#include <iostream>
using namespace std;
//1.基类
class Point{ //基类Point类的定义
public:
void initPoint(float x = 0, float y = 0){
this->x = x ; this->y = y;
}
void move(float offx, float offy){ //将坐标移动多少(x,y) ---> (x+offx,y+offy)
x += offx ; y += offy;
}
float getX() const { return x; } //类常成员函数
float getY() const { return y; }
private:
float x , y;
};
//2.派生类
class Rectangle : public Point { //派生类定义,继承点类
public: //新增公有函数成员
void initRectangle(float x , float y, float w , float h){
initPoint(x , y);//调用基类公有成员函数
this->w = w;
this->h = h;
}
float getH() const { return h; }
float getW() const { return w; }
private:
float w , h; //新增私有数据成员宽高
};
int main()
{
Rectangle a; //定义Rectangle类的对象
a.initRectangle(2 , 3 , 20 , 10);//设置矩形数据
a.move(3 , 2); //移动矩形的位置 ,将矩形位置移动到(5,5)坐标
//输出矩形的参数
cout << a.getX() <<" "<<
a.getY()<<" "<<
a.getW()<<" "<<
a.getH() << endl;
return 0;
}
7.2.2私有继承
当类的继承方式是私有继承时,基类中的公有和保护成员在派生类中变成私有成员,而基类的私有成员在派生类中不能直接访问
也就是说,
基类的公有和保护成员在派生类中变成私有成员,只能让派生类中的其他成员进行访问,而不可在类外访问,
两种访问方式都不可访问基类的私有成员。
#include <iostream>
using namespace std;
//1.基类
class Point{ //基类Point类的定义
public:
void initPoint(float x = 0, float y = 0){
this->x = x ; this->y = y;
}
void move(float offx, float offy){ //将坐标移动多少(x,y) ---> (x+offx,y+offy)
x += offx ; y += offy;
}
float getX() const { return x; } //类常成员函数
float getY() const { return y; }
private:
float x , y;
};
//2.派生类
class Rectangle : private Point { //派生类定义,私有继承点类
public: //新增公有函数成员
void initRectangle(float x , float y, float w , float h){
initPoint(x , y);//调用基类公有成员函数
this->w = w;
this->h = h;
}
//继承的公有变为私有,则不能够在类外访问,所以重写,
void move(float offx , float offy){
Point::move(offx , offy);//可以在类中访问继承后变成的私有
}
float getX() const { return Point::getX(); }
float getY() const { return Point::getY(); }
float getH() const { return h; }
float getW() const { return w; }
private:
float w , h; //新增私有数据成员宽高
};
int main()
{
Rectangle a; //定义Rectangle类的对象
a.initRectangle(2 , 3 , 20 , 10);//设置矩形数据
a.move(3 , 2); //移动矩形的位置 ,将矩形位置移动到(5,5)坐标
//输出矩形的参数
//这里调用的是派生类的公有函数
cout << a.getX() <<" "<<
a.getY()<<" "<<
a.getW()<<" "<<
a.getH() << endl;
return 0;
}
7.2.3保护继承
保护继承中,基类的公有和保护成员在派生类中都变为保护成员,而基类的私有成员不可以直接访问
也就是说,
派生类中的其他成员可以访问来自基类的公有和保护,但在类外不可以通过对象访问。
私有成员两种访问方式都不可访问。
7.3类型兼容规则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。
替代之后,派生类的对象就可以作为基类的对象来使用,注意公有继承不会继承构造和析构
class B { }
class D : public B { }
B b1 , *p1;
D d1;
用法:
(1)可以通过派生给基类赋值
b1 = d1;
(2)派生类的对象可以初始化基类对象的引用
B &rb = d1;
(3)派生类的对象的地址可以隐含转换为指向基类的指针
p1 = &d1;
#include <iostream>
using namespace std;
//1.基类Base1
class Base1 {
public:
void display() const {cout << "Base1::display()" << endl;}
};
//2.公有派生类Base2
class Base2 : public Base1 {
public:
void display() const {cout << "Base2::display()" << endl;}
};
//3.公有派生类Base3
class Base3 : public Base2 {
public:
void display() const {cout << "Base3::display()" << endl;}
};
//4.fun函数,参数为指向基类对象的指针
void fun(Base1 * ptr){
ptr->display(); //对象指针->成员名
}
int main()
{
Base1 base1;
Base2 base2;
Base3 base3;
fun(&base1);
fun(&base2);
fun(&base3);
return 0;
}
Base1::display()
Base1::display()
Base1::display()
//4.fun函数,参数为指向基类对象的指针
void fun(Base2 * ptr){
ptr->display(); //对象指针->成员名
}
int main()
{
Base1 base1;
Base2 base2;
Base3 base3;
//fun(&base1);
fun(&base2);
fun(&base3);
return 0;
}
//这样改后的结果
Base2::display()
Base2::display()
结果显示都是调用的是基类的display,
也就是说明,尽管指针指向派生类对象的地址,但是通过基类类型的指针,只能访问到从基类继承的成员,而不是前面说的,将同名的成员函数进行重写,
所以就是,当对象作为地址被基类类型(只能被基类或本类)的指针调用时,只能访问到从基类继承来的成员。
再如,对一般变量也是。
#include <iostream>
using namespace std;
//1.基类Base1
class Base1 {
public:
void display() const {cout << "Base1::s"<< s << endl;}
int s = 11;
};
//2.公有派生类Base2
class Base2 : public Base1 {
public:
int s = 12;
void display() const {cout << "Base2::s"<< s << endl;}
};
//3.公有派生类Base3
class Base3 : public Base2 {
public:
int s = 13;
void display() const {cout << "Base3::s"<< s << endl;}
};
//4.fun函数,参数为指向基类对象的指针
void fun(Base1 * ptr){
ptr->display(); //对象指针->成员名
}
int main()
{
Base1 base1;
Base2 base2;
Base3 base3;
fun(&base1);
fun(&base2);
fun(&base3);
return 0;
}
Base1::s11
Base1::s11
Base1::s11
7.4派生类的构造和析构函数
基类的构造和析构不会继承,如果在派生类中对新增成员进行初始化,就必须为派生类添加构造函数,
但派生的构造函数只对新增的成员进行初始化,对所有从基类继承的成员需要通过基类的构造函数来初始化。
7.4.1构造函数
构造派生类的对象时,就要对基类的成员对象和新增成员对象进行初始化
(1)首先要完成对基类成员对象的初始化工作,需要通过调用基类的构造函数;
(2)派生类的构造函数需要以合适的初值作为参数,其中一些参数要传给基类的构造函数,用于初始化相应的成员,
另一些参数要用于对派生类新增的成员进行初始化;
程序执行过程:
(1)在构造派生类的对象时,首先调用基类的构造函数,来初始化继承来的成员
(2)然后按照构造函数初始化派生类新增的数据成员,
(3)最后执行函数体。
派生类构造函数的一般语法形式:
派生类名 :: 派生类名( 参数表 ) : 基类名1(基类1初始化参数表) , … ,成员对名1(成员对象初始化参数表) , … , 基本类型成员初始化
{
}
一些说明:
(1)派生类的构造函数名与类名相同
(2)在参数表中,需要给出初始化基类数据和新增成员对象所需要的数据
(3)在参数表后,列出需要使用参数进行初始化的基类名和成员对象名以及各自的初始化参数表
如:
//Base1初始化a,Base2初始化b,c
class Base3 : public Base1 , public Base2{
public:
Base3(int a , int b , int c ,int d):Base1(a) , Base2(b , c) , member(d){
...
}
private:
Base1 member; //成员对象名
}
int main()
{
Base3 base3(1 , 2 ,3 , 4);
}
另外注意,什么时候需要声明派生类的构造函数,
如果对基类初始化时,需要调用基类的带有形参表的构造函数时,派生类就必须声明构造函数。
#include <iostream>
using namespace std;
class Base1 {
public:
Base1(int i){ cout << "constructing Base1 " << i << endl; }
};
class Base2 {
public:
Base2(int j){ cout << "constructing Base2 " << j << endl; }
};
class Base3 {
public:
Base3(){ cout << "constructing Base3 * "<< endl; }
};
class Derived : public Base2 , public Base1 , public Base3{
//注意基类名的顺序
public:
Derived(int a,int b ,int c ,int d):Base1(a) , member2(d), member1(c), Base2(b){
//注意基类名的个数与顺序,注意成员对象名的个数与顺序
}
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
int main()
{
Derived re(1 , 2 , 3 , 4);
return 0;
}
constructing Base2 2
constructing Base1 1
constructing Base3 *
constructing Base1 3
constructing Base2 4
constructing Base3 *
7.4.2复制构造函数
系统自带,自己写的话,
Derived::Derived(const Derived &v) : Base(v) { … }
7.4.3析构函数
派生类的析构函数的功能就是在该类对象消亡之前进行一些必要的清理工作。
没有类型,没有参数。
系统会自动生成,如果要自己定义,则派生类析构函数的声明和没有继承关系的类中析构函数的声明完全相同。
7.4.4删除delete构造函数
构造函数/复制构造函数 = delete
即可删除构造函数。
7.5派生类成员的标识与访问
7.5.1作用域分辨符
即 :: 它用来限定要访问的成员所在的类的名称
一般形式:
类名::成员名
类名::成员名(参数表)
1.单继承
(1)如果派生类声明了一个和某个基类成员同名的新成员,则就会隐藏基类成员,直接使用成员名只能访问到派生类的成员;
(2)同理如果在派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式都会被隐藏;
(3)要想访问被隐藏的成员,就要使用作用域分辨符和基类名来限定。
2.多继承
(1)某派生类有多个基类,如果派生类的多个基类拥有同名的成员函数
(2)当该派生类有同名函数时,会将所有的基类的同名函数隐藏,使用对象名.成员名或对象指针->成员名,只能访问到派生类的,
(3)当没有同名函数时,**在使用该函数必须使用基类名和作用域分辨符来标识成员,**因为不知道是哪个基类来的函数。
#include <iostream>
using namespace std;
class Base1 { //基类
public:
int var;
void fun(){ cout << "Base1 " << endl;}
};
class Base2 {
public:
int var;
void fun(){cout << " Base2 " << endl;}
};
class De : public Base1 , public Base2 { //派生类继承
public:
int var;
void fun(){cout << "DE " << endl;}
};
int main()
{
De d;
De* p = &d;
d.var = 1;
d.fun();
d.Base1::var = 2; //作用域分辨符标识
d.Base1::fun();
p->Base2::var = 3;
p->Base2::fun();
system("pause");
return 0;
}
DE
Base1
Base2
7.5.2虚基类
当某类的部分或全部直接基类是从另一个共同基类派生而来的时,
将共同基类设置为虚基类,这时从不同的路径继承而来的同名数据成员在内存中就只有一个,同一个函数名也只有一个映射。
虚基类的作用和作用域分辨符相同
虚基类的声明是在派生类定义时进行的,语法形式:
class 派生类名 : virtual 继承方式 基类名
进而声明了基类为派生类的虚基类,之后虚基类的成员在进一步派生的过程中和派生类一起维护同一个内存数据。
#include <iostream>
using namespace std;
/*
Base1 //共同基类
Base2 Base3 //都继承Base1,使用虚基类,可以省去作用域分辨符
De //继承Base2,Base3
*/
class Base1 { //共同基类
public:
int var1;
void fun1(){ cout << "Base1 " << endl;}
};
class Base2 : virtual public Base1 { //Base2是由虚基类Base1派生
public:
int var2;
};
class Base3 : virtual public Base1 { //Base3是由虚基类Base1派生
public:
int var3;
};
class De : public Base2 , public Base3 { //派生类继承
public:
int var;
void fun(){cout << "DE " << endl;}
};
int main()
{
De d;
d.var1 = 2; //由于定义基类为虚基类,所以可以直接访问基类的数据成员
d.fun1(); //直接访问虚基类的函数成员
system("pause");
return 0;
}
Base1
使用虚基类之后,在类的派生中使用virtual关键字,在main中,创建一个派生类的对象,然后就可直接通过对象名直接访问到该虚基类的成员了。
如果不使用虚基类,使用作用域分辨符,如下,产生同样结果。
#include <iostream>
using namespace std;
/*
Base1 //共同基类
Base2 Base3 //都继承Base1,使用虚基类,可以省去作用域分辨符
De //继承Base2,Base3
*/
class Base1 { //共同基类
public:
int var1;
void fun1(){ cout << "Base1 " << endl;}
};
class Base2 : public Base1 { //Base2是由虚基类Base1派生
public:
int var2;
};
class Base3 : public Base1 { //Base3是由虚基类Base1派生
public:
int var3;
};
class De : public Base2 , public Base3 { //派生类继承
public:
int var;
void fun(){cout << "DE " << endl;}
};
int main()
{
De d;
d.Base2 :: var1 = 2; //由于定义基类为虚基类,所以可以直接访问基类的数据成员
d.Base2 :: fun1(); //直接访问虚基类的函数成员
system("pause");
return 0;
}
这里为什么不能写d.Base1::var1
是因为Base1’ is an ambiguous base of 'De
ambiguous 模糊,Base1不是De的基类,所以不能够使用。
7.5.3虚基类及其派生类构造函数
比较麻烦,
直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中列出对虚基类的初始化
#include <iostream>
using namespace std;
/*
Base1 //共同基类
Base2 Base3 //都继承Base1,使用虚基类,可以省去作用域分辨符
De //继承Base2,Base3
*/
class Base1 { //共同基类
public:
Base1(int v) : var1(v){}
int var1;
void fun1(){ cout << "Base1 " << endl;}
};
class Base2 : virtual public Base1 { //Base2是由虚基类Base1派生
public:
Base2(int v) : Base1(v){}
int var2;
};
class Base3 : virtual public Base1 { //Base3是由虚基类Base1派生
public:
Base3(int v) : Base1(v){}
int var3;
};
class De : public Base2 , public Base3 { //派生类继承
public:
De(int v) : Base1(v) , Base2(v) ,Base3(v){ }
int var;
void fun(){cout << "DE " << endl;}
};
int main()
{
De d(1);
d.var = 2;
d.fun();
system("pause");
return 0;
}
DE