基本概念
继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指B拥有A的全部特点),就可以将A作为一个基类,将B作为基类的一个派生类(也即子类)。
关于派生类,是通过对积累进行修改和补充得到的
在派生类中,不仅拥有基类的全部成员函数和成员变量,不论private、protected、public,虽然派生类的各个成员函数,均不能访问在积累中的private成员
派生类也还可以扩充新的成员变量和成员函数。
派生类对象的内存空间
关于派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。
在派生类对象中,包含着积累对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。
以下是关于其内存空间的一个实例:
继承实例的程序——学籍管理
#include <iostream>
#include <string>
using namespace std;
class CStudent
{// 基类
private:
string name;
string id;
char gender;//性别
int age;
public:
void PrintInfo();//输出个人信息
void SetInfo( const string & name_, const string & id_,
int age_, char gender_);//读入信息
string GetName(){ return name;}
};
class CUndergraduateStudent:public CStudent
{// 本科生,继承自CStudent
private:
string department;//学生专业
public:
void QualifiedForBAOyan(){
cout<< "qualified for baoyan"<<endl;
}
void 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_;
}
};
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_;
}
int main()
{
CUndergraduateStudent S2;
s2.SetInfo("Harry Peter","118829212",19,'M',"Computer Science");
cout << s2.GetName()<<"";//输出姓名后不换行
s2.QualifiedForBaoyan();
s2.PrintInfo();
system("pause");
return 0;
}
/*输出
Harry Potter qualified for baoyan
Name:Harry Potter
ID:118829212
Age:19
Gender:M
Department:Computer Science
*/
继承和复合的区别
关于继承,简单来说是“是”的关系
关于复合,简单来讲是“有”的关系
先不用纠结上面两句话的意思,往下看
( i )在继承中,有类B是基类A的派生类。
即class B: public A { }
则我们在逻辑上要求,一个B对象,首先也要是一个A对象
( i i )在复合中,有类C和类D是复合关系
即,类C中有一个成员变量k,k是类D的一个对象,则C与D是复合关系
我们在逻辑上要求,D对象是C对象的固有属性或组成部分
这样的话,我们再看本节开头那两句话
关于继承,简单来说是“是”的关系
关于复合,简单来讲是“有”的关系
是不是还挺有道理的~
复合关系的两个例子
1 点与圆
在集合形体程序里,我们写出“点”类和“圆”类,易犯的一个错误是什么呢,将“圆”看做“点”的一个派生。像下面这样写:
class CPoint
{
double x,y;
};
class CCircle:public CPoint
{
double r;//半径
//将CPoint中x,y视为圆心坐标
};
这样对么?当然不对
首先,派生类是不能访问基类的私有成员的,对叭?这里“圆”显然不能使用类“点”中的两个变量。其次,在逻辑上,圆就不是一个点,对叭?
那么我们说,这是什么关系呢?圆和点之间显然是复合关系:
每个“圆”对象中都包含有一个“点”对象,即圆心。
那我们就可以这么写:
class CPoint
{//原写法基础上加上友元类CCircle
double x,y;
friend class CCircle;
};
class CCircle
{
double r;//半径
CPoint center;//圆心
};
这样可以吧~很善了哈哈,CCircle作为友元类的操作可以使之能够对圆心进行操作,当然我们也可以讲浮点数x,y声明为public
2 狗与人
要写一个小区养狗的管理程序,需要一个“业主”类和一个“狗”类。假设狗只有一户主人,但一个业主最多可以有十条狗。
最直接的,我们写出如下代码段
//error 1
class CDog;
class CMaster
{
CDog dogs[10];
};
class CDog
{
CMaster m;
};
这样的话,显然是错误的。
为什么错呢,CMaster 的定义中,有10个CDog的对象,而在CDog中,又有一个主人的对象,这样的循环定义,导致这两者均无法算出具体分配的内存空间
//error 2
class CDog;
class CMaster
{
CDog * pdogs[10];
};
class CDog
{
CMaster m;
};
我们将狗类设置为业主的成员对象,将业主类中存放着狗类的对象指针数组
这样对吗?对了一半
怎么说呢,这样是可以过编译的,但是每条狗都有自己的一套定义,十条狗内部需要保持主人信息的一致性。但我们上述的定义方式导致了在修改人的信息时,需要给十条狗同时进行修改,这样显然是不够善的。
再来:
//example 3
class CMaster;
class CDog
{
CMaster * pm;
};
class CMaster
{
CMaster dogs[10];
};
我们为狗类设置一个业主类的对象指针,为业主类设置一个狗类的对象数组
这样写对了吗?还凑合
能够修正前面指出的两种不足,可以一次性对狗or人做出修改。
值得一提的是,上述代码的编写顺序是固定的,必须先对master进行声明,然后依次写出dog和master的具体函数。这是由于master中的狗狗数组必须能够计算出分配的具体内存空间。
为什么说还不够好呢?因为在这种情况下,作为成员对象,狗是作为人的固有属性存在的,想要对狗进行修改或者读取,是需要通过狗主人而实现的,狗狗失去了“狗格”。(不要嬉皮笑脸哈哈哈)
再写:
// right finally
class CMaster;
class CDog
{
CMaster * pm;
};
class CMaster
{
CMaster * dogs[10];
};
我们为狗类设置一个业主类的对象指针,而为业主类设置一个狗类的对象指针数组。
这就非常善了。为啥呢?
人和狗在复合的同时,都具有一定的自主性,不会出现前几个example中各种各样的弊端
类内存放指针对象的含义,或许我们可以理解为“知道”、“了解”的含义,而在类内存放普通对象,可以理解为“拥有”意,成为该类的固有属性,更极端一点说是“附庸”了