面向对象的三大特性封装,继承,多态。
继承作为三大特性之一,在面向对象的设计的重要手段,它使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象程序设计的层次结构,继承是类设计层次的复用。
到底什么是继承?
如果B类继承于A类,那么B类中就拥有了A类中的方法和数据。A类中的方法和数据就成了B类中的一部分,B类对象可以访问到这些数据和方法当然也有一定的权限问题,当然B类也有自己的数据和方法。
继承格式
class A{
public:
private:
int a;
}
// B 继承于 A
class B : public A {
public:
private:
int b;
}
//B中隐式的包含了A中的 数据和方法
- B类 被称为派生类 或 子类
- A类 被称为基类 或 父类
- :为继承的符号
- public 为继承的权限
应用举例
//假设有一个Person类
class Person{
public:
void print(){
cout << name <<endl;
cout << age << endl;
}
protected:
string name = "liming";
int age = 20;
}
class Student : public Person{
public:
private:
int stuid: // 学号
}
class Teacher : public Person{
private:
int jobid;//教师职工号
}
int main()
{
Student stu;
Teacher tch;
stu.printf();
tch.printf();
return 0;
}
//输出结果
liming
20
liming
20
Student类和Teacher类继承person类,在这两个类中有了Person类中的数据name 和 age,以及成员方法 Printf();通过复用,可以使Student类和Teacher类,都可以使用person的数据和方法,不用再写声明相同的数据和方法,节省了代码量。
继承中的权限问题
在继承中还涉及到了权限的问题,有三种继承方式
//使用一下三种访问限定符来表示
public继承
protected继承
private继承
这三种继承方式于基类中的数据与方法的访问权限之间存在着一定的联系,不同的组合方式会有不同的结果。
继承方式与访问权限之年间的关系表
继承方式\访问权限 | 基类public成员 | 基类protected成员 | 基类private成员 |
---|---|---|---|
public继承 | 派生类的public成员 | 派生类的protected成员 | 派生类不可见 |
protected继承 | 派生类protected成员 | 派生类protected成员 | 派生类不可见 |
private继承 | 派生类private成员 | 派生类private成员 | 派生类不可见 |
通过继承关系表我们可以发现他们之间存在着错综复杂的关系。以下是一些需要注意的要点:
- 从基类继承过来的数据的访问权限与继承方式之间存在着一个关系: 派生类中继承来的数据和方法的访问权限取决于两个权限中较小的一个
-基类中的private已将被基类继承,但是却因为语法限制无法访问无论类外。无论何种继承方式都不能被访问。 - protected限定符表示在类外不可访问,在类中才可访问。保护成员限定符是因为继承才出现的。
- class中默认为private继承,struct中默认为public继承,一般最好显示写出继承方式
- public继承最为常用,private/protected继承只能在内中访问,作用不大
派生类对象和基类对象之间存在着类型转换的问题
将派生类给基类
可以把派生类对象赋值给基类对象\基类指针\基类引用
,通常将这种方法叫做切片,意指将派生类从基类继承来的那一部分赋值给基类。
class person{
protected:
string name;
int age;
}
class student : public person{
protected:
int stuid;
}
int main()
{
//可以将派生类对象赋值给基类对象,包括指针和引用
student stu;
person per = stu;
person& per = stu;
person* per = &stu;
}
- 对象之间的赋值,是将派生类从基类继承来的数据复制一份给基类对象
- 指针之间的赋值,是将基类的指针指向派生类的对象,但所指的范围只包括基类的数据
- 引用之间的赋值,引用在底层也是一个指针,所以原理指针赋值的原理基本相同
基类对象无法再给派生类
student stu;
person per = stu;
person* per1 = &stu;
person& per2 = stu;
;
//stu = per; //不可以
student* stu1 = (student*)per1; //通过类型的强制转换可以将转换过的指针转换回去
stu1 = (student*)&per;//可以转换但会越界访问无法访问到student的数据
//student& stu3 = per2; //不可以
person per3;
stu1 = (student*)&per3 //可以转换,会越界无法访问到student的数据是乱码
- 如果基类指针之前由派生类指针转换过来,此时则可以通过强制类型转换将该基类指针转换为派生类指针
- 如果该基类指针不是从派生类转换过来的,则可以通过强制类型转换完成,但会发生越界访问,只能访问到基类的数据,无法访问派生类数据,是乱码。
基类与派生类之间的赋值转换只能发生在共有继承中
继承之间的作用域关系
- 在继承中,基类与派生类都有自己独立的作用域。
- 隐藏(重定义):当派生类中与基类中存在同名数据时就构成了隐藏,子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定
义。
当子类对象对该同名数据或方法调用时,只能调用到子类的数据或方法。
- 当要访问被隐藏的基类数据或方法,只能同过域名作用限定符访问 基类类名::数据在子类中 进行显示访问
- 在基类和派生类中,只要函数名相同就构成了隐藏
- 尽量不要使用同名数据。
派生类中的默认的成员函数
在类中有六个默认的成员函数
- 构造函数:主要完成初始化工作
- 拷贝构造函数:使用同类对象初始化创建对象
- 析构函数:完成清理工作
- 赋值运算符的重载:把一个对象赋值给另一个对象
- 取地址运算符的重载:对&的重载
- const 取地址运算符的重载:对const & 的重载
默人的成员函数在派生类中与基类之间的关系:
- 派生类的构造函数调用基类的构造函数来初始化继承字基类中的数据,如果基类中没有默认的构造函数,就在派生类构造的初始化列表阶段进行显示的调用。
- 派生类的拷贝构造调用基类的拷贝构造完成对基类数据的拷贝。
- 派生类的operator=调用基类的operator=完成对基类数据的赋值
- 派生类对象初始化先调用基类构造再调派生类构造
- 派生类对象析构清理先调用派生类析构再调基类的析构
先构造基类再构造派生类 先析构派生类再析构基类
在函数栈帧的调用中,要符合先进后出的要求,符合栈的调用,所以先析构派生类,再析构基类
只有当派生类析构完后才会析构基类
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类
对象先清理派生类成员再清理基类成员的顺序。
派生类的析构与基类的析构构成了隐藏(由于继承中多态的原因),析构函数不显示调用,由编译器自己调用
实现一个不能被继承的类
- 在c++98中,我们将构造函数私有化,这个类将无法被继承
- 在c++11中提供了final ,final关键字修饰的类为最终类,表明该类不能被继承
继承中的友元关系
友元函数不能被继承,也就是说基类友元不能访问子类私有和保护成员
继承中的static
- 整个继承体系中只能有一个static成员,无论该基类派生出多少子类,都只能有一个static成员
- 所有子类对静态成员的操作都只是在同一个静态成员之上