一、类的继承与类的派生
继承的概念
通过已有的类建立新类的过程,叫做类的派生
。
原来的类称为基类,也称为父类或一般类;新类称为派生类,也称为子类或特殊类。
派生类的定义与大小
派生类的定义
在C++语言中,从基类派生派生类的格式
class 派生类名 : 继承方式说明符 基类名
{
类体
};
继承方式说明符指明如何控制基类成员在派生类中的访问属性,通常有public、private和protected 3中方式。
例5-1 基类与派生类的定义
class BaseClass //基类
{
int v1,v2;
};
class DerivedClass:public BaseClass //派生类
{
int v3;
};
空类也可以作为基类。
程序5-1 派生类该百年基类成员的访问权限
#include
using namespace std;
class BaseClass
{
public:
int v1,v2;
BaseClass():v1(1),v2(1){}
int temp1(){}
};
class DerivedClass:public BaseClass
{
int v1;
int temp1(){}
public:
DerivedClass():v1(10){}
void printv()
{
cout<<"v1="<
类的大小
派生类大小占用的存储空间大小,等于基类成员变量占用的存储空间大小加上派生类大小自身成员变量占用的存储空间大小。可以使用sizeof()函数计算大小占用的字节数。
为类大小分配空间时,遵循字节对齐的原则,空类的大小是1,这是一种特殊情况。
对象占用的存储空间包含对象中各成员变量占用的存储空间。对象的大小与普通成员变量有关,与成员函数和类中的静态成员变量无关,即普通成员函数、静态成员函数、静态成员变量、静态常量成员变量等均对类对象的大小没有影响。
程序5-2 基类与子类占用空间及字节对齐
#include
using namespace std;
class BaseClass
{
int v1,v2;
char v4;
public:
int temp1(){}
};
class DerivedClass:public BaseClass
{
int v3;
int *p;
public:
int temp(){}
};
int main()
{
cout<<"Base="<
继承关系的特殊性
如果基类有友元类或友元函数,则其派生类不会因继承关系而也有此友元类或友元函数。
如果基类是某类的友元,则这种友元关系是被继承的。即被派生类继承过来的成员函数。
如果原来是基类的友元函数,那么它作为派生类的成员函数仍然是某类的友元函数。
基类的友元不一定是派生类的友元;基类的成员函数是某类的友元函数,则其作为派生类继承的成员函数仍是某类的友元函数。
程序5-3 派生类继承了友元函数
#include
using namespace std;
class another; //前向引用声明
class Base //基类
{
private:
float x;
public:
void print(const another &K);
};
class Derived:public Base //派生类
{
private:
float y;
};
class another
{
private:
int aaa;
public:
another()
{
aaa = 100;
}
friend void Base::print(const another &K);
};
void Base::print(const another &K)
{
cout<<"Base:"<
如果派生类Derived中重写了print()函数,若还想在函数中访问another的私有成员,则必须将类Derived的print()函数也声明为another的友元。
程序5-4 派生关系中的静态成员
#include
using namespace std;
class Base
{
private:
float x;
public:
static int staV;
Base()
{
staV++;
}
};
int Base::staV = 0;
class Derived:public Base
{
private:
float y;
public:
Derived()
{
staV++;
}
};
int main()
{
Base a;
cout<
有继承关系的类之间的访问
在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域。二者的作用范围不同,是相互包含的两个层,派生类在内层,基类在外层。
程序5-5 访问基类和派生类成员的方式
#include
using namespace std;
class CB
{
public:
int a;
CB(int x)
{
a = x;
}
void showa()
{
cout<<"Class CB--a="<name = name;
}
string CStudent::GetName()
{
return name;
}
void CStudent::SetId(string id)
{
this->id = id;
}
string CStudent::GetId(){
return id;
}
class CUndergraduateStudent:public CStudent //本科生类,继承于类CStudent
{
private:
string department; //学生所属的系名
public:
void PrintInfo();
void SetInfo(const string &,const string &,int,char,const string &);
};
void CUndergraduateStudent::PrintInfo()
{
CStudent::PrintInfo(); //调用基类的公有PrintInfo函数
cout<<"院系:\t"<
程序5-7 类之间的访问
假设公司雇员分为雇员(employee)、经理(manager)、工程师(engineer)和高级主管(director)。各类的属性如下:
employee(雇员)类:姓名、年龄、工资;
manager(经理)类:姓名、年龄、工资、行政级别;
engineer(工程师)类:姓名、年龄、工资、专业、学位:
director(高级主管)类:姓名、年龄、工资、行政级别、职务。
将类employee 设计为基类,含有成员变量:姓名、年龄和工资。类manager 和类 engineer 是类employee的派生类,类director是类manager的派生类。
#include
#include
using namespace std;
class employee //类employee将作为其他几个类的基类
{
short age;
float salary;
protected:
string name;
public:
employee(short ag,float sa,string na)
{
age = ag;
salary = sa;
name = na;
};
void print()
{
cout<<"\n"<
protected访问范围说明符
基类中的保护成员可以在派生类的成员函数中被访问。
程序5-8 基类中的成员是私有成员时的访问方式
#include
using namespace std;
class BaseClass
{
int v1,v2; //私有成员变量
public:
void SetValue(int m,int n)
{
v1 = m;
v2 = n;
}
void PrintValue();
};
void BaseClass::PrintValue()
{
cout<<"v1="<
程序5-9 基类中保护成员的访问方式
#include
using namespace std;
class BaseClass
{
protected:
int v1,v2;
public:
void SetValue(int m,int n)
{
v1 = m;
v2 = n;
}
void PrintValue();
};
void BaseClass::PrintValue()
{
cout<<"v1="<
多重继承
C++允许从多个类派生一个类,即一个派生类可以同时有多个基类。这称为多重继承。
从一个基类派生一个派生类的情况,称为单继承或单重继承。
一个类从多个基类派生的格式
class 派生类名 : 继承方式说明符 基类名1,继承方式说明符 基类名2,...,继承方式说明符 基类名n
{
类体
};
程序5-10 多重继承下的访问方式
#include
using namespace std;
class CB1
{
public:
int a; //重名
CB1(int x)
{
a = x;
}
void showa() //重名
{
cout<<"Class CB1==>a="<a="<a="<a=11
Class CD==>a=909
Class CB1==>a=101
CDobj.a=909
CDobj.CB2::a=202
多重继承方式下,为避免二义性,成员变量的访问方式如下
程序5-11 多重继承
#include
using namespace std;
class BaseClass1
{
public:
int v1,v2;
BaseClass1();
BaseClass1(int,int);
~BaseClass1();
};
BaseClass1::BaseClass1()
{
cout<<"BaseClass1 无参构造函数"<
二、 访问控制
公有继承
当类的继承方式为公有继承时,基类的公有成员在派生类中直接访问,在基类与派生类外直接访问;基类的保护成员在派生类中直接访问,在基类与派生类外调用公有函数访问;基类的私有成员在派生类中调用公有函数访问,在基类与派生类外调用公有函数访问。
当类的继承方式为公有继承时,派生类中定义的公有成员在派生类中直接访问.在基类与派生类外直接访问;派生类中定义的保护成员在派生类中直接访问,在基类与派生类外调用公有函数访问;派生类中定义的私有成员在派生类中直接访问,在基类与派生类外调用公有函数访问。
表5-1 公有继承的访问控制
各成员 | 派生类中 | 基类与派生类外 |
---|---|---|
基类的公有成员 | 直接访问 | 直接访问 |
基类的保护成员 | 直接访问 | 调用公有函数访问 |
基类的私有成员 | 调用公有函数访问 | 调用公有函数访问 |
从基类继承的公有成员 | 直接访问 | 直接访问 |
从基类继承的保护成员 | 直接访问 | 调用公有函数访问 |
从基类继承的私有成员 | 调用公有函数访问 | 调用公有函数访问 |
派生类中定义的公有成员 | 直接访问 | 直接访问 |
派生类中定义的保护成员 | 直接访问 | 调用公有函数访问 |
派生类中定义的私有成员 | 直接访问 | 调用公有函数访问 |
程序5-12 公有继承访问控制示例
#include
#include
using namespace std;
class Base
{
public:
int vBPub;
protected:
int vBPro;
private:
int vBPri;
public:
Base()
{
vBPub = 10;
vBPro = 20;
vBPri = 30;
};
void SetPriValue(int);
void SetProValue(int,int);
int GetPriValue();
int GetProValue();
};
void Base::SetPriValue(int k)
{
vBPri = k;
}
void Base::SetProValue(int m,int n)
{
vBPro = m;
vBPri = n;
}
int Base::GetPriValue()
{
return vBPri;
}
int Base::GetProValue()
{
return vBPro;
}
class Derived:public Base
{
public:
int vDPub,vBPub;
protected:
int vDPro;
private:
int vDPri;
public:
Derived()
{
vDPub = 100;
vDPro = 200;
vDPri = 300;
vBPub = 15;
};
void SetPriValue(int);
void SetProValue(int,int);
int GetPriValue();
int GetProValue();
void PrintValue();
};
void Derived::SetPriValue(int k)
{
vDPri = k;
}
void Derived::SetProValue(int m,int n)
{
vDPro = m;
vDPri = n;
Base::vBPro = 2*m;
// Base::vBPri = 2*n; //不可以直接访问从基类继承的私有成员变量
}
int Derived::GetPriValue()
{
return vDPri;
}
int Derived::GetProValue()
{
return vDPro;
}
void Derived::PrintValue()
{
cout<<"在派生类中访问基类"<
类型兼容规则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,也称为赋值兼容规则。
在公有派生的情况下,又以下3条类型兼容规则。
- 派生类的对象可以赋值给基类对象。
- 派生类对象可以用来初始化基类引用。
- 派生类对象的地址可以赋值给基类指针。
程序5-13 类型兼容规则的使用
#include
using namespace std;
class A
{
int an;
public:
A(){}
A(int n)
{
an = n;
}
};
class B:public A
{
int bn;
public:
B(int n):A(2*n)
{
bn = n;
}
};
int main()
{
A a(10);
B b(20);
a = b;
return 0;
}
程序5-14 验证使用类型兼容规则的输出结果
#include
using namespace std;
class A
{
int an;
public:
A(){}
A(int n)
{
an = n;
}
void print()
{
cout<<"A的对象:";
cout<<"an:"<
私有继承
当类的继承方式为私有继承时,基类的公有成员在第一级派生类中可以直接访问,在第二级派生类中不可访问,在基类与派生类外不可访问;基类的保护成员在第一级派生类中可以直接访问,在第二级派生类中不可访问,在基类与派生类外不可访向;基类的私有成员在第一级派生类中可以调用公有函数访问,在第二级派生类中不可访问,在基类与派生类外不可访问。
表5-2 私有继承的访问控制
第一级派生类中 | 第二级派生类中 | 基类与派生类外 | |
---|---|---|---|
基类的公有成员 | 直接访问 | 不可访问 | 不可访问 |
基类的保护成员 | 直接访问 | 不可访问 | 不可访问 |
基类的私有成员 | 调用公有函数访问 | 不可访问 | 不可访问 |
保护继承
保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可以直接访问。这样,派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但在类外通过派生类的对象无法直接访问他们。
三、派生类的构造函数和析构函数
构造函数与析构函数
在执行一个派生类的构造函数之前,总是先执行基类的构造函数。派生类对象消亡时,先执行派生类的析构函数,在执行基类的析构函数。
如果基类定义了带有形参表的构造函数,则派生类就应当定义构造函数。
定义派生类构造函数的格式
派生类名::派生类名(参数表):基类名1(基类1 初始化参数表),...,基类名m(基类m初始化参数表),成员对象名1(成员对象1 初始化参数表),...,成员对象名n(成员对象n 初始化参数表)
{
派生类构造函数函数体 //其他初始化操作
}
程序5-15 基类和派生类的构造函数与析构函数
#include
using namespace std;
class BaseClass
{
protected:
int v1,v2;
public:
BaseClass();
BaseClass(int,int);
~BaseClass();
};
BaseClass::BaseClass()
{
cout<<"BaseClass 无参构造函数"<
例5-11 修改程序5-15
将主函数修改如下:
int main()
{
cout<<"带参数对象的创建"<
例5-12
int main()
{
cout<<"无参对象的创建"<
程序5-16 调用基类和派生类的构造函数、析构函数和成员函数
#include
using namespace std;
class Base
{
private:
int Y;
public:
Base(int y = 0)
{
Y = y;
cout<<"Base("<
复制构造函数
程序5-17 派生类中的复制构造函数
#include
using namespace std;
class A
{
public:
A()
{
i = 100;
cout<<"类A默认构造函数"<
程序5-18 赋值运算符的重载及使用
#include
using namespace std;
class CBase
{
public:
CBase(){}
CBase(CBase & c)
{
cout<<"CBase::复制构造函数"<
多重继承的构造函数与析构函数
设计两个基类BaseClass1和BaseClass2共同派生一个派生类DerivedClass。
程序5-19 多重继承
#include
using namespace std;
class BaseClass1
{
protected:
int v1,v2;
public:
BaseClass1();
BaseClass1(int,int);
~BaseClass1();
void SetValue(int,int);
void PrintValue();
};
BaseClass1::BaseClass1():v1(0),v2(0)
{
cout<<"BaseClass1 无参构造函数"<
四、类之间的关系
类与类之间的关系
使用已有类编写新的类有两种方式:继承和组合。
封闭类的派生
如果一个类的成员变量是另一个类的对象,则为封闭类。
定义封闭类构造函数的形式
类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),...
{
类体
}
程序5-20 封闭类的构造函数
#include
#include
using namespace std;
class myDate
{
public:
myDate(); //构造函数
myDate(int);
myDate(int,int);
myDate(int,int,int); //构造函数
myDate(const myDate &d);
~myDate();
void setDate(int,int,int); //设置日期
void setDate(myDate); //设置日期
myDate getDate(); //获取日期
void setYear(int); //设置年
int getMonth();//获取月
void printDate() const; //打印日期
private:
int year,month,day; //成员变量,表示年、月、日
};
//在类体外定义成员函数
myDate::myDate()
{
year = 1970;
month = 1;
day = 1;
cout<<"myDate默认构造函数"<
*互包含关系的类
在处理相对复杂的问题而需要考虑类的组合时,很可能遇到两个类相互引用的情况,称为循环依赖。
class A //类A的定义
{
public:
void f(B b); //以类B对象为形参的成员函数
};
class B //类B的定义
{
public:
void g(A a); //以类A对象为形参的成员函数
}
解决办法是使用前向引用声明。
在提供一个完整的类定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。
例5-15 互包含的类
#include
#include
using namespace std;
class B; //前向引用声明
class A
{
int a;
B b; //不完整的类型定义
};
class B
{
A a;
int b;
};
int main()
{
return 0;
}
程序5-21 互包含的类
#include
#include
using namespace std;
class B;
class A
{
public:
int aInt;
B *bPoint = NULL;
void SetValue(int v)
{
aInt = v;
}
};
class B
{
public:
A aCla;
int bInt;
void SetValue(int v)
{
bInt = v;
}
};
int main()
{
A ca;
B cb;
ca.bPoint = &cb;
cout<<"ca.bPoint="<
五、多层次的派生
在C++中,派生可以是多层次的。
在C++中,类之间的继承关系具有传递性。
在定义派生类时,只需写直接基类,不需要写间接基类。
程序5-22 多级派生时构造函数的调用
#include
using namespace std;
class BaseClass1
{
protected:
int v1,v2;
public:
BaseClass1();
BaseClass1(int,int);
~BaseClass1();
};
BaseClass1::BaseClass1()
{
cout<<"BaseClass1 无参构造函数"<
将程序5-22的主函数修改如下
int main()
{
cout<<"带参数对象的创建"<
六、基类与派生类指针的互相转换
对于指针类型,可以使用基类指针指向派生类对象,也可以将派生类的指针直接赋值给基类指针。
但即使基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类中没有而仅在派生类中定义的成员函数。
当派生类指针指向基类对象时,必须要将基类对象进行强制类型转换,才能赋给派生类指针。
程序5-23 使用指针的情况
#include
using namespace std;
class CBase
{
protected:
int n;
public:
CBase(int i):n(i){}
void Print()
{
cout<<"CBase:n="<Print(); //调用的时派生类中的函数
pBase = pDerived; //基类指针 = 派生类指针,正确
cout<<"使用基类指针调用函数"<Print(); //调用的是基类中的函数
//pBase->Func(); //错误,通过基类指针不能调用派生类函数
//pDerived = pBase //派生类指针 = 基类指针
pDerived = (CDerived *)pBase; //强制类型转换,派生类指针 = 基类指针
cout<<"使用派生类指针调用函数"<Print(); //调用的是派生类的函数
return 0;
}
使用派生类指针调用函数
CDerived:n=3
CDerived:v=6
使用基类指针调用函数
CBase:n=3
使用派生类指针调用函数
CDerived:n=3
CDerived:v=6