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

初识C++ · 继承(1)

目录

前言:

1 继承的概念和定义

2 基类与子类的赋值转换

3 继承中的作用域

4 派生类的默认成员函数

4.1 构造函数

4.2 拷贝构造

4.3 赋值重载

4.4 析构函数


前言:

对于面向对象这门语言的三大特性 -> 封装 继承 多态,我们已经学习了封装,这里简单理解一下封装,在面向过程的时候,数据和方法(函数)的分离开来的,所以C语言干什么事情都是要自己造轮子,比较麻烦,对于C++ 来说,有了类和对象这个概念,就可以把数据和方法放在一起,那么访问数据就更容易,不需要自己造轮子,这是一种封装,比如不同的数据结构,顺序表链表等,C++有专门的头文件,这也是一种封装,对于反向迭代器来说,是对迭代器的一种封装。

那么今天,就进入到继承这一特点来。


1 继承的概念和定义

继承,顾名思义,从上一代传下来的,比如家中的传家宝可以继承下来给你使用,比如你父亲的财产也可以继承给你,再比如说,某种情况下,私房钱明面上不能继承给你,但是可以间接的继承给你。

在C++中的继承,比如有两个类,他们有着一样的成员变量,比如说人和学生,都有名字,年龄,性别,身高这些概念,我们定义两个类的时候,重复的元素太多,写起来就没那么舒服,那么使用继承,即学生有人的特点,也有属于自己的特点,比如学号等等,就不需要重复定义许多东西了。

继承的定义如下:

class Person
{
public:private:};class student : public Person
{
public:private:};

定义的格式就是class 类名 :继承方式 类名.

其中继承方式一共有3种,public protected private。

在类和对象章节protected private是没有什么区别的,在继承这里,就有区别了,我们先看不同的继承方式对于访问权限的区别:

不难发现一个规律就是 两两权限继承之后,权限都是变成两两中权限小的那个,比如public 继承基类 ,也就是父类中的protected成员后,权限变成了protected,这里protected 和 private的区别就出来了:

权限大小 public > protected > private。

但是问题来了,protected private的成员都访问不了,继承下来有什么用处呢?实际上protected的成员变量我们可以间接的访问:

class Person
{
public:void Func(){cout << _num << endl;}
protected:int _num = 18;
private:string _name = "zhangsan";
};
class student : public Person
{
public:private:int _id = 232323;
};int main()
{student s1;s1.Func();return 0;
}

比如这里的打印就是一种间接的访问,通过基类继承下来的成员函数等,进行修改访问打印都是没有问题的。

C++的基础是C语言,那么C语言常用的是struct,C++里面可不可以使用struct来继承呢?

答案是可以,但是这里的继承默认是public,使用class默认的是private继承:

struct St
{void Func(){cout << "Func()" << endl;cout << _num << endl;}int _num = 0;string _name = "xx";int _age = 18;string address = "earth";
};struct Ss : St
{void Func(){cout << _age << endl;}
};

2 基类与子类的赋值转换

对于内置类型来说,直接赋值是没有问题的,对于自定义类型来说,直接赋值一般也是没有问题的,大不了就是涉及到拷贝临时对象然后赋值而已:

int a = 1;
int b = a;
string s1 = "xxxx";

那么基类和子类直接能否赋值呢?答案是可以,但是也不完全可以。

子类可以赋值给基类,但是基类不能赋值给子类,这里引入一个切片的概念,假如基类有5个成员变量,继承给子类后,子类加上自己的成员变量,就有7个成员变量了,那么子类赋值过去的时候,就会把自己的成员变量给切掉,赋值的部分是继承的部分,那么相反的,如果是基类赋值给子类,怎么赋值?子类有那么多成员变量,基类没办法赋值:

class Person
{
public:protected:int _num = 18;
private:string _name = "zhangsan";
};class student : public Person
{
public:private:int _id = 232323;
};int main()
{student s1("aaa");Person p1;p1 = s1;return 0;
}

3 继承中的作用域

截止到现在,我们学习了局部域 全局域 类域 命名空间域,加上今天继承中的作用域,就有5个域了。

当局部域和全局域中都有一个整型a的时候,打印往往是打印的局部域中的a,那么同理,如果基类和子类都有一个相同名字的变量,打印的往往是最近的那个,比如:

class Person
{
public:protected:int _num = 18;
};class student : public Person
{
public:void Func(){cout << _num << endl;}private:int _id = 232323;int _num = 0;
};int main()
{student s1;s1.Func();
}

这段代码的打印结果就是0,那么我们就是想要打印基类中的_num怎么办呢?这时候就需要域名访问限定符:

class Person
{
public:protected:int _num = 18;
};class student : public Person
{
public:void Func(){cout << Person::_num << endl;}private:int _id = 232323;int _num = 0;
};

这时候的打印结果就是18。

这种现象叫做隐藏,因为两个变量名是一样的,但是继承下来之后,基类的就被隐藏了,我们需要加点手段才能访问得到。

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

函数也是同理可得。


4 派生类的默认成员函数

默认成员函数有6个,取地址重载那两个不用管,我们需要注意构造函数,析构函数,拷贝构造函数以及赋值重载。

4.1 构造函数

class Person
{
public:Person(const char* name):_name(name){}private:string _name = "zhangsan";
};
class student : public Person
{
public:student():_id(1){}private:int _id = 232323;
};

