当前位置: 首页 > news >正文

C++笔记---继承(上)

1. 继承的简单介绍

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。

简单来说,被继承的类叫做父类(或基类),继承自父类的类叫做子类(或派生类)

子类拥有父类的所有成员,在此基础之上可以对父类进行拓展。

1.2 子类的定义方式

class 子类名 : 访问限定符 父类名
{// 拓展内容
}

通常来说,父类和子类具有类别上的包含关系

例如,老师和同学不仅具有人的基本特点,还在人的基础之上有了自己的拓展,而老师和同学都属于人。

我们在用C++进行描述的时候,就可以将老师和同学设计成人的子类:

人类(父类):

class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){cout << "void identity()" << _name << endl;}
protected:string _name = "张三"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄
};

学生类(子类):

class Student : public Person
{
public:// 学习void study(){// ...}
protected:int _stuid; // 学号
};

老师类(子类):

class Teacher : public Person
{
public:// 授课void teaching(){//...}
protected:string title; // 职称
};

1.3 继承方式

访问限定符限定的是继承的方式,不同访问限定符下的继承方式如下:

类成员 / 继承方式publicprotectedprivate
父类的public成员子类的public成员子类的protected成员子类的private成员
父类的protected成员子类的protected成员子类的protected成员子类的private成员
父类的private成员子类无法直接显式访问子类无法直接显式访问子类无法直接显式访问

子类成员的访问权限:public > protected > private > 父类中被修饰为private

其中,protected访问限定符是伴随着继承的出现而出现的。被它修饰的成员,意味着无法在类外部进行访问,而可以在类内部或其子类中被访问

无论子类以何种方式继承,在父类中被修饰为private的成员子类都不可直接显式访问。

继承方式用于限定继承下来的成员访问权限不能高于继承方式。

当不指定继承方式时,class子类默认为private方式继承,struct子类默认为public方式继承。

在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类类里使用,实际中扩展维护性不强。

可以采用protected/private继承的一个例子:

#include<vector>
#define CONTAINER std::vectortemplate<class T>
class stack : private CONTAINER<T>
{
public:void push(const T& x){CONTAINER<T>::push_back(x);}void pop(){CONTAINER<T>::pop_back();}const T& top(){return CONTAINER<T>::back();}bool empty(){return CONTAINER<T>::empty();}
};int main()
{stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();} return 0;
}

这里采用了继承vector的方式来实现stack,但我希望用户只用stack的接口而不直接访问vector的成员函数,此时就可以采取protected/private的继承方式。

1.4 继承类模板

相信细心的小伙伴已经发现了,在上面stack的例子中,我们每次调用vector的接口时都对其类域进行了指定,否则会发生编译报错:

error C3861: “push_back”: 找不到标识符

这是因为,模板是按需实例化的,当类模板中的函数没有被调用时,其就不会实例化

我们在实例化stack<T>对象时,vector<T>对象也跟着实例化。但是vector<T>中,只有成员变量和构造函数被实例化了

我们在调用push_back()函数时,由于this指针为stack<T>类型,所以编译器并不会将vector模板中的push_back()函数实例化,而是直接去寻找其定义或声明

所以,继承模板类时,调用父类函数要注意指定类域。

2. 父类和子类的转换

1. public继承的子类对象可以赋值给父类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切出来,父类指针或引用指向的是子类中切出来的父类那部分。

2. 父类对象不能赋值给子类对象。

3. 父类的指针或者引用可以通过强制类型转换赋值给子类的指针或者引用。但是必须是父类的指针是指向子类对象时才是安全的。(这里父类如果是多态类型,可以使用RTTI(Run-Time TypeInformation)的dynamic_cast 来进行识别后进行安全转换)

class Person
{
protected:string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{
public:int _No; // 学号
};int main()
{Student sobj;// 1.派⽣类对象可以赋值给基类的指针/引⽤Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错sobj = pobj;return 0;
}

 3. 继承中的作用域及"隐藏"规则

隐藏规则:

1. 在继承体系中父类和子类都有独立的作用域。
2. 子类和基类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏。(在子类成员函数中,可以使用"父类::父类成员"显式访问)

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是⾮常容易混淆
class Person
{
protected:string _name = "⼩李⼦"; // 姓名int _num = 111; // ⾝份证号
};class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " ⾝份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}
protected:int _num = 999; // 学号
};int main()
{Student s1;s1.Print();return 0;
};

 这里,程序运行的结果为

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同(分别在父类和子类中定义的函数只会触发隐藏而不会触发函数重载)就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员

