1. 继承和派生的概念
1 继承的概念
在定义一个新的类 B 时,如果该类与某个已有的类 A 相似(指的是 B 拥有 A 的全部特点),那么就可以把 A 作为一个基类,而把 B 作为基类的一个派生类(也称子类)。
- 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。修改指的是派生类中写名称与基类相同的成员函数,而该函数所进行的行为不太相同。
- 派生类一经定义后,可以独立使用,不依赖于基类。
- 派生类拥有基类的全部成员函数和成员变量,不论是 private、protected、public。
- 虽然继承了基类的 private 成员变量,但在派生类新定义的各个成员函数中,不能访问基类中的 private 成员。
2 派生类的写法
class 派生类类名:public 基类类名
{
...
};
- 派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。
3 继承实例程序:学籍管理
#include <iostream>
#include <string>
using namespace std;
class CStudent
{
private:
string name; //姓名
string id; //学号
char gender; //性别,‘F’代表女,‘M’代表男
int age;
public:
void PrintInfo();
void SetInfo(const string & name_,const string & id_,int age_,char gender_);
string GetName(){ return name; }
};
void CStudent::PrintInfo()
{
cout<<"Name:"<<name<<endl;
cout<<"ID:"<<id<<endl;
cout<<"Age:"<<age<<endl;
cout<<"Gender:"<<gender<<endl;
}
void CStudent::SetInfo(const string & name_,const string & id_,int age_,char gender_)
{
name = name_;
id = id_;
age = age_;
gender = gender_;
}
class CUndergraduate:public CStudent
{//本科生类,继承了CStudent 类
private:
string department; //学生所属的系的名称
public:
void QualifiedForBaoyan() //给予保研资格
{
cout<<"qualified for baoyan"<<endl;
}
void PrintInfo() //对基类 PrintInfo()函数的覆盖,即为对基类 PrintInfo()函数的修改
{
CStudent::PrintInfo(); //调用基类的 PrintInfo 函数
cout<<"Department:"<<department<<endl;
}
void SetInfo(const string & name_,const string & id_,int age_,char gender_,const string & department_)
{
CStudent::SetInfo(name_,id_,age_,gender_); //调用基类的 SetInfo 函数
department = department_;
}
};
int main()
{
CUndergraduate s2;
s2.SetInfo("Harry Potter","118829212",19,'M',"Computer Science");
cout<<s2.GetName()<<" ";
s2.QualifiedForBaoyan();
s2.PrintInfo();
system("pause");
return 0;
}
2. 继承关系和复合关系
1 C++类之间的关系
- 类之间有三种关系:没有关系、继承关系、复合关系
- 继承:“是”关系。基类A,B是基类A的派生类。逻辑上要求:“一个B对象也是一个A对象”。
- 复合:“有”关系。类C中“有”成员变量 k,k 是类D的对象,则C和D是复合关系。逻辑上要求:“D对象是C对象的固有属性或组成部分”。
复合关系使用的例子 :
class CPoint
{
double x,y;
friend class CCircle;
//便于 CCircle 类操作其圆心,因为 CPoint 类的成员对象是其私有的
};
class CCircle
{
double r;
CPoint center;
};
小区养狗的管理程序:两个类:主人类、狗类。狗只有一个主人,而每个业主最多可拥有10条狗。
class CDog;
class CMaster
{
CDog dogs[10];
};
class CDog
{
CMaster m;
};
上面这种处理方法是错误的,循环定义。CDog 类和CMaster 类的字节大小是多少?
另一种写法:为“狗”类设一个“业主”类的成员变量,为“业主”类设一个“狗”类的对象指针数组。
class CDog;
class CMaster
{
CDog * dogs[10];
};
class CDog
{
CMaster m;
};
该种写法避免了循环定义,但还是错误的。无法维护不同的狗中主人信息一致性的问题。如果改变了一条狗中的主人信息,另一条狗中的主人信息也应进行相应的改变。而这种写法无法做到。
第三种写法,凑合的写法。 为“狗”类设一个“业主”类的对象指针,为“业主”类设一个“狗”类的对象数组。
class CMaster; //CMaster必须提前声明,不能先写CMaster类,后写CDog类
class CDog
{
CMaster * m;
};
class CMaster
{
CDog dogs[10];
};
这种写法有两点不好的地方:一、狗不是主人的一部分,狗不是主人的固有属性。二、“狗”类失去了自由。对“狗”对象进行操作时要通过“主人”对象。
正确的写法: 为“狗”类设一个“业主”类的对象指针,为“业主”类设一个“狗”类的对象指针数组。
class CMaster; //CMaster必须提前声明,不能先写CMaster类,后写CDog类
class CDog
{
CMaster * m;
};
class CMaster
{
CDog * dogs[10];
};
3. 覆盖和保护成员
1 覆盖的概念
派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号 :: 。
基类和派生类不要定义同名的成员变量。
2 保护成员
基类的 private 成员可以被下列函数访问:
- 基类的成员函数
- 基类的友元函数
基类的 public 成员可以被下列函数访问:
- 基类的成员函数
- 基类的友元函数
- 派生类的成员函数
- 派生类的友元函数
- 其它的函数
基类的 protected 成员可以被下列函数访问:
- 基类的成员函数
- 基类的友元函数
- 派生类的成员函数可以访问当前对象的基类的保护成员。
#include <iostream>
using namespace std;
class Father
{
private:
int nPrivate; //私有成员
public:
int nPublic; //公有成员
protected:
int nProtected; //保护成员
};
class Son:public Father
{
void AccessFather()
{
nPublic = 1; //ok
// nPrivate = 1; //error
nProtected = 1; //ok ,访问当前对象从基类继承的 protected 成员。
Son f;
// f.nProtected = 1; //error。f 不是当前对象
}
};
int main()
{
Father f;
Son s;
f.nPublic = 1; //ok
s.nPublic = 1; //ok
// f.nProtected = 1; //error
// f.nPrivate = 1; //error
// s.nProtected = 1; //error
// s.nPrivate = 1; //error
system("pause");
return 0;
}
4. 派生类的构造函数
在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
调用基类构造函数的两种方式:
- 显式方式:在派生类的构造函数中,为基类的构造函数提供参数。derived::derived(arg_derived-list):base(arg_base-list)
- 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数。(若基类没有无参构造函数,则编译出错)
派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。
封闭派生类对象(含有成员对象)的构造函数执行顺序:
- 先执行基类的构造函数,用以初始化派生类对象 中从基类继承的成员。
- 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
- 最后执行派生类自己的构造函数。
在封闭派生类对象消亡时:
- 先执行派生类自己的析构函数。
- 再依次执行各成员对象类的析构函数。
- 最后执行基类的析构函数。
#include <iostream>
using namespace std;
class Bug
{
private:
int nLegs;
int nColor;
public:
int nType;
Bug(int legs, int color);
void PrintBug(){ };
};
Bug::Bug(int legs, int color)
{
nLegs = legs;
nColor = color;
}
class FlyBug:public Bug //FlyBug 是 Bug 的派生类
{
int nWings;
public:
FlyBug(int legs,int color, int wings);
};
//错误的派生类 FlyBug 构造函数的写法
//FlyBug::FlyBug(int legs,int color, int wings)
//{
// nLegs = legs; //不能访问
// nColor = color; //不能访问
// nType = 1; //ok
// nWings =wings;
//}
//正确的 FlyBug 构造函数
FlyBug::FlyBug(int legs,int color, int wings):Bug(legs,color) //派生类的初始化列表里直接初始化基类的 Bug 对象
{
nWings = wings;
}
int main()
{
FlyBug fb(2,3,4);
fb.PrintBug();
fb.nType = 1;
// fb.nLegs = 2; //error.nLegs是基类的私有成员变量,派生类不能访问
system("pause");
return 0;
}
5. public 继承的赋值兼容规则
class base { };
class derived:public base { };
base b;
derived d;
- 派生类的对象可以赋值给基类对象。b = d 。而基类对象不能赋值给派生类对象。
- 派生类对象可以初始化基类引用。 base & br = d 。
- 派生类对象的地址可以赋值给基类的指针。 base * pb = &d 。
如果派生方式是 private 或 protected ,则上述三条不可行。
2 直接基类和间接基类
在声明派生类时,只需要列出它的直接基类。
派生类沿着类的层次自动向上继承它的间接基类。
派生类的成员包括:
- 派生类自己定义的成员
- 直接基类中的所有成员
- 所有间接基类的全部成员