类的构造函数对于自定义类型来说会调用它自己的构造函数,那么student继承了Person,Person相当于在student里面了,所以我们应该把student看成两部分,一部分是student,一部分是Person,那么构造函数调用的时候,就需要初始化两部分,一部分是student自己的成员变量,一部分是Person的成员变量。

按照上面的写法,是有问题的,student的对象创建好了后,_id的初始化没问题,但是Person的初始化就有问题了,因为没有默认构造函数,name是什么编译器也不知道,就会报错,那么如果加上:

class Person
{
public:Person(const char* name = "hhh"):_name(name){}private:string _name = "zhangsan";
};

就不会报错了。

那么问题来了,如果我们不想提供默认构造函数怎么办,就是想要传参,这时候需要用到student的初始化列表了:

class Person
{
public:Person(const char* name):_name(name){}private:string _name = "zhangsan";
};
class student : public Person
{
public:Person(const char* name):Person(name),_id(1){}private:int _id = 232323;
};

这里的语法看起来有点怪?

我们可以这样理解,构造的时候,调用的有两个构造函数,一个是子类自己的,一个是基类的,但是基类的如果没有默认的拷贝构造函数,我们就需要自己显式的去调用基类的构造函数。

4.2 拷贝构造

拷贝构造和构造函数一样,都要为基类考虑,这里有个问题就是,应该怎么调用它自己的拷贝构造?

class student : public Person
{
public:student(const char* name):_id(1),Person(name){}student(const student& s):_id(s._id),Person(s){}private:int _id = 232323;
};

就直接传s就可以了,语法稍稍有点怪,但不大。

4.3 赋值重载

赋值重载这里,我们要考虑的是如何调用基类函数的赋值重载:

	student& operator=(const student& s){if (this != &s){operator=(s);_id = s._id;}return *this;}

如果这样调用,就会栈溢出,基类和子类的赋值重载的函数名一样,那么就构成了隐藏关系,所以需要我们显式的调用基类的赋值重载函数:

student& operator=(const student& s)
{if (this != &s){Person::operator=(s);_id = s._id;}return *this;
}

4.4 析构函数

析构函数在这里就是很有说法的了,如果我们显式的去调用析构函数,就无法满足析构函数的先子后父原则。

这里引入两个原则,构造是先父后子,析构是先子后父,这也好理解,构造方面,如果基类都没有先构造好怎么继承给子类?

析构函数同理,如果我们先显式的调用了基类的析构函数,就无法满足先子后父了,所以如果加上打印观察的话,就会发生析构了两次的情况出现,如果碰上了动态开辟,就会析构两次从而导致程序挂掉。

那么这里,我们写析构函数只需要:

~student()
{// 显示写无法先子后父//Person::~Person();cout << "~Student()" << endl;// 注意,为了析构顺序是先子后父,子类析构函数结束后会自动调用父类析构
}

只调用子类的析构就好了,基类的析构会自己调用的。

实际上,子类的析构和基类的析构构成了隐藏关系,这里先了解,因为都是析构,编译器对函数名继续特殊处理,使析构函数的名字都变成destructor。


感谢阅读!

相关文章:

  • 乐鑫ESP32相关资料整理
  • 喜马拉雅项目调整
  • 让NSdata对象转变成UIImage对象再裁剪图片的方法
  • Linux--视频推流及问题
  • 新渠道+1!TDengine Cloud 入驻 Azure Marketplace
  • 代码随想录刷题复习day01
  • Java多线程设计模式之保护性暂挂模式
  • 关于Threejs的使用二
  • 东芝-Soft Limit 报警及其解决办法
  • 代码随想录算法训练营第29天(贪心)|455.分发饼干、376. 摆动序列、53. 最大子序和
  • C语言 图的基础知识
  • HTTP/2 协议学习
  • VMware ESXi 8.0U2c macOS Unlocker OEM BIOS ConnectX-3 网卡定制版 (集成驱动版)
  • 流程图工具评测:十大热门软件对比
  • Spring Boot 中如何解决跨域问题、Spring Cloud 5大组件、微服务的优缺点是什么?
  • Angular 2 DI - IoC DI - 1
  • Apache Zeppelin在Apache Trafodion上的可视化
  • Debian下无root权限使用Python访问Oracle
  • express.js的介绍及使用
  • git 常用命令
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • jquery cookie
  • magento 货币换算
  • MySQL的数据类型
  • Odoo domain写法及运用
  • ReactNative开发常用的三方模块
  • React-redux的原理以及使用
  • SwizzleMethod 黑魔法
  • V4L2视频输入框架概述
  • 阿里研究院入选中国企业智库系统影响力榜
  • 检测对象或数组
  • 警报:线上事故之CountDownLatch的威力
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 前端性能优化--懒加载和预加载
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 实习面试笔记
  • 手机端车牌号码键盘的vue组件
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • 阿里云API、SDK和CLI应用实践方案
  • # dbt source dbt source freshness命令详解
  • # 职场生活之道:善于团结
  • #nginx配置案例
  • (4) PIVOT 和 UPIVOT 的使用
  • (二)十分简易快速 自己训练样本 opencv级联lbp分类器 车牌识别
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (论文阅读30/100)Convolutional Pose Machines
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (十二)Flink Table API
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (原創) 是否该学PetShop将Model和BLL分开? (.NET) (N-Tier) (PetShop) (OO)
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)fock函数详解