class A
{
public:void fun(){cout << "func()" << endl;}
};class B : public A
{
public:void fun(int i){cout << "func(int i)" << i << endl;}
};

此时,B中的fun函数会隐藏掉A中的fun函数,而不会构成重载。

4. 子类的默认成员函数

子类的默认成员函数主要是用于处理父类没有的成员成员变量,父类自身的成员交由其自己的默认成员函数去处理。

4.1 构造函数

子类在构造函数的初始化列表中可以显式调用父类的构造函数对父类成员变量进行初始化,若未显式调用则会调用父类的默认构造函数。

注意:无论是否显式调用,父类的构造函数都是在子类构造函数的初始化列表中被调用的。

如果没有显式调用父类的构造函数,且父类没有默认构造函数,那么此时就会发生报错。

显式调用父类构造函数的方式是:

父类名(参数列表)
Student(const char* name, int num):Person(name), _num(num)
{cout << "Student()" << endl;
}

由于父类和子类的可以发生转换,所以在子类的拷贝构造中,将子类对象直接传给父类的构造函数即可调用父类的拷贝构造:

Student(const Student& s):Person(s), _num(s._num)
{cout << "Student(const Student& s)" << endl;
}

4.2 赋值重载

必须要在子类的operator=中显式调用父类的operator=才能按照预期正常的对父类成员进行拷贝,否则只会完成浅拷贝。

要注意,子类的operator=会隐藏父类的operator=,调用父类的operator=需要指定类域。

Student& operator = (const Student& s)
{cout << "Student& operator= (const Student& s)" << endl;if (this != &s){// 构成隐藏,所以需要显⽰调⽤Person::operator =(s);_num = s._num;} return* this;
}

4.3 析构函数

父类的析构函数会在子类的析构函数被调用之后自动调用,无需显式调用。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 香港电讯SASE解决方案:终端与云端的安全护航
  • FloodFill(洪水灌溉)算法专题——DFS深搜篇
  • 【C#生态园】选择最适合你的工具:C# GUI库完整比较及指南
  • C++第二讲:类和对象
  • 从入门到精通,玩转Python的print函数(探索Python print函数的隐藏功能)
  • 实时数仓3.0DWD层
  • kali里面搭建docker容器
  • leetcode 难度【简单模式】标签【数据库】题型整理大全
  • 小红书热门系列,风口副业项目AI宠物壁纸号,玩法分享
  • Go语言入门实战教程(超详细)从零基础入门到高级实战,看完就懂了(2024年新版,建议收藏)
  • Python 课程15-PyTorch
  • 产品探秘|开物——面向AI原生和云原生网络研究的首选科研平台
  • 电脑的主板,内存条插多少合适?
  • SpringBoot入门(黑马)
  • 抖音生活服务常见玩法及收益情况详解!普通人如何把握机会?
  • 【5+】跨webview多页面 触发事件(二)
  • Android 架构优化~MVP 架构改造
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • Asm.js的简单介绍
  • centos安装java运行环境jdk+tomcat
  • emacs初体验
  • Gradle 5.0 正式版发布
  • js正则,这点儿就够用了
  • Mysql优化
  • vue:响应原理
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 将 Measurements 和 Units 应用到物理学
  • 学习JavaScript数据结构与算法 — 树
  • 正则与JS中的正则
  • MyCAT水平分库
  • ​Linux·i2c驱动架构​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • # .NET Framework中使用命名管道进行进程间通信
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #vue3 实现前端下载excel文件模板功能
  • #图像处理
  • #我与Java虚拟机的故事#连载14:挑战高薪面试必看
  • $(selector).each()和$.each()的区别
  • $.ajax()参数及用法
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (2.2w字)前端单元测试之Jest详解篇
  • (4) PIVOT 和 UPIVOT 的使用
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (ros//EnvironmentVariables)ros环境变量
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (离散数学)逻辑连接词
  • (四)linux文件内容查看
  • (一)模式识别——基于SVM的道路分割实验(附资源)
  • (一)为什么要选择C++
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • .chm格式文件如何阅读
  • .java 9 找不到符号_java找不到符号
  • .net dataexcel 脚本公式 函数源码
  • .NET 读取 JSON格式的数据