前言:从这篇文章开始,我们进入C++进阶知识的分享,在此之前,我们需要先来回顾一个知识:
C++语言有三大特性,分别是封装、继承和多态,而我们前边所分享的各种容器类,迭代器等,它们所体现的就是封装:
- 将数据和方法放在一起,能够给用户访问就设为公有,不给用户访问就设为私有或保护。
- 可以将一个类型放到另一个类型里边,通过typedef成员函数调整,封装出一个全新的类型。
这篇文章,我们将分享C++的第二大特性------继承。
目录
一.何为继承
举个例子:
在学校这一个大的教育机构中,有学生,老师,保安,宿管 等等人员的存在。这些人的身份不同,那么就会有一些自己所独有的标签 ,比如学生有学生号,老师有职工号等。但是他们同时也都有共同的特性,比如姓名,年龄等等。
他们有共同的标签,也有独有的标签 ,如果每个身份都要单独为他们创造标签,这样是不是过于繁琐,于是就有了继承的概念:
先封装一个父(基)类,里边封装有这些人所具有的共同属性,比如姓名,年龄。随后通过父类继承出多个子(派生)类,子类既可以拥有父类中的属性,也可以自己建立自己独有的属性。
二.继承方式
cpp
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
string _name;//姓名
int _age;//年龄
};
class Student : public Person
{
protected:
int _stuid;//学号
};
class Teacher : public Person
{
protected:
int _jobid;//职工号
};
来看,Person作为父类,用于一个成员函数和两个成员变量 ,Student和Teacher通过"public"方式继承父类,同时也拥有自己的成员变量。来看测试:
能够看出,子类能够使用父类中的成员变量而后成员函数。
三.继承关系
在类中有public、protected、private三种访问方式。这些访问方式的不同,会影响到子类对父类中的成员变量和函数的访问。
这三种访问方式可以理解为三级权限,public为低级权限,protected为中级权限,private为高级权限。
这里给出最简单的理解方法,较低级的权限要向较高级的权限低头。
前边在类的讲解中我们没有分享protected和private的区别,这里我们给出二者的区别:
父类的private成员在类外任何地方都不能被访问,包括派生类,而父类的protected成员可以在派生类中访问。
一般情况下,public继承是最常用的。
此外,我们知道struct也可以定义类,也可以继承父类,它与class的区别是:
- struct默认的继承方式和访问限定符为public。
- class默认的继承方式和访问限定符为private。
四.父类和子类对象赋值转换
通过标题,我们能够得出信息,父类对象和子类对象直接可以赋值,但是这里我先强调:只能由子类对象赋值给父类对象,而不能父类对象赋值给子类对象。
直接来看例子:
要注意的是,父类对象只能得到自己继承下去的成员变量的信息,而不能得到子类自己的成员变量。
除此之外,我们还可以通过父类对象的引用或指针来访问或修改子类对象的成员变量:
因为父类对象中没有子类对象的成员变量,所以不能对其赋值。
五.继承中的作用域
- 在继承体系中父类和子类都有独立的作用域。
- 子类和父类中有同名成员,在调用该成员时,子类会默认调用自己的成员,这种情况叫隐藏,也叫重定义 。(在子类成员函数中,可以使用 基类::基类成员 显示访问父类的同名成员)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏,与参数无关。
- 注意在实际中在继承体系里面最好不要定义同名的成员。
来看实例:
cpp
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
string _name;//姓名
int _age;//年龄
int _num = 0;
};
class Student : public Person
{
public:
void fun()
{
cout << _num << endl;
cout << Person::_num << endl;
}
protected:
int _stuid;//学号
int _num = 1;
};
这里我们分别在Person和Student类中都定义了名为_num的成员变量,并在Student类中定义fun函数来打印二者,结果如下:
能够看出,默认情况下访问的是Student类自己的_num,加了域作用限定符后才会调用父类的_num。同名成员函数同理。
六.派生类的默认构造函数
6个默认成员函数,"默认"的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
总结来说, 子类自己的成员变量调用自己的默认成员函数,而继承自父类的成员则需调用父类的默认成员函数。父类没有的,就在子类中补充。
七.其他简单知识
- 父类的友元,子类无法直接使用,但也可以将该友元定义在子类中,成为子类的友元。
- 父类的静态成员,与所有的子类共用。
- 一个子类可以同时继承多个父类,但是不推荐这样设计。
总结
关于继承的知识就分享这么多,喜欢本篇文章记得一键三连,我们下期再见!