一、引入继承的目的
1. 代码重用
类的继承和派生机制,使程序员无需修改已有类,只需在已有类的基础上,通过增加少量代码或修改少量代码的方法得到新的类,从而较好地解决了代码重用的问题。
2. 代码的扩充
只有在派生类中通过添加新的成员,加入新的功能,类的派生才有实际意义。
二、派生类的声明格式(单继承)
class 派生类名:继承方式 基类名
{
//派生类新增的数据成员和成员函数
};
1)基类名是一个已经定义的类的名字,也可称为父类;
2)派生类名是继承原有类的特性而生成的新类的名称;
3)继承方式规定了如何访问从基类继承的成员,指定了派生类成员以及对象对于基类继承来的成员的访问权限,它包括关键字private、protected、public(分别表示私有、保护、公有继承)。
4) 如果不显示地给出继承方式关键字,系统默认为私有继承(private)。
三、基类成员在派生类中的访问属性
1. 从基类成员属性看
1)当基类成员在基类中的访问属性为private时,
在三种继承方式的派生类中的访问属性都不可直接访问;
2)当基类成员在基类中的访问属性为public时,
继承方式为public,在派生类中的访问属性为public,
继承方式为private,在派生类中的访问属性为private,
继承方式为protected,在派生类中的访问属性为protected;
3)当基类成员在基类中的访问属性为protected时,
继承方式为public,在派生类中的访问属性为protected,
继承方式为private,在派生类中的访问属性为private,
继承方式为protected,在派生类中的访问属性为protected。
基类成员在基类中访问属性 |
基类成员在派生类中访问属性 |
||
public |
private |
protected |
|
public |
public |
private |
protected |
private |
不可直接访问 |
不可直接访问 |
不可直接访问 |
protected |
protected |
private |
protected |
2. 从继承方式看
1)当继承方式为private时,
基类成员属性为public和protected,则在派生类中的访问属性为private,
基类成员属性为private,则在派生类中的访问属性为不可直接访问;
2)当继承方式为public时,
基类成员属性为public和protected,则在派生类中的访问属性为不变,
基类成员属性为private,则在派生类中的访问属性为不可直接访问;
3)当继承方式为protected时,
基类成员属性为public和protected,则在派生类中的访问属性为protected,
基类成员属性为private,则在派生类中的访问属性为不可直接访问。
派生类的继承方式 |
基类成员在基类中访问属性 |
||
public |
private |
protected |
|
public |
public |
不可直接访问 |
protected |
private |
private |
不可直接访问 |
private |
protected |
protected |
不可直接访问 |
protected |
四、派生类的构造函数和析构函数(单继承)
1. 说明:
1)基类的构造函数和析构函数不能被继承;
2)在派生类中,若对派生类中新增的成员进行初始化,就需要加入派生类的构造函数;
3)对所有从基类继承下来的成员的初始化工作,由基类的构造函数完成;
4)当基类含有带参数的构造函数时,派生类必须定义构造函数,以对基类的构造函数所需要的参数进行设置;
5)当基类的构造函数没有参数,或没有显式定义构造函数时(即使用默认构造函数),派生类可以不向基类传递参数,甚至可不定义构造函数;
6)若派生类的基类也是一个派生类,则每个派生类只需负责其直接基类的构造,一次上溯;
7)派生类与基类的析构函数是独立的(因为析构函数不带参数,故基类的析构函数不会因为派生类没有析构函数而得不到执行)。
2.构造函数和析构函数的执行顺序:
1)当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;
2)当撤销派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
例子:
#include <iostream.h>
class KBase
{
public:
KBase() //基类的构造函数
{
cout<<"Constructing base class/n";
}
~KBase() //基类的析构函数
{
cout<<"Destructing base class/n";
}
};
class KDerive1:public KBase
{
public:
KDerive1() //派生类1的构造函数
{
cout<<"Constructing derive1 class/n";
}
~KDerive1() //派生类1的析构函数
{
cout<<"Destructing derive1 class/n";
}
};
class KDerive2:public KDerive1
{
public:
KDerive2() //派生类2的构造函数
{
cout<<"Constructing derive2 class/n";
}
~KDerive2() //派生类2的析构函数
{
cout<<"Destructing derive2 class/n";
}
};
class KDerive3:public KDerive2
{
public:
KDerive3() //派生类3的构造函数
{
cout<<"Constructing derive3 class/n";
}
~KDerive3() //派生类3的析构函数
{
cout<<"Destructing derive3 class/n";
}
};
int main()
{
KDerive3 obj;
return 0;
}
程序结果:
Constructing base class
Constructing derive1 class
Constructing derive2 class
Constructing derive3 class
Destructing derive3 class
Destructing derive2 class
Destructing derive1 class
Destructing base class
3. 当派生类中含有内嵌对象成员时,构造函数的执行顺序:
1)首先调用基类的构造函数,
2)其次调用内嵌对象成员的构造函数(有多个对象成员时,调用顺序由它们在类中声明的顺序确定),
3)最后执行派生类的构造函数体中的内容。
4)撤销对象时,析构函数的调用顺序与构造函数的调用顺序正好相反。
4. 构造规则
4.1. 派生类构造函数的一般格式:
派生类名(参数总表):基类名(参数表)
{
//派生类新增成员的初始化语句
}
注:基类构造函数的参数,通常来源于派生类构造函数的参数总表,也可用常数值。
4.2. 当派生类中含有内嵌对象成员时,其构造函数的一般形式:
派生类名(参数总表):基类名(参数表1),内嵌对象名1(内嵌对象参数表1),……,内嵌对象名n(内嵌对象参数表n)
{
//派生类新增成员的初始化语句
}
4.3. 例子:
#include <iostream.h>
class KBase
{
private:
int x;
public:
KBase(int i)
{
x = i;
cout<<"Constructing base class/n";
}
~KBase()
{
cout<<"Destructing base class/n";
}
void Show()
{
cout<<" x = "<<x<<endl;
}
};
class KDerived:public KBase
{
private:
KBase d; //基类对象d作为派生类的对象成员
int y;
public:
//派生类嵌有对象成员时的构造函数格式
KDerived(int i, int j, int k):KBase(i),d(j)
{
y = k;
cout<<"Constructing derived class/n";
}
~KDerived()
{
cout<<"Destructing derived class/n";
}
void Show()
{
KBase::Show();
d.Show();
cout<<" y = "<<y<<endl;
}
};
int main()
{
KDerived obj(4,6,8);
obj.Show();
return 0;
}
程序结果:
Constructing base class
Constructing base class
Constructing derived class
x = 4
x = 6
y = 8
Destructing derived class
Destructing base class
Destructing base class
五、派生类成员覆盖基类的同名成员
1. 含义:
在派生类中定义了与基类同名的成员,称为派生类成员覆盖基类的同名成员。
2. 在派生类中使用基类的同名成员的方法:
2.1 基类名::成员名;
2.2 派生类的对象名.基类名::成员名;
3. 例子:
#include <iostream.h>
#include <string.h>
//类Student
class KStudent
{
private:
char *name;
char *stu_no;
float score;
public:
KStudent(char *name1, char *stu_no1, float score1);
~KStudent();
void Show();
};
KStudent::KStudent(char *name1, char *stu_no1, float score1)
{
name = new char[strlen(name1) + 1];
strcpy(name, name1);
stu_no = new char[strlen(stu_no1) + 1];
strcpy(stu_no, stu_no1);
score = score1;
}
KStudent::~KStudent()
{
delete []name;
delete []stu_no;
}
void KStudent::Show()
{
cout<<"/n name: "<<name;
cout<<"/n stu_no: "<<stu_no;
cout<<"/n score: "<<score;
}
//类KUstudent
class KUstudent:public KStudent
{
private:
char *major;
public:
KUstudent(char *name1, char *stu_no1, float score1, char *major1);
~KUstudent();
void Show(); //在派生类中,重新定义了成员函数Show()
};
KUstudent::KUstudent(char *name1, char *stu_no1, float score1, char *major1)
:KStudent(name1, stu_no1, score1)
{
major = new char[strlen(major1) + 1];
strcpy(major, major1);
}
KUstudent::~KUstudent()
{
delete []major;
}
void KUstudent::Show()
{
KStudent::Show(); //定义派生类时,访问基类的同名成员的格式
cout<<"/n major: "<<major<<endl;
}
int main()
{
KUstudent stu1("Liming", "990201", 90, "computer");
stu1.Show();
stu1.KStudent::Show();//派生类对象访问基类的同名成员的格式
return 0;
}
程序结果:
name: Liming
stu_no: 990201
score: 90
major: computer
name: Liming
stu_no: 990201
score: 90
六、访问声明
1. 格式:(在私有派生类的同名段中)
基类名::基类的成员函数名(或数据成员名);
2. 说明:
1)数据成员和函数成员均可使用访问声明;
2)访问声明的方法针对私有派生类、基类的保护成员或公有成员(不能访问基类的私有成员);
3)访问声明必须写在派生类定义式中的同名段中;
4)访问声明不能改变类成员原来在基类中的成员性质,只能把原基类的保护成员调整为派生类的保护成员,原基类的公有成员调整为派生类的公有成员;
5)访问声明机制可个别调整私有派生类从基类继承下来的成员性质,从而使外界可通过派生类的界面直接访问基类的某些成员,同时不影响其他基类成员的封闭性;
6)注意函数的访问声明的格式中不带返回类型和参数(既不能写出返回类型和括号);
7)对于重载函数使用访问声明时要慎重(因为对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用)。
3. 例子:
#include <iostream.h>
class KA
{
private:
int x;
public:
KA(int x1)
{
x = x1;
}
void Print()
{
cout<<" x = "<<x;
}
};
class KB:private KA
{
private:
int y;
public:
KB(int x1, int y1):KA(x1)
{
y = y1;
}
KA::Print; //访问声明,这时Print也就成为了类KB的公有成员
};
int main()
{
KB b(10,20);
b.Print();
return 0;
}
程序结果:x = 10
七、多重继承
1. 声明格式:
class 派生类名:继承方式1 基类名1,……,继承方式n 基类名n
{
//派生类新增的数据成员和成员函数
};
2. 说明:
1)默认的继承方式是private;
2)在多重继承中,公有继承和私有继承对于基类成员在派生类中的可访问性与单继承的规则相同。
3. 构造函数和析构函数
3.1. 构造函数定义格式:
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),……,基类名n(参数表n)
{
//派生类新增成员的初始化语句
};
3.2. 构造函数的执行顺序:(与单继承构造函数的执行顺序相同)
1)首先执行基类的构造函数(处于同一层次的各个基类的构造函数的执行顺序,取决于声明派生类时所指定的各个基类的顺序,与派生类构造函数中所定义的成员初始化列表的各项顺序没有关系);
2)再执行对象成员的构造函数;
3)最后执行派生类构造函数。
4) 析构函数的执行顺序刚好与构造函数的执行顺序相反。
3.3. 说明:
1)多重继承下派生类构造函数与单继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用,同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数;
3.4. 例子:
#include <iostream.h>
class KX
{
private:
int a;
public:
KX(int sa)
{
a = sa;
cout<<"Constructing class KX/n";
}
~KX()
{
cout<<"Destructing class KX/n";
}
int GetX()
{
return a;
}
};
class KY
{
private:
int b;
public:
KY(int sb)
{
b = sb;
cout<<"Constructing class KY/n";
}
~KY()
{
cout<<"Destructing class KY/n";
}
int GetY()
{
return b;
}
};
class KZ:public KX, private KY
{
private:
int c;
public:
KZ(int sa, int sb):KX(sa), KY(sb)
{
c = sb;
cout<<"Constructing class KZ/n";
}
~KZ()
{
cout<<"Destructing class KZ/n";
}
int GetZ()
{
return c;
}
/*int GetY() //采用重载的方式使函数GetY()成为类KZ的公有成员
{
return KY::GetY();
}*/
KY::GetY; //采用访问声明的方式使函数GetY()成为类KZ的公有成员
};
int main()
{
KZ obj(2,4);
int ma = obj.GetX();
cout<<"a = "<<ma<<endl;
int mb = obj.GetY();
cout<<"b = "<<mb<<endl;
int mc = obj.GetZ();
cout<<"c = "<<mc<<endl;
return 0;
}
程序结果:
Constructing class KX
Constructing class KY
Constructing class KZ
a = 2
b = 4
c = 4
Destructing class KZ
Destructing class KY
Destructing class KX