继承与派生
定义
从已有的类产生新的类叫派生,新的类叫子类(派生类),已有类叫父类(基类),
称子类继承父类;或父类派生出子类。派生与继承是同一种意义的两种表述。
作用
C++利用继承来实现代码的可重用。子类继承父类的全部成员(除了构造函数与析构函数);
则子类对象可以调用父类的函数而不必重新定义函数与变量。
继承的关系定性
is a
is a 的关系是一种属于的关系,如:学生是人,老师是人。校长是人。则可以把
人设计成为基类把学生,老师,校长设计成为派生类。
has a
has a 的关系是一种拥有关系,如:电脑有: CPU,硬盘,显卡,内存等等。则可以
设计 CPU 硬盘 显卡 内存 等等为基类。电脑为派生类,由各种组件组成。
语法
派生类:
class 派生类名 :继承方式 基类名
{
正常声明;
}
继承示例
基类 man
class man{
char* sex;
int tall;
char *name;
public:
void showData(char _name[],char _sex[],int _tall){
sex=new char[strlen(_sex)+1];
strcpy(sex,_sex);
tall=_tall;
name=new char[strlen(_name)+1];
strcpy(name,_name);
cout<<"名字:"<<name<<" 性别:"<<sex<<" 身高:"<<tall<<endl;
}
};
派生类 student
class student:public man
{
};
int main() {
student st;
st.showData("小明","男",180);
return 0;
}
如果需要实现老师类,校长类,则不需要重复实现函数 showdata(),都继承至父类就好了
继承方式
示例说明:
存在以下基类:
class base{
public:
int pub;
protected:
int pro;
private:
int pri;
}
派生类在不同方式继承下,三个数据的可见性表:
继承方式 | public | protected | private |
---|---|---|---|
pub | public | protected | private |
pro | protected | protected | private |
pri | 不可见 | 不可见 | 不可见 |
补充说明
类内的权限控制:
public | protected | private |
---|---|---|
对外开放 | 子类可见 | 仅本类内 |
派生类的构造
基类成员初始化
派生类要调用基类成员必须对基类进行构造
基类成员的初始化还是由基类的构造函数完成。然后再执行子类构造。
语法:
派生类名::派生类名(参数总表)
:基类名(参数表),内嵌子对象(参数表)
{
派生类新增成员的初始化语句; //也可出现地参数列表中
}
需要注意的是:在C++类的模块中系统提供很多默认的东西。这里也不例外。当你都不实现采用系统提供的构造函数时,不需要显式调用基类构造函数。但如果父类没有默认构造函数;则需要上面的格式实现派生类构造器。
构造顺序与析构顺序
构造顺序:
派生类的构造,会先调用基类的构造,如果派生类的数据中存在其他类对象作为数据成员
则构造顺序为:基类构造->派生类内数据对象构造->派生类构造
** **:当派生类构造完成则该派生类对象已经全部初始化,所以基类与类内对象必须提前
初始化。
析构顺序:
析构顺序为:派生类析构->派生类内数据对象析构->基类析构
个人理解:当变量的存在空间销毁时自动回收,则当派生类对象析构时,带动其他两个析构。
例子:
class birthday{
public:
birthday(){
cout<<birthday<<endl;
}
~birthday(){
cout<<"~birthday()"<<endl;
}
}
class man{
public:
man(){
cout<<"man()"<<endl;
}
~man(){
cout<<"~man()"<<endl;
}
};
class student:public man
{ public:
student(){
cout<<"student()"<<endl;
}
~student(){
cout<<"student()"<<endl;
}
};
测试:
int main() {
student st;
//st.showData("小明","男",180);
return 0;
}
结果:
继承中的拷贝构造
派生类中的默认拷贝构造器会调用父类中默认或自实现拷贝构造器,若派生类中自实现拷
贝构造器,则必须显示的调用父类的拷贝构造器
自实现使用格式可参考以下实例
示例
class Student{
public:
Student(const Student & another)
{
name = another.name;
num = another.num;
sex = another.sex;
}
}
class Graduate{
Graduate(const Graduate & another)
:Student(another),salary(another.salary){ }
}
这里之所以可以直接传派生类对象给基类拷贝构造函数,是因为继承有个赋值兼容的机制
赋值兼容
赋值兼容是指可以派生类对象可以隐式转换为基类对象,这一点在拷贝构造,友元中都有相关应用。
继承中的赋值运算符重载
在继承中,编译器都会提供 派生类与基类的默认赋值运算符重载,关于默认可参考day4 C++复制构造、赋值运算符重载、this指针,类对象内存,赋值运算符重载跟普通成员函数一样都可以被继承,同时,系统提供的默认会自动调用基类的赋值运算符重载,但如果自实现了派生类的赋值运算符重载,则需要主动显式地调用基类重载。
自实现使用格式可参考以下实例
示例
class student{
private:
string name;
int num;
char sex;
public:
student& opreator=(const student& another){
if(this == &another)
return *this;
this->name = another.name;
this->num = another.num;
this->sex = another.sex;
return * this;
}
}
class Graduate{
private:
int salary;
public:
Graduate & operator=(const Graduate & another){
if(this == &another)
return *this;
Student::operator =(another);
this->salary = another.salary;
return * this;
}
}
友元
友元并不能继承,但是有些时候需要使用基类的友元。这时候可以借助赋值兼容把子类对象强转成为基类对象。
shadow现象
shadow现象是指派生类对象中出现了与基类的同名函数,注意这里单单是因为同名派生类隐藏基类的同名成员。如果需要访问这些被隐藏的成员,需要加上基类名作用域。
格式:基类名::成员名;