【C++】类和对象终篇

个人主页 : zxctscl
文章封面来自:艺术家–贤海林
如有转载请先通知

文章目录

  • 1. 前言
  • 2. 友元
    • 2.1 友元函数
    • 2.2 友元类
  • 3. 内部类
  • 4. 匿名对象
  • 5. 拷贝对象时的一些编译器优化
  • 6. 再次理解类和对象

1. 前言

在上一篇博客中提到了类和对象中的构造函数与static成员 【C++】类和对象之初始化列表与static成员,接下来一起看看类和对象中的友元。

2. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数友元类

2.1 友元函数

问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧ostream& operator<<(ostream& _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}
private:int _year;int _month;int _day;
};

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{Date d;cin >> d;cout << d << endl;return 0;
}

在这里插入图片描述
说明:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同

2.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  1. 友元关系是单向的,不具有交换性。
    比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

  2. 友元关系不能传递
    如果C是B的友元, B是A的友元,则不能说明C时A的友元。

  3. 友元关系不能继承,在继承位置再给大家详细介绍。

举个例子:声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量。

class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){_t._minute++;}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};

在Date里面要访问Time,Date是Time的友元,但是Time是不能访问Date的。但是Time是想要访问Date的,可以在Date里面声明Time,让Date和Time互为友元。

在这里插入图片描述
是类似像在Time中有含Date类的函数,但是在这里是不能用的,因为在还没有声明Date和Time互为友元,就在Time里面用了Date。
这里事先定义和声明分离。
在这里插入图片描述

一般情况下,类的友元关系是单向的,不需要互为友元。

3. 内部类

概念:**如果一个类定义在另一个类的内部,这个内部类就叫做内部类。**内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。

举个例子:像下面这样的代码中A有多大?

class A
{
public:class B{private:int _b1;};
private:int _a1;int _a2;
};int main()
{cout << sizeof(A) << endl;return 0;
}

在这里插入图片描述
为什么大小是8?
从内存的角度类不占用空间,类只是一个声明。类被编译器编译之后,只会看到类的对象和类的函数,类是不占用空间的。
这样定义只会是B这个类受A类的类域的限制。所以它里面就是它自己的成员,没有B的成员。

如果在定义一个B类对象,定义变不了,不仅仅受到类域的限制,还受到访问界定符的限制。
在这里插入图片描述
改为这样就行。
在这里插入图片描述

如果定义为私有的。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
内部类天生就是外部类的友元类
在这里插入图片描述
但外部类不能访问内部类。
在这里插入图片描述
总之:
1、内部类受到类域的限制
2、内部类天生就是外部类的友元类

4. 匿名对象

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};class Solution {
public:int func(int n) {cout << "int func(int n)" << endl;return n;}
};int main()
{// 有名对象A aa1;A aa2(10);// 匿名对象A();A(10);return 0;
}

这里就创建了4个对象:
在这里插入图片描述

C++中下面这样的被称为有名对象。
在这里插入图片描述
像下面这样的就叫做匿名对象。
在这里插入图片描述
匿名对象的特点是:生命周期只在当前一行。
与之前定义的对象不同,之前定义的对象生命周期是在当前作用域。

但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数。
即用即销毁
在这里插入图片描述
那么它有什么用呢?
举个例子:在OJ里面会有:

class Solution {
public:int func(int n) {cout << "int func(int n)" << endl;//...return n;}
};

要调用这个函数,一般得先取个名字,再调用:
在这里插入图片描述
可以考虑用匿名对象,那么就是这样。
像有点地方就传个参,定义一个匿名对象会比较好。
匿名对象就是为了让一些地方更方便。
在这里插入图片描述

5. 拷贝对象时的一些编译器优化

这个部分了解一下就可以。

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

编译器对于构造的一些优化:
一个是: 构造+拷贝
还有一个是:拷贝构造+拷贝构造
优化的结果就是合二为一。

来看看下面的代码:

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}private:int _a;
};void f1(A aa)
{}void f2(const A& aa)
{}int main()
{A aa1 = 2;  // 构造 + 拷贝构造 -》 直接构造const A& aa2 = 2;f1(aa1);f1(A(2));   // 构造 + 拷贝构造 -》 直接构造f2(aa1);return 0;
}

在这里插入图片描述
但是const A& aa2 = 2;就没有办法优化。
这里有类型转换,会产生临时变量,这里引用是临时变量。

------------------------------------------------------------------------
这里要拷贝构造:
在这里插入图片描述
如果定义一个匿名对象来传:
构造一个再拷贝构造,直接优化为直接构造
在这里插入图片描述
在这里插入图片描述
------------------------------------------------------------------------

如果不想让这里的拷贝构造发生,那么就传引用:
在这里插入图片描述
在之前老编译器上可能不会优化,所以并不是每个编译器都会优化,没有优化也是正常的。
在这里插入图片描述

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}A(const A& aa):_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A(){cout << "~A()" << endl;}private:int _a;
};void f1(A aa)
{}void f2(const A& aa)
{}A f3()
{A aa;return aa;	
}int main()
{f3();	A ret = f3();return 0;
}

在这里插入图片描述

正常情况下f3()是一个构造一个拷贝构造。
这里是传值返回,不会把aa作为值返回,它会拷贝构造一个再返回。

