概念
继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)。
派生类具有基类的全部特点。而且派生类时通过基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。派生类一经定义后,可以独立使用,不依赖于基类。
派生类拥有基类的全部成员函数和成员变量,不论是private、protect、public。在派生类的各个成员函数中,不能访问基类中的private成员。
下面给一个例子
class Student
{
private:
string Name;
int Age;
public:
int GPA;
bool IsThreegood{};
void PrintInfo(){
cout << Name << ":" << Age << endl;
}
};
class UndergraduateStudent:public Student{
private:
int Department;//扩充
public:
bool IsThreegood() { return GPA >= 3.6; }//覆盖
bool canBaoyan() { return GPA >= 3.8; }; //扩充
void PrintInfo(){
Student::PrintInfo();//调用基类的PriintInfo
cout << Department << endl;
}
};//派生类的写法是 类名:public 基类名
像上面的程序,基类会有一个PrintInfo
函数,然后如果派生类里面想要实现同样的功能,但是无法读取基类的Private成员,所以会调用基类的函数输出基类里面的成员变量,然后再将派生类里面的成员变量输出。如果有SetInfo
函数也是同理。
派生类的对象的内存空间
派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。
继承关系和复合关系
继承:“是”关系。如果有一个基类A,B是A的派生类,那么逻辑上要求:“一个B对象也是一个A对象”。比如上面的从学生类派生出中学生类,这就是一个合理的派生;而比如从男人类派生出女人类,就是不合理的,因为“一个女人也是一个男人”在逻辑上是不对的。
复合:“有”关系。类C中有成员变量k,k是类D的对象,则C和D是复合关系。一般逻辑上要求:“D对象是C对象的固有属性或组成部分”。比如点类和圆类:
class Point{
private:
double x, y;
};
class Circle:public Point{
private:
double r;
};
像上面就是虽然看起来是对的,但实际上使用的是继承关系,而圆并不是一个点,所以这种继承关系在逻辑上是不成立的,应该写成下面的复合关系:
class Point{
private:
double x, y;
friend class Circle;
};
class Circle:{
private:
Point center;
double r;
};
因为圆是有圆心的,这就是一个特殊的点,所以复合关系在逻辑上是正确的。而为了Circle类便于操作center这个成员变量,所以需要在Point类中将Circle类声明为友元。接下来再举一个复合关系的例子:
假设有一个主人类,一个狗类,每个主人最多有10条狗,而每只狗只能有一个主人,我们很容易想到这样写:
class Dog;
class Master{
Dog dogs[10];
};
class Dog{
Master m;
};
然而实际上这样是错误的,其中出现了循环定义的问题,编译会出错。比如问你一个Master对象的大小是多少,其实是算不出来的。那如果写成指针数组的形式:
class Dog;
class Master{
Dog *dogs[10];
};
class Dog{
Master m;
};
这样也是不好的,因为可能多个狗指向同一个主人,那么怎么维护不同的狗中的主人的信息的一致性。比如一条狗里面的主人的信息修改了,那么其他同样主人的狗里面的信息都需要修改。
如果反过来:
class Master ;
class Dog{
Master *m;
};
class Master{
Dog d[10];
};
这样勉强可以,但一个问题是其实逻辑上还是有点别扭(狗并不是主人的固有属性),另一方面,所有的狗对象都包含在主人对象中,如果想要对它们进行修改,则需要通过主人对象,这是不太方便也不太好的。
正确的写法是:
class Master;
class Dog{
Master *pm;
};
class Master{
Dog *dogs[10];
};
覆盖
派生类可以定义一个和基类成员同名的成员,叫做覆盖。派生类中的访问缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号::
class Base{
public:
int i;
void func();
};
class Derived:public Base{
public:
int i;
void func();
void access(){
i = 5;//派生类的
Base::i = 2;//基类的
func();//派生类的
Base::func();//基类的
}
};
int main()
{
Derived obj;
cout << obj.i << endl;
cout << obj.Base::i << endl;
system("pause");
}
覆盖基类的成员函数是很常见的,但是不建议在派生类中覆盖基类的成员变量。
保护成员 protected
派生类的构造函数
在创建派生类对象时,需要调用基类的构造函数,来初始化派生类对象从基类继承的成员。
在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
对象消亡的时候,会先调用派生类的析构函数,再调用基类的析构函数。
class Bug
{
private:
int nLegs;
int nColor;
public:
Bug(int legs, int color) : nLegs(legs), nColor(color) {}
};
class FlyBug:public Bug
{
private:
int nWings;
public:
FlyBug(int legs, int color, int wings)
{
nLegs = legs;
nColor = color;
nWings = wings;
} //错误的构造函数,因为nLegs和nColor是基类的私有成员,不能在派生类中访问
FlyBug(int legs, int color, int wings) : Bug(legs, color), nWings(wings) {}//正确的构造函数
};
派生类的构造函数中也可以省略基类的构造函数,这样会调用基类的默认构造函数
public 继承的赋值兼容规则
class Base{ };
class derived:public base{ };
base b;
derived d;
以下三条必须要在public继承下才能进行
(1)派生类的对象可以赋值给基类对象
因为我们说“一个派生类对象就是一个基类对象”
b=d'
就是把d中的Base对象拷贝到b里面去,如果d=b
就是错误的
(2)派生类对象可以初始化基类引用
base &bd=d;
(3)派生类对象的地址可以赋值给基类指针
base *pb=&d;
这个指针就指向了派生类对象中的基类对象,因为基类对象的存储就在派生类对象存储地址的最前面,所以这个指针就指向了派生类对象的起始地址。