【C++】-关于类和对象的默认成员函数(中)-拷贝构造函数和赋值运算符重载函数
💖作者:小树苗渴望变成参天大树
❤️🩹作者宣言:认真写好每一篇博客
💨作者gitee:gitee
💞作者专栏:C语言,数据结构初阶,Linux,C++
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!
文章目录
- 前言
- 一、案例引入
- 二、拷贝构造
- 三、案例引入
- 四、运算符重载
- 五、总结
前言
今天博主又来更新新的文章了,今天我们接着上面的内容就下两个默认成员函数,讲完这两个,剩下来的两个就简单了,因为用到也不多,今天讲的这个两个也特别的关键,尤其是第一个也不好理解,我尽量使用易懂的语言给大家讲解,而且要用到之前的栈类,日期类,myQueue类,话不多说,我们开始进入正文。
一、案例引入
在我们之前学习的内置类型我定义一个整型变量
int a=10;
此时我想定义一个和a是一样的变量怎么做:
int b=a;
内置类型是这样就可以解决问题了。
对于自定义类型我们如果也这样呢??
Date d1(2023,5,1);
Date d2=d1;
在C++里面是不允许这么赋值的,在传营参的时候也不是直接把对象1直接赋值给对象2,必须要通过调用拷贝构造函数去实现。
拷贝构造函数其实是特殊的构造函数,也是完成初始化操作的,所以有些特性和构造函数一样,无返回值,函数名和类名相同,形参是固定的
拷贝构造函数: 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
我们来看具体写法:
大家可以看到完成我们想要的效果。
解决困惑:
1.为什么要加引用
我给大家举一个例子:
class Date
{
public:void print(){cout << _year << " " << _month << " " << _day << endl;}Date()//无参构造函数{_year = 1;_month = 1;_day = 1;}Date(const Date& d)//拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};void func(int m){}
void func(Date d){}
int main()
{Date d1;//调用无参构造器初始化d1;func(10);//对于传内置类型是直接把值赋给形参,在一开始就讲过了func(d1);//这个传给形参需要调用拷贝构造,然后拷贝构造里面的形参也是通过实参传过去有会形成新的拷贝构造,我们一起来看调用会出现什么情况//Date d2(d1);//使用d1对象初始化d2,是两个不同的对象,但是里面的内容是一样的//d1.print();/*d2.print();*/return 0;
}
大家看到我们箭头指向第二个func的时候不是直接跳到函数体里面,而是直接跳到拷贝构造那里了,因为现在的编译器都强制检查,不使用引用就会报错,如果不加能调试你就会看到箭头一致在拷贝构造哪一行,永远进不去拷贝构造体里面,无线递归下去,就像下面的情况一样:
这时候就需要使用到引用,形参就是实参的别名,不需要像传值一样,还要创建历史变量在赋值过去,所以之前的传值的拷贝构造不行,传引用就直接使用也不需要创建临时变量,所以可以直接进入函数体里面,这一定是最不好理解的,所以大家一定对于调用函数的传参机制要弄明白,而且要之前自定义类型和内置类型的赋值方式是不一样的(传参就是一种赋值)
2.为什么要加const
加const是怕有人写反,例如:
这样不仅仅没有给d2进行初始化,反而让自己的值也改变了,所以加一个const就刚好的解决了这个问题
通过上面的例子,我们大致知道了拷贝构造函数的用法,以及拷贝构造函数的特征有那些,也解释了拷贝构造使用时候该注意的细节是什么,算是入门了,但拷贝构造的细节往往不止这些,让我们正式进入拷贝构造的讲解
二、拷贝构造
在案例引用那一块我已经介绍了拷贝拷贝构造的一部分细节,在文章的开头,我提到过,拷贝构造函数也是默认成员函数,不写,编译器会默认生成的,那我们来看看不写会出现什么情况:
大家发现这写不写拷贝构造效果都一样啊
大家在来看一下栈类如果这样会出现什么情况:
我们发现我们不写构造函数就会出现问题,我们通过调试来看看什么时候出现这样的错误:
我们来看一下图解:
报错的原因是对同一块地址析构了两次,在st1结束时调用析构释放了那块空间,在st2结束时有调用了析构函数,对已经释放的空间在次释放,在动态内存管理那一节明确说过这样是不可以的。
那为什么会出现这样的情况呢??
拷贝构造在不显示的时候,并不会像构造函数一样对内置类型不做任何处理,而是会将内置类型按照字节拷贝去进行拷贝的,也就是值拷贝或者叫浅拷贝,跟memcpy类似,刚才那种情况是,那三个成员变量都是内置类型,指针也属于内置类型,所以才会出现刚才的问题,那么怎么解决这个问题,显然默认生成的肯定不行,就需要自己写一个拷贝构造函数,采取深拷贝,这里就提一下,后面的博客会重点介绍,我们来看一下深拷贝是怎么解决这个问题的:
Stack(const Stack& st){_array = (int*)malloc(sizeof(int) * st._capacity);if (_array == NULL){perror("malloc:");exit(-1);}memcpy(_array, st._array, sizeof(int) * st._size);_size = st._size;_capacity = st._capacity;}
相信大家应该知道怎么处理了吧,并且一开始那种不止是析构两次的问题,在操作的是另一个会影响另一个,因为公用一块空间
可以简单的理解,拷贝构造函数就是为深拷贝而生的,也体会到了创造者的厉害之处
但是刚才对于默认生成的拷贝构造函数,对内置类型会做处理,对自定义类型呢?我们来看效果:
对于自定义类型,编译器默认生成的拷贝构造会自动调用自定义类中的拷贝构造,这一点和构造函数,析构函数类似希望大家可以更好的理解
什么时候需要自己写拷贝构造呢??
- 都是内置类型并且没有动态申请资源的,就可以不用自己写拷贝构造。
- 全部都是自定义类型的时候也不需要写,典型就是两个栈实现队列的时候
具体问题具体分析,希望大家可以理解。
到这几乎把拷贝构造讲解清楚了,大家一定要好好消化,接下来我将讲解运算符重载函数
三、案例引入
我们来看一下整型怎么比较大小的:
int a=10;
int b=20;
a<b;
那我们来看一下自定义类型:
Date d1(2023,5,1);
Date d2(2022,4,30);
d1>d2;
我们看到显然这样是不行的,我们需要写一个函数来进行比较大小,我直接将函数体内容写出来:
bool Less(const Date& x1, const Date& x2)
{if (x1._year < x2._year)//年小就小{return true;}if (x1._year == x2._year && x1._month < x2._month)//年不小,月小就小{return true;}if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)//年月不小,天小就小{return true;}return false;//所以小的都找到,剩下的就是大的
}
大家看我们上面都是报错,原因就是,我在类外写的函数体,恰好成员变量是私有的,只能在类里面使用,所以这个时候就会报错,就将成员变量的权限改成共有的就可以了,也可以将函数放到类里面但是还有好多细节,一会在说
大家可能认为这么解决问题很简单,但是如果有人的函数名写的千奇百怪的怎么办,又不写注释,那么我们使用起来就非常的难受,我们希望的是一开始那种,直接使用运算符来进行比较,清晰明了,这时候就要使用运算符重载来做,
- 我先将成员变量的权限变成共有的,方便运行,平时都是私有的,安全性高
- 我们在运算符前面加一个operator就可以重载运算符
- 因为输出流插入的优先级高于运算符所以要加括号
cout << (d1<d2)<<endl;
cout << operator<(d1, d2) << endl;//这两种写的效果一样
在底层的汇编两者是一样的指令。
到现在大家应该已经算是初步了解的运算符重载,他的目的就是将原来的运算符进行一个新的定义,因为自定义类型的大小比较只有设计者自己知道,但为了让代码看的显而易懂,创造者就引出了运算符重载。接下来正式开始介绍运算符重载,也是给一个新的知识做铺垫
四、运算符重载
为什么要讲运算符重载,一是他非常重要,二是方便讲赋值运算符重载,他是默认的成员函数。在案例引用的时候,我们发现将函数体写在外面,成员函数出现了无法访问的情况,那我们将函数写在类中,看看需要注意什么:
出现参数太多的情况,原因是成员函数都会有一个隐含的this,所以这里面报参数过多,==我们大部分的运算符都是二元运算符,所以在运算符重载几乎都是只有一个参数
这样就可以了,上面那种方式就不可以这么写了
cout << (d1<d2)<<endl;
cout << d1.operator<(d2) << endl;//必须写成这样的形式,d1.才能将d1的地址传给this指针
大家应该知道元素在类中是怎么使用的吧,接下来讲解一个重要的知识,赋值运算符重载,上面是小于运算符重载,这也是默认成员函数只有,不写就会自动生成,让我们这个函数是怎么使用的,和注意的细节
赋值运算符重载:
对于赋值运算符重载,我们只能卸载类里面,不能写成全局的
用一个已经存在的对象去初始化另一个的对象,这是拷贝构造
已经存在的两个对象之间的赋值拷贝,这是赋值运算符重载
大家一定要理解这两个,不然一开始很容易将拷贝构造和赋值运算符搞混,觉得是同一个东西,实际上还是有本质的差别
d1成功被d2赋值了,这是目前写的一个最简单版本的赋值运算符重载,相比较拷贝构造,他又返回值,而拷贝构造没有,但是这种写的不太完美,万一我想连续赋值呢??
d4=d3=d2=d1;
我们以整型为例:
int i,j,k=0;
i=j=k=0;
在整型的时候可以这样写,原因是k=0,返回的是k,j=k返回的是j,i=j返回的是i,每个运算符返回的都是对应类型的,那么我们赋值运算符重载是不也要有类型返回值:
这么写还是不太完美,用值返回,我们在函数那一将说过,返回的值,需要创建临时变量,先将值拷贝到临时变量上,在返回,而我们上面说过,对于自定义类型的拷贝,需要调用拷贝构造,我们来看看效果:
有四次返回就要四次调用拷贝构造,怎么解决这个问题呢??我们就需要使用到引用返回,对于引用返回我们需要主要几个点,局部变量不能哟个引用很危险,静态的可以用引用,对于这里,我们的this是局部变量出了我们的赋值运算符函数就会被销毁,但是我们返回的是*this,*this就是对象了,他的生命周期是main函数,所以不会随着赋值运算符的结束而销毁,所以可以使用引用返回
这里就不用调用拷贝构造减少消耗,提高效率,但是我们还需要完善,有的人会这么写:
d1=d1;
这样没有什么意义,避免这样我们需要加一个if判断:
相同对象的地址肯定能够是一样的,有的人会这么写:
if(*this!=d)
这样写的前提是重载了!=运算符,那这样成本太高,不如直接用地址来判断。
说到这里,大家应该可以体会到我之前写的C++入门那篇博客的主要性了吧,前后知识都是连贯的
赋值运算符重载也是默认的成员函数,不写会默认生成,我们看看默认生成的会干那些事:
大家看到我们没有写,居然达到了同样的效果,那我们写他为了干什么,对于内置类型我们会完成浅拷贝,但是又自定义类型,我们就会去调用他的赋值运算符重载,我们来看效果:
我们来看看效果,会去调用栈里面的赋值运算符重载
所以赋值运算符重载的操作行为和构造函数的行为一样,对内置类型完成浅拷贝,对于自定义类型去调用他的赋值运算符。
什么时候需要写赋值运算符重载??
- 全部都是内置类型的时候不需要写(日期类)
- 有动态开辟的空间需要写(栈类)
- 都是自定义类型的不需要写(MyQueue类)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
现。
五、总结
今天重点讲解了拷贝构造和运算符重载,这都是默认成员函数比较重要的知识点,大家一定要学号,后面的学习都是围绕这些基础展开讲解的,接下来我会写一篇博客,来完善我们的日期类,把我们这两篇总结的知识点运用一下,我们一个六个默认成员函数,目前已经讲解了四个,剩下来的两个不是重点,后面我在单独写一篇博客,给大家简单介绍一下即可,现在我们的任务就是完成这两篇博客的学习和练习。希望大家都来学到知识。我们下篇再见
相关文章:
【C++】-关于类和对象的默认成员函数(中)-拷贝构造函数和赋值运算符重载函数
💖作者:小树苗渴望变成参天大树 ❤️🩹作者宣言:认真写好每一篇博客 💨作者gitee:gitee 💞作者专栏:C语言,数据结构初阶,Linux,C 如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点…...
c++11上篇
c11 1.C11简介2.列表初始化2.1 {}初始化2.2 std::initializer_list 3.变量类型推导3.1 auto3.2 decltype3.3 nullptr 4.范围for循环5.final与override6.智能指针7.新增加容器---静态数组array、forward_list以及unordered系列8.默认成员函数控制9.右值引…...
异构无线传感器网络路由算法研究(Matlab代码实现)
目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨💻4 Matlab代码 💥1 概述 无线传感器网络(Wireless Sensor Networks, WSN)是一种新型的融合传感器、计算机、通信等多学科的信息获取和处理技术的网络,…...
MySQL数据库——MySQL TRUNCATE:清空表记录
MySQL 提供了 DELETE 和 TRUNCATE 关键字来删除表中的数据。下面主要讲解一下 TRUNCATE 关键字的使用。 TRUNCATE 关键字用于完全清空一个表。其语法格式如下: TRUNCATE [TABLE] 表名 其中,TABLE 关键字可省略。 例 1 新建表 tb_student_course&…...
财报解读:连续三年逆势增长的背后,欧派家居到底靠的是什么?
能在过去3年逆势增长的家居企业并不多,而欧派家居就是其中一个。4月25日,欧派家居发布2022年年度报告。据年报数据显示,2022年,欧派家居共实现营业收入224.80亿元,净利润约26.88亿元。 从2020年到2022年,欧…...
希望计算机专业同学都知道这些宝藏博主
湖科大教书匠——计算机网络 “宝藏老师”、“干货满满”、“羡慕湖科大”…这些都是网友对这门网课的评价,可见网课质量之高! 湖南科技大学《计算机网络》微课堂是该校高军老师精心制作的视频课程,用简单的语言描述复杂的问题,…...
1694_week1_MIT使用Python编程学习手记1
全部学习汇总: GreyZhang/python_basic: My learning notes about python. (github.com) 首先说明一下,这部分信息的整理只是我个人的理解。由于自己的知识功底以及英语水准,很可能会有大量的疏漏。再此,我只想把自己学习时候的一…...
第二十一章 光源
光源是每个场景必不可少的部分,光源除了能够照亮场景之外,还可以产生阴影效果。 Unity中分为四种光源类型: 1. 方向光:Directional Light 用于模拟太阳光,方向光任何地方都能照射到。 2. 点光源:Point L…...
CVPR 2023 超分辨率(super-resolution)方向上接收论文总结
目录 CVPR 2023图像超分任意尺度超分盲超分 视频超分特殊场景 总结参考资料 CVPR 2023 官网链接:https://cvpr2023.thecvf.com/ 会议时间:2023年6月18日-6月22日,加拿大温哥华 CVPR 2023统计数据: 提交:9155篇论文接…...
Python 基于 Django 的学生成绩管理系统,可视化界面(附源码,教程)
1简介 对于学生成绩管理系统,充分运用现代化的信息技术手段,对于学生成绩信息管理发展的趋势就是信息化,信息化时代下的信息管理,需要深化信息管理体制与手段的改革,充分运用信息化手段来全方位的进行学生成绩管理系统…...
第二弹进阶吴恩达 ChatGPT Prompt 技巧
第一弹笔记在这里: 总结吴恩达 ChatGPT Prompt 免费课程 今天分享第二弹,进阶篇。 第一点,任务序列化。 通常看完一篇长文,脑子里往往充满无数疑问。急切想知道所有答案,必须列一个问题清单。对话式问法,对…...
约瑟夫环问题
约瑟夫问题 题目描述 n n n 个人围成一圈,从第一个人开始报数,数到 m m m 的人出列,再由下一个人重新从 1 1 1 开始报数,数到 m m m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。…...
JavaScript中的异步编程
当我们在编写JavaScript代码时,经常会遇到需要执行长时间运行的任务的情况,例如从服务器获取数据或进行复杂的计算。在这些情况下,我们不希望阻塞用户界面,因为这会使网站看起来卡顿,甚至无响应。为了避免这种情况&…...
奥斯汀独家对话|从机构的「拉扯」中成长的美国加密监管
前言 4月25日,在美国得克萨斯州的首府奥斯汀,这座充满活力和创造力的城市,欧科云链研究院与来自哥伦比亚商学院的Austin Campbell教授就美国加密监管以及其相关话题进行了一次深入探讨。双方讨论了美国整体的监管问题、监管逻辑、最新的稳…...
PostgreSQL16中pg_dump的LZ4和ZSTD压缩
PostgreSQL16中pg_dump的LZ4和ZSTD压缩 pg_dump压缩lz4和zstd LZ4和ZSTD压缩算法合入了PG16。LZ4补丁的作者是Georgios Kokolatos。由Tomas Vondra提交。由Michael Paquier、Rachel Heaton、Justin Pryzby、Shi Yu 和 Tomas Vondra 审阅。提交消息是: Expand pg_dum…...
网络安全基础入门学习路线
在大多数的思维里总觉得学习网络安全得先收集资料、学习编程、学习计算机基础,这样不是不可以,但是这样学效率太低了! 你要知道网络安全是一门技术,任何技术的学习一定是以实践为主的。也就是说很多的理论知识其实是可以在实践中…...
错误检测技术:奇偶校验
文章目录 参考描述奇校验与偶校验错误检测技术奇偶校验 奇校验与偶校验奇校验偶校验局限性漏网之鱼 奇偶校验的三种形式水平奇偶校验垂直奇偶校验水平垂直奇偶校验优劣漏网之鱼 参考 项目描述搜索引擎Google 、Bing百度百科奇偶校验计算机网络 基础与应用(微课版&a…...
语义版本控制规范(SemVer)
Semantic Versioning 2.0.0 官网 给出一个版本号MAJOR.MINOR.PATCH,增加如下: MAJOR version 进行不兼容的API更改时MINOR version 当您以向后兼容的方式添加功能时PATCH version 当您进行向后兼容的错误修复时 预发布(pre-release )和构建元数据的附…...
基于Flask的留言板的设计与实现
这是《Flask Web开发实战:入门、进阶与原理解析》这本书中的一个小项目,我在学习后根据书中的教程实现了留言板的功能,并结合我的思路将代码做了一些调整。 下面这是实现后的展示图片 文章目录 设计思路项目代码exts.pymodels.pyforms.pyerrors.pycomma…...
vmware 详细安装教程
一.VM是什么? VMware Workstation是一个“虚拟 PC”软件。它使你可以在一台机器上同时运行二个或更多 Windows、DOS、LINUX 系统。与“多启动”系统相比,VMWare 采用了完全不同的概念。多启动系统在一个时刻只能运行一个系统,在系统切换时需…...
Python 爬虫工具
Python3 默认提供了urllib库,可以爬取网页信息,但其中确实有不方便的地方,如:处理网页验证和Cookies,以及Hander头信息处理。 为了更加方便处理,有了更为强大的库 urllib3 和 requests, 本节会分别介绍一下…...
再也不去字节跳动面试了,6年测开经验的真实面试经历.....
前几天我朋友跟我吐苦水,这波面试又把他打击到了,做了快6年软件测试员。。。为了进大厂,也花了很多时间和精力在面试准备上,也刷了很多题。但题刷多了之后有点怀疑人生,不知道刷的这些题在之后的工作中能不能用到&…...
第十五章 角色移动旋转实例
本章节我们创建一个“RoleDemoProject”工程,然后导入我们之前创建地形章节中的“TerrainDemo.unitypackage”资源包,这个场景很大,大家需要调整场景视角才能看清。 接下来,我们添加一个人物模型,操作方式就是将模型文…...
数据湖Data Lakehouse支持行级更改的策略:COW、MOR、Delete+Insert
COW:写时复制,MOR:读时合并,Delete+Insert:保证同一个主键下仅存在一条记录,将更新操作转换为Delete操作和Insert操作 COW和MOR的对比如下图,而Delete+Insert在StarRocks主键模型中用到。 目前COW、MOR在三大开源数据湖项目的使用情况,如下图。 写入时复制【Copy-On…...
双亲委派机制的原理和作用
双亲委派机制,就必须弄清楚Java的类加载器。 什么是类加载器 Java类加载器(ClassLoader)是Java运行时环境(JRE)的一部分,负责动态的将Java类加载到Java虚拟机的内存空间。 类加载器有哪些 主要有三个: 引导类加载器(Bootstrap ClassLoade…...
mac免费杀毒软件哪个好用?如何清理mac系统需要垃圾
CleanMyMac x是一款功能强大的Mac系统优化清理工具,使用旨在帮助用户更加方便的清理您系统中的所有垃圾,从而加快电脑运行速度,保持最佳性能,更加稳定、流畅、快速!!! CleanMyMac X无疑是目前m…...
css 实现太极效果
目录 一、简述二、太极效果制作 一、简述 本次主要介绍::after,::before,box-shadow这三个属性。 ::after,::before这两个是伪类选择器,box-shaow是用来设置元素的阴影效果 before:向选定的元素前插入内容 after:向选定的元素后插…...
【前端基础知识】Vue中的变量不是响应式的吗?属性赋值后视图不变化的原因是什么?
目录 🤔问题📝回答🎨使用场景动态添加属性动态添加数组元素 ❌注意事项$set只能在响应式对象上使用$set不能用于根级别的属性$set的性能问题 📄总结 🤔问题 Vue是一款在国内非常流行的框架,采用MVVM架构&a…...
如何完全卸载linux下通过rpm安装的mysql
卸载linux下通过rpm安装的mysql 1.关闭MySQL服务2.使用 rpm 命令的方式查看已安装的mysql3. 使用rpm -ev 命令移除安装4. 查询是否还存在遗漏文件5. 删除MySQL数据库内容 1.关闭MySQL服务 如果之前安装过并已经启动,则需要卸载前请先关闭MySQL服务 systemctl stop…...
[渗透教程]-004-长城防火墙GFW的原理
文章目录 1. baidu.com 请求过程2. GFW原理2.1 GFW拦截方法1:DNS渲染2.2 通过IP黑名单2.3 VPN阻断1. baidu.com 请求过程 家庭的路由器具备了交换机的功能.域名–>ip,优先检测本地的缓存,没有的话就查找DNS服务器,传输层对应该层的数据进行封装增加了端口的信息,网络层对传输…...
专业的上海网站建设公司哪家好/查询网站域名
Flutter中的第三方包 指纹识别、触摸ID、面部ID、密码、pin或图案 local_auth...
给传销做网站/简述seo的优化流程
HTML5 规范规定,用户能够为元素 自己定义非标准属性, 可是要加入 data- 前缀。 目的是为元素提供与页面渲染无关的信息、或者语义信息。这些属性名能够任意加入,仅仅要带上前缀 data- 开头就能够。<div id"myid" data-appid&quo…...
wordpress的链接怎么设置/seo怎么才能做好
四、self-attention 1、是什么? attention机制通常用在encode与decode之间,但是self-attention则是输入序列与输出序列相同,寻找序列内部元素的关系即 KVQ。l例如《Attention Is All You Need》在编码器中使用self-attention,利用…...
淘宝客网站怎么做分销/个人开发app最简单方法
源码获取:博客首页 "资源" 里下载! 一、项目简述 Java电影院系统功能: 登陆注册模块 : 普通用户可以直接访问影院主界面进行电影浏览、查询等 功能,但是当用户操作需要读取用户信息时就要求用户进 行登录了。普通用户…...
家电网站设计/百度服务热线电话
今天使用IntelliJ IDEA构建Maven聚合项目时,在子项目的pom文件中正确导入了资源后,在代码中却无法引用 下图为父项目的pom 下图为子项目的pom 下图为项目中引用 下图为Maven管理窗口 图示看出资源时正确的导入的,但是在代码使用却不行&a…...
做亚马逊学英语有什么网站吗/网络营销中的seo是指
转载于:https://www.cnblogs.com/lr86/p/6656115.html...