C++之继承
✅博客主页:爆打维c-CSDN博客 🐾
🔹分享c语言知识及代码 🐾
前言
c++是面向对象的编程语言,三大特性就是封装、继承、多态,本文将着重介绍继承这一特性。
一、继承是什么?
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。继承呈现面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用。
二、为什么需要使用继承?
没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/电话/年龄等成员变量,都有identity⾝份认证的成员函数,设计到两个类⾥⾯就是冗余的。当然他们也有⼀些不同的成员变量和函数,⽐如⽼师独有成员变量是职称,学⽣的独有成员变量是学号;学⽣的独有成员函数是学习,⽼师的独有成员函数是授课。
class Student {
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity() {// ...}// 学习void study() {// ...}protected:string _name = "peter"; // 姓名string _address;// 地址string _tel;// 电话int _age = 18;// 年龄int _stuid;// 学号
};
class Teacher {
public: // 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity() {// ...}// 授课void teaching() {//...}protected:string _name = "张三";// 姓名int _age = 18;// 年龄string _address;// 地址string _tel;// 电话string _title;// 职称
};
int main() { return 0; }
如果将公共的成员都放到Person类中,Student和teacher都继承Person,就可以复用这些成员,就不需要重复定义了,省去了很多麻烦
class Person{.....
};class Student : public Person {
public:// 学习void study() {// ...}protected:int _stuid;// 学号
};class Teacher : public Person {
public:// 授课void teaching() {//...}protected:string title;// 职称
};
所以我们需要使用继承 减少重复冗余的代码 使代码得到复用!!!
三、继承怎么用?
1.定义一个派生类
了解了继承的基本概念,我们下面将讲到继承的用法,怎么定义一个派生类?
在这之前我们需要了解继承基类后访问方式的变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
总结:
1. 基类private成员在派生类中无论以什么方式继承都是不可见的。基类的私有成员还是被继承到了派生类对象中,但派生类对象不管在类里还是类外都不能去访问它。
2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3. 基类的私有成员在派生类都是不可见。基类的其他成员在派生类的访问方式 :public > protected >private。
4. 使⽤关键字class时默认的继承方式是private,使⽤struct时默认的继承方式是public,但最好显式写出继承方式。
5. 在实际运用中⼀般使用都是public继承,因为protetced/private继承下来的成员都只能在派生类的类⾥⾯使⽤,实际中扩展维护性不强。
定义格式:下面我们看到Person是基类,也称作父类。Student是派生类,也称作子类。
2.基类和派生类间的转换
public继承的派生类对象可以赋值给基类的指针 / 基类的引用,但基类对象不能赋值给派生类。
3.继承中的作用域
下面给出成员函数隐藏的一个例子
B类是A类的派生类,A中定义print函数时,B中没有出现同名函数,B可调用A类中的print函数
当B类中定义print的同名函数时,B类优先调用自己类中的print函数,也可以通过作用域调用A类中的print函数
4.派生类的默认成员函数
前面我们讲到一个类的默认成员函数,不清楚的可以去看一下【c++】类和对象详解-CSDN博客
(1)派生类的构造函数会先调用基类的构造函数,把基类的成员给初始化,然后再调用自身的构造函数,我们这里可以把基类当做一个整体。如果基类没有定义默认构造函数(不传实参就可以调用的构造就叫默认构造),那么派生类在构造时必须显式地调用基类的某个已定义的构造函数。
例如下面这样在列表显示调用Person的构造
(2)派生类在调用拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝。
成功的把s1拷贝给了s2
(3)派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域
如果像下面这样写,函数会死循环,一直运行,这是因为它隐藏了基类的operator=,一直在调用自己
加上作用域就可以正常赋值
(4)派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派⽣类对象先清理派⽣类成员再清理基类成员的顺序。
(5) 派⽣类对象初始化先调⽤基类构造再调派⽣类构造。
(6)派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构。
5.继承中的静态成员变量
派生类和基类共用一个静态成员,基类定义了static静态成员,则整个继承体系里只有⼀个这样的成员。无论派生出多少个派生类,都只有⼀个static成员实例
下面给出一个实例
#include<iostream>
using namespace std;
class Person {
public:Person(const char* name):_name(name){}Person(const Person& p) {_name = p._name;}Person& operator=(const Person& p) {_name = p._name;return *this;}string _name;//姓名static int _cnt;
};
int Person::_cnt = 1;class Student :public Person {
public:Student(const char* name,string id):Person(name),_No(id){}Student(const Student& s):Person(s),_No(s._No){}Student& operator=(const Student& s) {if (this != &s) {Person::operator=(s);_No = s._No;}else {return *this;}}
private:string _No; //学号
};
int main(){Person p("z");Student s1("l","2024");Student s2(s1);Student s3("w", "2023");// 结果可以看到⾮静态成员_name的地址是不⼀样的// 说明派生类继承下来了,基类 派生类对象各有⼀份cout << &s1._name << endl;cout << &s2._name << endl;cout << &s3._name << endl;cout << &p._name << endl;cout << endl;// 这⾥的运⾏结果可以看到静态成员_cnt的地址是⼀样的// 说明派⽣类和基类共⽤同⼀份静态成员cout << &p._cnt << endl;cout << &s1._cnt << endl;cout << &s2._cnt << endl;cout << &s3._cnt << endl;return 0;
}
6.继承与友元
友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员 。
7.多继承及其菱形继承问题
单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后面。
菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承有数据冗余和二义性的问题,在Assistant的对象中Person成员会有两份。支持多继承就⼀定会有菱形继承,实践中我们不建议设计出菱形继承。
Assistant的对象中Person成员会有两份!
不过可以用虚继承解决这种问题,也就是在Student类和Teacher类继承Person类时加上virtual
//使⽤虚继承Person类
class Student : virtual public Person
{
protected:
int _num; //学号
};
// 使⽤虚继承Person类
class Teacher : virtual public Person
{
protected:
int _id; // 职⼯编号
};
此时Assistant内部空间如图
虚基表指针vbptr,指向虚基表。
而虚基表中会存在偏移量,这个量就是表的地址到父类数据地址的距离。
如果这篇文章对你有帮助的话,请给博主一个免费的赞鼓励一下吧~ 💓
本文仅简单介绍了有关继承的一些基本概念和相关代码实现,以上个人拙见,若有错误之处,希望各位能提出宝贵的建议和更正,感谢您的观看!