在不优化时:
在这里插入图片描述
如果优化的话,是和f3()是一样的。
在这里插入图片描述
aa直接构造ret,那么它是怎么构造的呢?
在函数f3()结束之前,用aa直接构造ret。
在函数栈帧里面,在main函数,在f3()结束之前,用aa拷贝构造,不要临时对象了,直接用aa直接构造ret,然后函数再结束。

在这里插入图片描述

A f3()
{A aa;return aa;	
}
A f4()
{return A();
}int main()
{A ret = f4();return 0;
}

这里是一个构造加一个拷贝构造加一个拷贝构造。这里合三为一。
在f4没有结束之前直接去构造ret。

而这个是两个构造加赋值,没有拷贝构造
在这里插入图片描述

6. 再次理解类和对象

现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:

  1. 用户先要对现实中洗衣机实体进行抽象—即在人为思想层面对洗衣机进行认识,洗衣机有什么属性,有那些功能,即对洗衣机进行抽象认知的一个过程
  2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中
  3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。
  4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。

在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象

在这里插入图片描述

有问题请指出,大家一起进步!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/512388.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【QT】在窗口中添加按钮

在窗口中添加按钮 创建一个QT程序&#xff0c;首先要做什么&#xff1f; 先创建一个应用程序类创建一个窗口类 在窗口中添加按钮 添加头文件&#xff1a;#include 没有设置按钮标题的函数&#xff0c;是因为它继承了父类&#xff0c;设置标题的函数在父类中。 因为好多子类…

十三、类的继承、访问级别

类的继承与访问控制 类的继承 使用sealed修饰的类&#xff0c;是私有类&#xff0c;不能作为基类使用C#中一个类&#xff0c;只能有一个父类&#xff0c;但是可以实现多个接口子类的访问级别不能超过父类的访问级别 using System; using System.Collections.Generic; using S…

【C语言】【洛谷】P1125笨小猴

一、个人解答 #include<stdio.h> #include<string.h>int prime(int num);int main() {char max a, min z;int maxn0, minn1000;char str[100];int num[26] { 0 };fgets(str, sizeof(str), stdin);str[strcspn(str, "\n")] \0;for (int i 0; str[i]…

CentOS的yum报错except OSError, e:

报错信息 Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile base: mirrors.cloud.aliyuncs.comextras: mirrors.cloud.aliyuncs.comupdates: mirrors.cloud.aliyuncs.com File “/usr/libexec/urlgrabber-ext-down”, line 28 except OSError, e: ^…

Gradle学习(一)

最近在学习Gradle&#xff0c;感觉有些东西没见过&#xff0c;记录一下&#xff01; 1.Gradle与Maven的目录框架 2.Gradle的命令行安装 注&#xff1a;学习常用的是使用脚手架生成gradle项目 3.

maven项目报错Cannot resolve plugin org.apache.maven.plugins:maven-war-plugin:2.2

如果IDEA整合maven没有问题&#xff0c;还是报这个错误&#xff0c;很大可能是由于在下载过程中存在网络问题&#xff0c;导致文件下载一半而停止&#xff0c;但是已经在仓库中存在这个文件夹&#xff0c;解决方法是删除文件夹重新下载即可。 删除本地仓库下的\org\apache\mav…

AI大预言模型——ChatGPT与AI绘图及论文高效写作

原文链接&#xff1a;AI大预言模型——ChatGPT与AI绘图及论文高效写作 2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网…

灯丝灯双通道低过温高压线性恒流芯片SM2082ED的应用及特性解析

双通道低过温高压线性恒流芯片是一种电子芯片&#xff0c;它具有双通道设计&#xff0c;可以在高电压条件下工作&#xff0c;并具有低过温特性。这种芯片通常用于需要高电流和高电压的应用&#xff0c;如LED照明、激光器、电机驱动等。 双通道设计意味着该芯片可以同时处理两个…

分布式测试插件 pytest-xdist 使用详解

使用背景&#xff1a; 大型测试套件&#xff1a;当你的测试套件非常庞大&#xff0c;包含了大量的测试用例时&#xff0c;pytest-xdist可以通过并行执行来加速整体的测试过程。它利用多个进程或计算机的计算资源&#xff0c;可以显著减少测试执行的时间。高计算资源需求&#…

保姆级GeoWebCache矢量瓦片切片流程

1矢量切片解决方案 1.1Geoserver配置geowebcache插件 参考文章 (53条消息) 独立安装geoservergeowebcache发布arcgis切片服务_itouch_ok的专栏-CSDN博客 1.将下载好的geoserver 2.19.3安装部署 将下载好的geowebcache 2.19.3的war包解压到 GeoServer 安装目录下./usr/loc…

生活干货知识:手机流量卡网速不佳的常见原因及解决办法

最近有很多的小伙伴后台留言给我&#xff0c;为什么有时候手机网路那么差呢&#xff1f;甚至连一个基本的网页都打不开&#xff0c;播放视频和电影更不用说了&#xff0c;这样严重影响了我们的使用体验&#xff0c;到底什么原因导致的呢&#xff1f;相信很多小伙伴至今仍然未清…

文件恢复软件推荐,数据恢复,很简单!

“我有一些很重要的文件被误删了&#xff0c;尝试了各种方法都无法找回&#xff0c;听说借助文件恢复软件或许有机会找回重要的数据&#xff0c;大家有什么比较好用的文件恢复软件推荐吗&#xff1f;” 在数字化时代&#xff0c;电脑和各种存储设备对我们来说都是比较重要的&am…